windows编程: 进程间通信

207 阅读10分钟

前言

windows出于安全考虑,进程间的内存是隔离的.数据也是隔离的.所以,两个进程如果需要通信(同一台设备),需要通过其他的方式进行处理.

邮槽

邮槽是一种简单的 IPC 机制,允许一个或多个进程发送消息到一个邮件槽中,其他进程可以从中读取这些消息。

特点

  • 单向通信:邮件槽通常用于单向通信,即数据从发送者流向接收者。
  • 不可靠性:邮件槽不保证消息的送达,适合对实时性要求不高的场景。
  • 简单易用:实现相对简单,适合快速开发。

服务端

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

int main() {
    const char* mailSlotName = "\\\\.\\mailslot\\MyMailSlot";
    HANDLE hMailSlot = CreateMailslotA(mailSlotName, 0, MAILSLOT_WAIT_FOREVER, NULL);

    if (hMailSlot == INVALID_HANDLE_VALUE) {
        std::cerr << "Failed to create mail slot. Error: " << GetLastError() << std::endl;
        return 1;
    }

    std::cout << "Mail Slot created. Waiting for messages..." << std::endl;

    char buffer[100];
    DWORD bytesRead;
    
    while (true) {
        BOOL result = ReadFile(hMailSlot, buffer, sizeof(buffer) - 1, &bytesRead, NULL);
        if (result && bytesRead > 0) {
            buffer[bytesRead] = '\0'; // Null-terminate the string
            std::cout << "Received message: " << buffer << std::endl;
        }
    }

    CloseHandle(hMailSlot);
    return 0;
}

客户端

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

int main() {
    const char* mailSlotName = "\\\\.\\mailslot\\MyMailSlot";
    HANDLE hMailSlot = CreateFileA(mailSlotName, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);

    if (hMailSlot == INVALID_HANDLE_VALUE) {
        std::cerr << "Failed to open mail slot. Error: " << GetLastError() << std::endl;
        return 1;
    }

    const char* message = "Hello from the client!";
    DWORD bytesWritten;
    
    BOOL result = WriteFile(hMailSlot, message, strlen(message) + 1, &bytesWritten, NULL);
    if (result) {
        std::cout << "Message sent: " << message << std::endl;
    } else {
        std::cerr << "Failed to send message. Error: " << GetLastError() << std::endl;
    }

    CloseHandle(hMailSlot);
    return 0;
}

匿名管道

匿名管道是一种用于父子进程之间进行通信的机制。它允许一个进程(通常是父进程)创建一个管道,并通过该管道与其子进程进行数据传输。一般用于父进程读取子进程的数据.(也可以双向通信,但只能是父子进程间通信)

特点

  • 单向通信:通常为单向,数据从写入端流向读取端。
  • 父子进程之间:主要用于父进程与其直接创建的子进程之间的通信。
  • 不具名:没有名称,进程通过句柄引用。
  • 速度快:由于在同一进程内的内存中操作,速度相对较快。
  • 安全性:因为不具名,外部进程无法访问,增加了安全性。

父进程代码

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

int main() {
    HANDLE hReadPipe, hWritePipe;
    SECURITY_ATTRIBUTES sa;
    sa.nLength = sizeof(SECURITY_ATTRIBUTES);
    sa.bInheritHandle = TRUE;
    sa.lpSecurityDescriptor = NULL;

    // 创建管道
    if (!CreatePipe(&hReadPipe, &hWritePipe, &sa, 0)) {
        std::cerr << "Failed to create pipe. Error: " << GetLastError() << std::endl;
        return 1;
    }

    // 创建子进程
    STARTUPINFO si = { sizeof(si) };
    PROCESS_INFORMATION pi;
    si.hStdOutput = hWritePipe;
    si.dwFlags |= STARTF_USESTDHANDLES;

    if (!CreateProcess(NULL, (LPSTR)"child_process.exe", NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi)) {
        std::cerr << "Failed to create child process. Error: " << GetLastError() << std::endl;
        return 1;
    }

    CloseHandle(hWritePipe); // 关闭写端,父进程只需读

    // 读取子进程输出
    char buffer[128];
    DWORD bytesRead;
    while (ReadFile(hReadPipe, buffer, sizeof(buffer) - 1, &bytesRead, NULL) && bytesRead > 0) {
        buffer[bytesRead] = '\0'; // Null-terminate the string
        std::cout << "Received from child: " << buffer << std::endl;
    }

    CloseHandle(hReadPipe);
    CloseHandle(pi.hProcess);
    CloseHandle(pi.hThread);
    return 0;
}

子进程代码

#include <iostream>

int main() {
    std::cout << "Hello from the child process!" << std::endl;
    return 0;
}

这段代码是父进程读取子进程的控制台输出.

命名管道

命名管道与匿名管道差不多,区别是命名管道可以在任意的两个进程间进行通信

服务端

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

int main() {
    const char* pipeName = "\\\\.\\pipe\\MyNamedPipe";
    HANDLE hPipe;

    // 创建命名管道
    hPipe = CreateNamedPipeA(
        pipeName,
        PIPE_ACCESS_DUPLEX, // 双向访问
        PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT,
        1, // 仅允许一个连接
        512, // 输出缓冲区大小
        512, // 输入缓冲区大小
        0, // 默认超时
        NULL
    );

    if (hPipe == INVALID_HANDLE_VALUE) {
        std::cerr << "Failed to create named pipe. Error: " << GetLastError() << std::endl;
        return 1;
    }

    std::cout << "Waiting for client to connect..." << std::endl;

    // 等待客户端连接
    BOOL connected = ConnectNamedPipe(hPipe, NULL);
    if (!connected) {
        std::cerr << "Failed to connect to pipe. Error: " << GetLastError() << std::endl;
        CloseHandle(hPipe);
        return 1;
    }

    // 读取客户端发送的数据
    char buffer[128];
    DWORD bytesRead;
    if (ReadFile(hPipe, buffer, sizeof(buffer) - 1, &bytesRead, NULL)) {
        buffer[bytesRead] = '\0'; // Null-terminate the string
        std::cout << "Received from client: " << buffer << std::endl;

        // 发送响应回客户端
        const char* response = "Hello from server!";
        DWORD bytesWritten;
        WriteFile(hPipe, response, strlen(response) + 1, &bytesWritten, NULL);
    } else {
        std::cerr << "Failed to read from pipe. Error: " << GetLastError() << std::endl;
    }

    CloseHandle(hPipe);
    return 0;
}

客户端

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

int main() {
    const char* pipeName = "\\\\.\\pipe\\MyNamedPipe";
    HANDLE hPipe = CreateFileA(
        pipeName,
        GENERIC_READ | GENERIC_WRITE, // 读写访问
        0, // 不共享
        NULL,
        OPEN_EXISTING, // 打开现有管道
        0,
        NULL
    );

    if (hPipe == INVALID_HANDLE_VALUE) {
        std::cerr << "Failed to open named pipe. Error: " << GetLastError() << std::endl;
        return 1;
    }

    // 发送数据到服务器
    const char* message = "Hello from client!";
    DWORD bytesWritten;
    WriteFile(hPipe, message, strlen(message) + 1, &bytesWritten, NULL);

    // 读取服务器的响应
    char buffer[128];
    DWORD bytesRead;
    if (ReadFile(hPipe, buffer, sizeof(buffer) - 1, &bytesRead, NULL)) {
        buffer[bytesRead] = '\0'; // Null-terminate the string
        std::cout << "Received from server: " << buffer << std::endl;
    } else {
        std::cerr << "Failed to read from pipe. Error: " << GetLastError() << std::endl;
    }

    CloseHandle(hPipe);
    return 0;
}

消息队列

服务端

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

int main() {
    HANDLE hQueue = CreateMsgQueue(NULL, NULL);
    if (hQueue == NULL) {
        std::cerr << "Failed to create message queue. Error: " << GetLastError() << std::endl;
        return 1;
    }

    while (true) {
        char buffer[256];
        DWORD bytesRead;
        if (ReadMsgQueue(hQueue, buffer, sizeof(buffer), &bytesRead, NULL, 0)) {
            buffer[bytesRead] = '\0';
            std::cout << "Received: " << buffer << std::endl;
        }
    }

    CloseMsgQueue(hQueue);
    return 0;
}

客户端

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

int main() {
    HANDLE hQueue = OpenMsgQueue(NULL, NULL);
    if (hQueue == NULL) {
        std::cerr << "Failed to open message queue. Error: " << GetLastError() << std::endl;
        return 1;
    }

    const char* message = "Hello from client!";
    DWORD bytesWritten;
    WriteMsgQueue(hQueue, message, strlen(message) + 1, &bytesWritten, 0);

    CloseMsgQueue(hQueue);
    return 0;
}

WM_COPYDATA

服务端

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

int main() {
    HWND hWnd = FindWindow(NULL, "TargetWindowName"); // 替换为目标窗口的名称
    if (hWnd == NULL) {
        std::cerr << "Could not find target window. Error: " << GetLastError() << std::endl;
        return 1;
    }

    COPYDATASTRUCT cds;
    cds.dwData = 1; // 类型标识
    cds.cbData = strlen("Hello from WM_COPYDATA") + 1;
    cds.lpData = (PVOID)"Hello from WM_COPYDATA";

    SendMessage(hWnd, WM_COPYDATA, (WPARAM)NULL, (LPARAM)(LPVOID)&cds);
    return 0;
}

客户端

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

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
    if (uMsg == WM_COPYDATA) {
        PCOPYDATASTRUCT pcds = (PCOPYDATASTRUCT)lParam;
        std::cout << "Received: " << (char*)pcds->lpData << std::endl;
    }
    return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

int main() {
    // 注册窗口类和创建窗口...
    // 在消息循环中调用 WindowProc

    return 0;
}

共享内存(文件映射)

共享内存与文件映射实现基本一致.

服务端

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

int main() {
    const char* sharedMemoryName = "MySharedMemory";
    HANDLE hMapFile = CreateFileMappingA(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, 256, sharedMemoryName);

    if (hMapFile == NULL) {
        std::cerr << "Could not create file mapping object. Error: " << GetLastError() << std::endl;
        return 1;
    }

    char* pBuf = (char*)MapViewOfFile(hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, 256);
    if (pBuf == NULL) {
        std::cerr << "Could not map view of file. Error: " << GetLastError() << std::endl;
        CloseHandle(hMapFile);
        return 1;
    }

    strcpy(pBuf, "Hello from server!");

    // 等待客户端读取
    std::cout << "Press Enter to exit..." << std::endl;
    std::cin.get();

    UnmapViewOfFile(pBuf);
    CloseHandle(hMapFile);
    return 0;
}

客户端

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

int main() {
    const char* sharedMemoryName = "MySharedMemory";
    HANDLE hMapFile = OpenFileMappingA(FILE_MAP_READ, FALSE, sharedMemoryName);

    if (hMapFile == NULL) {
        std::cerr << "Could not open file mapping object. Error: " << GetLastError() << std::endl;
        return 1;
    }

    char* pBuf = (char*)MapViewOfFile(hMapFile, FILE_MAP_READ, 0, 0, 256);
    if (pBuf == NULL) {
        std::cerr << "Could not map view of file. Error: " << GetLastError() << std::endl;
        CloseHandle(hMapFile);
        return 1;
    }

    std::cout << "Received: " << pBuf << std::endl;

    UnmapViewOfFile(pBuf);
    CloseHandle(hMapFile);
    return 0;
}

剪切板

服务端

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

int main() {
    if (!OpenClipboard(NULL)) {
        std::cerr << "Could not open clipboard. Error: " << GetLastError() << std::endl;
        return 1;
    }

    const char* message = "Hello from clipboard!";
    HGLOBAL hGlobal = GlobalAlloc(GMEM_MOVEABLE, strlen(message) + 1);
    memcpy(GlobalLock(hGlobal), message, strlen(message) + 1);
    GlobalUnlock(hGlobal);

    SetClipboardData(CF_TEXT, hGlobal);
    CloseClipboard();
    return 0;
}

客户端

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

int main() {
    if (!OpenClipboard(NULL)) {
        std::cerr << "Could not open clipboard. Error: " << GetLastError() << std::endl;
        return 1;
    }

    HGLOBAL hGlobal = GetClipboardData(CF_TEXT);
    if (hGlobal == NULL) {
        std::cerr << "Could not get clipboard data. Error: " << GetLastError() << std::endl;
        CloseClipboard();
        return 1;
    }

    char* message = static_cast<char*>(GlobalLock(hGlobal));
    if (message != NULL) {
        std::cout << "Received from clipboard: " << message << std::endl;
        GlobalUnlock(hGlobal);
    }

    CloseClipboard();
    return 0;
}

RPC(Sockets/http)

基于网络协议来通信,可以使用socket,http协议进行网络请求.来进行进程通信

服务端

#include <winsock2.h>
#include <ws2tcpip.h>
#include <iostream>

#pragma comment(lib, "ws2_32.lib") // 链接WS2_32.lib

int main() {
    WSADATA wsaData;
    SOCKET serverSocket, clientSocket;
    sockaddr_in serverAddr, clientAddr;
    int clientAddrSize = sizeof(clientAddr);
    
    // 初始化 Winsock
    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
        std::cerr << "WSAStartup failed. Error: " << WSAGetLastError() << std::endl;
        return 1;
    }

    // 创建套接字
    serverSocket = socket(AF_INET, SOCK_STREAM, 0);
    if (serverSocket == INVALID_SOCKET) {
        std::cerr << "Socket creation failed. Error: " << WSAGetLastError() << std::endl;
        WSACleanup();
        return 1;
    }

    // 设置服务器地址信息
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_addr.s_addr = INADDR_ANY; // 监听所有接口
    serverAddr.sin_port = htons(54000); // 端口号

    // 绑定套接字
    if (bind(serverSocket, (sockaddr*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR) {
        std::cerr << "Bind failed. Error: " << WSAGetLastError() << std::endl;
        closesocket(serverSocket);
        WSACleanup();
        return 1;
    }

    // 开始监听
    if (listen(serverSocket, SOMAXCONN) == SOCKET_ERROR) {
        std::cerr << "Listen failed. Error: " << WSAGetLastError() << std::endl;
        closesocket(serverSocket);
        WSACleanup();
        return 1;
    }

    std::cout << "Waiting for client connections..." << std::endl;

    // 接受客户端连接
    clientSocket = accept(serverSocket, (sockaddr*)&clientAddr, &clientAddrSize);
    if (clientSocket == INVALID_SOCKET) {
        std::cerr << "Accept failed. Error: " << WSAGetLastError() << std::endl;
        closesocket(serverSocket);
        WSACleanup();
        return 1;
    }

    // 接收消息
    char buffer[256];
    int bytesReceived = recv(clientSocket, buffer, sizeof(buffer), 0);
    if (bytesReceived > 0) {
        buffer[bytesReceived] = '\0'; // Null-terminate the string
        std::cout << "Received from client: " << buffer << std::endl;
    }

    // 关闭套接字
    closesocket(clientSocket);
    closesocket(serverSocket);
    WSACleanup();
    return 0;
}

客户端

#include <winsock2.h>
#include <ws2tcpip.h>
#include <iostream>

#pragma comment(lib, "ws2_32.lib") // 链接WS2_32.lib

int main() {
    WSADATA wsaData;
    SOCKET clientSocket;
    sockaddr_in serverAddr;

    // 初始化 Winsock
    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
        std::cerr << "WSAStartup failed. Error: " << WSAGetLastError() << std::endl;
        return 1;
    }

    // 创建套接字
    clientSocket = socket(AF_INET, SOCK_STREAM, 0);
    if (clientSocket == INVALID_SOCKET) {
        std::cerr << "Socket creation failed. Error: " << WSAGetLastError() << std::endl;
        WSACleanup();
        return 1;
    }

    // 设置服务器地址信息
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_port = htons(54000); // 端口号
    inet_pton(AF_INET, "127.0.0.1", &serverAddr.sin_addr); // 服务器地址

    // 连接到服务器
    if (connect(clientSocket, (sockaddr*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR) {
        std::cerr << "Connection failed. Error: " << WSAGetLastError() << std::endl;
        closesocket(clientSocket);
        WSACleanup();
        return 1;
    }

    // 发送消息
    const char* message = "Hello from client!";
    send(clientSocket, message, strlen(message), 0);

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

COM (Component Object Model)

使用 COM(组件对象模型)进行进程间通信可以让不同的应用程序和组件相互交互。下面是一个简单的 COM 示例,展示了如何创建一个 COM 服务器和一个 COM 客户端。

服务端

#include <windows.h>
#include <iostream>
#include <comdef.h>
#include <atlbase.h>
#include <atlcomcli.h>

class MyComServer : public IDispatch {
public:
    MyComServer() : refCount(1) {}
    
    // IUnknown
    STDMETHODIMP QueryInterface(REFIID riid, void** ppv) {
        if (riid == IID_IUnknown || riid == IID_IDispatch) {
            *ppv = static_cast<IDispatch*>(this);
            AddRef();
            return S_OK;
        }
        return E_NOINTERFACE;
    }

    STDMETHODIMP_(ULONG) AddRef() {
        return InterlockedIncrement(&refCount);
    }

    STDMETHODIMP_(ULONG) Release() {
        ULONG count = InterlockedDecrement(&refCount);
        if (count == 0) delete this;
        return count;
    }

    // IDispatch
    STDMETHODIMP GetTypeInfoCount(UINT* pctinfo) {
        *pctinfo = 0;
        return S_OK;
    }

    STDMETHODIMP GetTypeInfo(UINT iTInfo, LCID lcid, ITypeInfo** ppTInfo) {
        return E_NOTIMPL;
    }

    STDMETHODIMP GetIDsOfNames(REFIID riid, LPOLESTR* rgszNames, UINT cNames, LCID lcid, DISPID* rgDispId) {
        return E_NOTIMPL;
    }

    STDMETHODIMP Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS* pDispParams, VARIANT* pVarResult, EXCEPINFO* pExcepInfo, UINT* puArgErr) {
        if (dispIdMember == 1) { // Custom method ID
            std::cout << "Hello from COM Server!" << std::endl;
            return S_OK;
        }
        return DISP_E_MEMBERNOTFOUND;
    }

private:
    ULONG refCount;
};

class MyComModule : public ATL::CComModule {
public:
    HRESULT Init() {
        return CoInitialize(NULL);
    }
    
    void Term() {
        CoUninitialize();
    }
};

MyComModule _Module;

extern "C" HRESULT __declspec(dllexport) DllGetClassObject(REFCLSID clsid, REFIID iid, void** ppv) {
    if (clsid == CLSID_NULL) {
        *ppv = new MyComServer();
        return S_OK;
    }
    return CLASS_E_CLASSNOTAVAILABLE;
}

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved) {
    if (fdwReason == DLL_PROCESS_ATTACH) {
        _Module.Init();
    } else if (fdwReason == DLL_PROCESS_DETACH) {
        _Module.Term();
    }
    return TRUE;
}

客户端

#include <windows.h>
#include <iostream>
#include <comdef.h>

int main() {
    CoInitialize(NULL);
    
    IDispatch* pDispatch = NULL;
    CLSID clsid;
    HRESULT hr = CLSIDFromProgID(L"YourProgID", &clsid); // Replace with your actual ProgID

    if (SUCCEEDED(hr)) {
        hr = CoCreateInstance(clsid, NULL, CLSCTX_INPROC_SERVER, IID_IDispatch, (void**)&pDispatch);
        if (SUCCEEDED(hr)) {
            DISPPARAMS params = { NULL, NULL, 0, 0 };
            VARIANT result;
            VariantInit(&result);
            hr = pDispatch->Invoke(1, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD, &params, &result, NULL, NULL);
            if (SUCCEEDED(hr)) {
                std::cout << "COM method called successfully." << std::endl;
            }
            pDispatch->Release();
        }
    }

    CoUninitialize();
    return 0;
}

运行步骤

  1. 编译 COM 服务器

    • 将服务器代码编译为 DLL 文件(例如 MyComServer.dll)。
  2. 注册 COM 服务器

    • 使用 regsvr32 注册 DLL: regsvr32 MyComServer.dll
  3. 编译 COM 客户端

    • 将客户端代码编译为可执行文件(例如 client.exe)。
  4. 运行客户端

    • 运行 client.exe,它将调用 COM 服务器的方法。

事件 (Events)

使用事件(Events)进行进程间通信是一种常见的方式,特别是在 Windows 中。事件可以用来通知一个或多个进程某个特定的事件发生。下面是一个使用 Windows 事件对象的示例,展示了如何在一个进程中设置事件,并在另一个进程中等待该事件的触发。

生产者(事件发送方)

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

int main() {
    // 创建一个命名事件
    HANDLE hEvent = CreateEventA(NULL, TRUE, FALSE, "MyEvent");

    if (hEvent == NULL) {
        std::cerr << "CreateEvent failed. Error: " << GetLastError() << std::endl;
        return 1;
    }

    std::cout << "Press Enter to signal the event..." << std::endl;
    std::cin.get(); // 等待用户输入

    // 触发事件
    SetEvent(hEvent);
    std::cout << "Event signaled!" << std::endl;

    CloseHandle(hEvent);
    return 0;
}

消费者

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

int main() {
    // 打开命名事件
    HANDLE hEvent = OpenEvent(EVENT_MODIFY_STATE | SYNCHRONIZE, FALSE, "MyEvent");

    if (hEvent == NULL) {
        std::cerr << "OpenEvent failed. Error: " << GetLastError() << std::endl;
        return 1;
    }

    std::cout << "Waiting for the event to be signaled..." << std::endl;

    // 等待事件
    WaitForSingleObject(hEvent, INFINITE);
    std::cout << "Event was signaled!" << std::endl;

    CloseHandle(hEvent);
    return 0;
}

信号量 (Semaphores)

使用信号量(Semaphore)进行进程间通信可以有效地控制对共享资源的访问,确保在多进程环境中对资源的安全访问。以下是一个使用 Windows 信号量的示例,展示了如何在一个进程中创建信号量,并在另一个进程中等待和释放信号量。

生产者

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

int main() {
    // 创建一个命名信号量
    HANDLE hSemaphore = CreateSemaphoreA(NULL, 1, 1, "MySemaphore");

    if (hSemaphore == NULL) {
        std::cerr << "CreateSemaphore failed. Error: " << GetLastError() << std::endl;
        return 1;
    }

    std::cout << "Press Enter to signal the semaphore..." << std::endl;
    std::cin.get(); // 等待用户输入

    // 释放信号量
    ReleaseSemaphore(hSemaphore, 1, NULL);
    std::cout << "Semaphore signaled!" << std::endl;

    CloseHandle(hSemaphore);
    return 0;
}

消费者

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

int main() {
    // 打开命名信号量
    HANDLE hSemaphore = OpenSemaphore(SYNCHRONIZE, FALSE, "MySemaphore");

    if (hSemaphore == NULL) {
        std::cerr << "OpenSemaphore failed. Error: " << GetLastError() << std::endl;
        return 1;
    }

    std::cout << "Waiting for the semaphore to be signaled..." << std::endl;

    // 等待信号量
    WaitForSingleObject(hSemaphore, INFINITE);
    std::cout << "Semaphore was signaled!" << std::endl;

    CloseHandle(hSemaphore);
    return 0;
}