目标
我想要知道进程间通信的方法。我在《进程间通信(IPC)介绍 - ZH奶酪 - 博客园》中看到了各种进程间通信的方法,其中它提到共享内存是最快的方式,于是我想实践一下它。
本篇的目标实现这样一个测试:在一个进程中向一块“共享内存”中写数据,在另一个进程中从这块“共享内存”中读数据。
在Windows上,最权威的文档与代码范例是:《Creating Named Shared Memory - Win32 apps | Microsoft Docs》
另外,《Windows共享内存示例 - 可笑痴狂 - 博客园》这篇也对我帮助很大。
使用函数介绍
用到的函数需要
CreateFileMapping
首先,要使用 CreateFileMapping创建一个file mapping object。
1 2 3 4 5 6 7 8 | HANDLE CreateFileMappingA( HANDLE hFile, LPSECURITY_ATTRIBUTES lpFileMappingAttributes, DWORD flProtect, DWORD dwMaximumSizeHigh, DWORD dwMaximumSizeLow, LPCSTR lpName ); |
其中:
hFile 参数将设置为INVALID_HANDLE_VALUE
If
hFile isINVALID_HANDLE_VALUE , the calling process must also specify a size for the file mapping object in thedwMaximumSizeHigh anddwMaximumSizeLow parameters. In this scenario,CreateFileMapping creates a file mapping object of a specified size that is backed by the system paging file instead of by a file in the file system.
flProtect 代表保护权限,例如PAGE_READWRITE 代表有“读”和“写”的权限。lpName 将是“文件”的名字。当然,在我的两个测试进程里这个名字应该一致。
OpenFileMapping
而在读内存的进程中,将使用 OpenFileMapping 打开一个file mapping object。
1 2 3 4 5 | HANDLE OpenFileMappingA( DWORD dwDesiredAccess, BOOL bInheritHandle, LPCSTR lpName ); |
其中:
dwDesiredAccess 代表权限,例如FILE_MAP_ALL_ACCESS 表示所有的权限包括读写。bInheritHandle :
If this parameter is TRUE, a process created by the CreateProcess function can inherit the handle; otherwise, the handle cannot be inherited.
lpName 将是“文件”的名字。当然,在我的两个测试进程里这个名字应该一致。
MapViewOfFile
使用 MapViewOfFile 可以将
1 2 3 4 5 6 7 | LPVOID MapViewOfFile( HANDLE hFileMappingObject, DWORD dwDesiredAccess, DWORD dwFileOffsetHigh, DWORD dwFileOffsetLow, SIZE_T dwNumberOfBytesToMap ); |
hFileMappingObject :填写CreateFileMapping 或OpenFileMapping 返回的file mapping object的Handle。dwDesiredAccess :代表权限,例如FILE_MAP_ALL_ACCESS 代表有读写权限。dwNumberOfBytesToMap :映射多少字节。(If this parameter is 0 (zero), the mapping extends from the specified offset to the end of the file mapping.)
代码实践
写入共享内存的数据的进程:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | #include <windows.h> #include <iostream> //测试用的数据结构 struct MyTestData {<!-- --> int TestInt; //测试的整数数据 char TestStr[5]; //测试的字符串数据 }; int main() {<!-- --> //FMO(file mapping object)的名字(应该在两个测试进程中保持一致) const std::wstring FMO_Name(L"TestFMO"); //创建一个FMO HANDLE hMap = CreateFileMapping( INVALID_HANDLE_VALUE, // use paging file NULL, // default security PAGE_READWRITE, // 读写权限 0, // maximum object size (high-order DWORD) sizeof(MyTestData), // maximum object size (low-order DWORD) FMO_Name.c_str()); // FMO 的名字 //映射到缓冲中 void* pBuffer = MapViewOfFile(hMap, FILE_MAP_ALL_ACCESS, 0, 0, 0); //将指针转换为 MyTestData 类型 MyTestData* shared_data = (MyTestData*)pBuffer; //在循环中不断改变数据 while (1) {<!-- --> //写一个随机的数据 shared_data->TestInt = rand() % 10; for (int i = 0; i < 4; i++) shared_data->TestStr[i] = 'a' + rand() % 26; shared_data->TestStr[4] = '\0'; //打印信息: std::cout << "写入共享内存:" << shared_data->TestInt <<' '<< shared_data->TestStr <<std::endl; //停留1秒 Sleep(1000); } //解除映射 UnmapViewOfFile(pBuffer); //关闭FMO的Handle CloseHandle(hMap); return 0; } |
读取共享内存的数据的进程:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | #include <windows.h> #include <iostream> //测试用的数据结构 struct MyTestData {<!-- --> int TestInt; //测试的整数数据 char TestStr[5]; //测试的字符串数据 }; int main() {<!-- --> //FMO(file mapping object)的名字(应该在两个测试进程中保持一致) const std::wstring FMO_Name(L"TestFMO"); //打开一个FMO HANDLE hMap = OpenFileMapping( FILE_MAP_ALL_ACCESS, // 读/写权限 FALSE, // do not inherit the name FMO_Name.c_str()); // FMO 的名字 //映射到缓冲中 void* pBuffer = MapViewOfFile(hMap, FILE_MAP_ALL_ACCESS, 0, 0, 0); //将指针转换为 MyTestData 类型 MyTestData* shared_data = (MyTestData*)pBuffer; //在循环中不断读取数据 while (1) {<!-- --> //打印信息: std::cout << "读取共享内存:" << shared_data->TestInt << ' ' << shared_data->TestStr << std::endl; //停留0.1秒 Sleep(100); } //解除映射 UnmapViewOfFile(pBuffer); //关闭FMO的Handle CloseHandle(hMap); return 0; } |
效果:
*另一个测试
我在想,如果共享内存中有指针,其指向了进程A中的一个数据,那么在进程B中,还能通过上面这种方式访问吗?
可惜测试发现是不能的。
在另一个进程中读取,发现可能会有多种行为:
或
还有一次没中断,不过数据读到是“烫烫烫。。。”。之后试了几次没能再复现