进程间通信

  1. 通过 窗口的消息机制发送信息
  2. 通过文件交流
  3. 通过tcp/udp 交流
  4. 创建一个空进程专门用来存储数据(不安全)

附加: 内存申请的状态

    MEM_RESERVE //申请空间 ,暂不使用
    MEM_RESET   //表示不再使用空间,可以回收
    MEM_RESET_UNDO //表示返回了 想撤销 MEM_RESET 操作,返回0的话表示内存已被更改,撤销失败
    MEM_COMMIT  //表示 直接 申请并且划分一片空间

方案一: 在异进程中 申请内存空间

  1. 假设 AB 发送信息
  2. AB 进程中申请一片空间,并且在里面写入数据
  3. B 直接就可以 获取 A 写入的信息
  4. virtualallocex 负责在异进程中申请空间
    //我们先 在 两边 都 定义一个窗口编号
    #define WM_MYMSG WM_USER + 1

发送方cpp

    //进程标示符
    DWORD dwPID = 0;
    
    //1:获取接收信心的窗口的句柄
    HWND hRecv = FindWindowA(NULL, "Recver");
    
    //2:通过窗口句柄 获取 进程标示符
    GetWindowThreadProcessId(hRecv, &dwPID);
    
    //3:根据进程标示符 打开进程句柄
    HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS,
        TRUE,
        dwPID);
    
    //4:根据进程句柄 申请内存空间
    LPVOID addr =VirtualAllocEx(hProcess,
        (LPVOID)NULL,
        0x256,
        MEM_COMMIT,
        PAGE_EXECUTE_READWRITE);
    
    //5:准备 要传递的信息
    char* pBuf = new char[256];
    strcpy_s((char*)addr, 256, "Hello World!");
    
    //保存 写入结果的变量
    DWORD dwWritedBytes = 0;
    
    //6: 写入信息
    WriteProcessMemory(hProcess,
        addr,
        "Hello World!",
        strlen("Hello World!") + 1,
        &dwWritedBytes);
    
    /*
    7: 信息写入完成后 向接收者窗口 发送 信息消息
    WPARAM 是我们申请到的地址
    LPARAM 是内存片的大小
    */
    SendMessage(hRecv, WM_MYMSG, (WPARAM)addr, strlen("Hello World!") + 1);
    
    //8: 接收方处理完成后在释放消息
    VirtualFreeEx(hProcess, addr, 0x256, MEM_RELEASE);

接收方cpp

    case WM_MYMSG://捕获 我们自定义的消息编号
    {
        char* pBuf = (char*)wParam;
        MessageBoxA(NULL, pBuf, NULL, NULL);
    }

方案二: WM_COPYDATA

  1. win已经封装了一个消息传递机制
  2. 发送 WM_COPYDATA消息即可
  3. 操作系统会首先将发送的数据拷贝到高2GB共享区,然后再拷贝到接收方的用户空间中,所以会有两次拷贝,因此在数据量不大的时候,可以使用此方法。
  4. 操作步骤:

    1. SendMessage时系统把我们的消息 复制到系统内存区间
    2. WM_COPYDATA消息处理时 把传递的消息 复制给 lParam
    3. 我们 强转 lParam 就获取到了 数据结构体

发送方 CPP

    //1: 获取 接收者的窗口句柄
    HWND hRecv = FindWindowA(NULL, "Recver");
    
    //2: 准备 专用的 信息传送结构体
    COPYDATASTRUCT cds;
    
    //3: 填充结构体
    cds.dwData = 1;//这个是自定义标志,建议用来标志信息类型
    cds.cbData = strlen("Hello World!") + 1;//传递信息长度
    cds.lpData = "Hello World!";//信息的实体
    
    //4: 发送 窗口消息/传递消息 
    SendMessage(hRecv, WM_COPYDATA, (WPARAM)NULL, (LPARAM)&cds);

接收方 CPP

    case WM_COPYDATA:// 捕获 消息
    {
        //获取消息结构体
        PCOPYDATASTRUCT pCds = (PCOPYDATASTRUCT)lParam;
            
        //提取消息
        MessageBoxA(NULL, (LPCSTR)pCds->lpData, NULL, NULL);
    }
    break;

方案三: DLL 共享节

  1. 创建一个节
  2. 在 代码中件 强制 赋予 共享属性

    1. link
    2. def 文件
    3. 项目->属性->命令行
  3. 别忘了在变量前添加 __declspec(dllexport)
  4. 使用方直接 隐式/静态 加载 饥渴使用共享的数据节了
  5. 使用时 署名 extern __declspec(dllimport)

定义数据节

dll 的cpp中我们定义 是共享数据节

    //数据节开始,我们定义这个数据节的名称为 MY_DATA
    #pragma data_seg("MY_DATA")
    
    /*
    这里写我们需要共享的数据
    一定要先初始化
    */
    int     g_nData  = 2;
    char  g_szBuff[MAXBYTE] = "Hello";
    
    //结束 数据节
    #pragma data_seg() 

赋予共享属性

  1. 上一步我们已经点一好了我们需要共享的 数据节点
  2. 但是这些 数据结 默认不是共享的,不同程序中仍然有不同数据
  3. 我们要强制赋予她们 共享属性,一共三种方法

方案1 link 说明

    //我们 直接给 名为 MY_DATA 的数据节 设置为 rws权限
    #pragma comment(linker,"/section:MY_DATA,rws")

方案2 编译器设定

我们链接的时候添加以下参数

    /SECTION:MY_DATA,rws

方案3 def文件中定义

    SETCTIONS
    MY_DATA READ WRITE SHARED

读取共享节点的数据

  1. 我们要 隐式/静态 加载 lib,DLL
  2. 我们 通过 extern __declspec(dllimport) 声明使用 外部变量
  3. 程序中正常使用即可
    //隐式/静态 加载 lib,DLL
    #pragma comment(lib,"创建共享DLL")
    //声明 我们使用的是 dll中的变量
    extern __declspec(dllimport)   char    g_szBuff[12];
    
    int main()
    {
        std::cout << g_szBuff<<std::endl;
    }

方案三: 文件映射

  1. 官网连接
  2. 内存映射文件允许在进程中预定一块地址空间区域,将磁盘上的文件映射到地址空间中,对其访问,好像整个文件已经被载入到内存一样
  3. 文件<->内存 双向影响,任何一处的更改会影响到另一边
  4. 文件映射 是一个对象 分为 有名 ,匿名
  5. 有名称 的文件映射 可以跨进程使用
  6. 用来共享数据很方便,但是不适合通信

步骤

  1. 创建文件内核对象&文件映射内核对象
  2. 将文件映射对象的部分或全部映射到进程的地址空间
  3. 操作文件
  4. 从进程的地址空间撤销对文件数据的映射
  5. 关闭文件对象&文件映射对象

相关api

    CreateFileMapping   创建内存映射文件对象(如果这里给 mapping 起了个名字那么 其他进程可以通过名字直接使用这个mapping)
    OpenFileMapping     根据mapping名称打开映射
    MapViewOfFile       将内存文件映射对象部分或全部映射到进程地址空间
    FlushViewOfFile     强制系统把部分或全部修改过的数据写回到磁盘中
    UnmapViewOfFile     取消映射

实例

创建映_绑定文件

  1. 标准的用法
  2. 我们把一个具体的文件映射到内存中
  3. 内存<->文件相互影响
  4. 文件映射到内存中的各大小是可以设置的,一般设置为文件大小
    void maping_file() {
    
    //1:打开文件
    HANDLE hFile = CreateFileA("ReadMe.txt",
                GENERIC_READ | GENERIC_WRITE,
                FILE_SHARE_READ | FILE_SHARE_WRITE,
                NULL,
                OPEN_EXISTING,
                FILE_ATTRIBUTE_NORMAL,
                NULL);

    if (hFile == INVALID_HANDLE_VALUE) {
        return;
    }
    //2:将文件映射到 内存中
    HANDLE hFileMap = CreateFileMapping(hFile,
        NULL,
        PAGE_READWRITE,
        0,
        0,
        NULL);

    if (hFileMap == NULL)
    {
        return;
    }
    //3:打开 映射
    LPVOID addr =MapViewOfFile(hFileMap,
        FILE_MAP_ALL_ACCESS,
        0,
        0,
        0);

    UnmapViewOfFile(addr);
}

创建映_绑定内存

  1. 有时候我们不需要整个文件内存映射到内存中
  2. 我们需要的仅仅是 多个程序间共享 一块内存
  3. 我们可以创建一个 没有实体文件的共享内存
    void maping_memory()
    {
    //1: 我们直接创建一个没有文件 实体的映射
        HANDLE hFileMap = CreateFileMappingA(INVALID_HANDLE_VALUE,
        NULL,
        PAGE_READWRITE,
        0,
        0x1000,
        "CR31_file");

    if (hFileMap == NULL)
    {
        return;
    }
    //2: 打开映射
    LPVOID addr =MapViewOfFile(hFileMap,
        FILE_MAP_ALL_ACCESS,
        0,
        0,
        0);

    if (addr != NULL) {
        //3: 打开的映射 实际上就是一个指针
        memcpy(addr, "Hello", sizeof("Hello"));
        UnmapViewOfFile(addr);
    }
}

通过名称 打开映射

  1. 我们只需要知道 映射的名称就可以在任意程序中打开这个映射
    void open_mapping()
    {
    //1: 通过名称搜索映射
        HANDLE hFileMap = OpenFileMappingA(FILE_MAP_ALL_ACCESS,
            FALSE,
            "CR31_file");

        if (hFileMap == NULL)
        {
            return;
        }
        
        //2: 打开映射
        LPVOID addr = MapViewOfFile(hFileMap,
            FILE_MAP_ALL_ACCESS,
            0,
            0,
            0);
    
        if (addr != NULL) {
        
            
            UnmapViewOfFile(addr);
        }

方案四: 管道

  1. 管道分为 匿名管道,命名管道
  2. 匿名管道 可以用来跨线程
  3. 命名管道 可以跨进程
  4. CreateProcess 时 可以绑定到一个 管道
  5. 如果管道没有输出 ReadFile()时会等待,在当前位置等待
  6. ReadFile() 不能保证 会读出指定数量的 已存在的 数据,建议根据 返回的 数据读取量 计算后进行下一次读取

相关 api

    CreatePipe()    创建一个管道, 获取 读取信息的句柄 以及 发送信息的句柄
    WriteFile()     通过 发送信息的句柄 向管道写入信息,这里注意下 同步/异步
    ReadFile()      通过 读取信息的句柄 读取管道中的信息,读取后会删除读取到的信息,读取不到的话会等待
    PeekNamedPipe() 效果和 ReadFile 一样,只是读取后没有删除 读取到的信息,可以通过它检测 信息有多大

实例

    int pipe() {
        HANDLE hReadPipe;
        HANDLE hWritePipe;

        //创建一个匿名管道
        BOOL bRet = CreatePipe(&hReadPipe,
            &hWritePipe,
            NULL,
            0);

        DWORD dwWritedBytes = 0;
        DWORD dwReadedBytes = 0;

        char szWriteBuf[256] = "Hello World!";
        char szReadBuf[256] = { 0 };
        
        //写入数据
        bRet = WriteFile(hWritePipe,
            szWriteBuf,
            strlen(szWriteBuf) + 12,
            &dwWritedBytes,
            NULL);

        if (!bRet) {
            return 0;
        }
        //读取信息
        bRet = ReadFile(hReadPipe,
            szReadBuf,
            256,
            &dwReadedBytes,
            NULL);
        return 0;
    }
Last modification:November 19, 2020
如果觉得我的文章对你有用,请随意赞赏