目标
我想要知道进程间通信的方法。我在《进程间通信(IPC)介绍 - ZH奶酪 - 博客园》中看到了各种进程间通信的方法,其中它提到共享内存是最快的方式,于是我想实践一下它。
本篇的目标实现这样一个测试:在一个进程中向一块“共享内存”中写数据,在另一个进程中从这块“共享内存”中读数据。
在Windows上,最权威的文档与代码范例是:《Creating Named Shared Memory - Win32 apps | Microsoft Docs》
另外,《Windows共享内存示例 - 可笑痴狂 - 博客园》这篇也对我帮助很大。
使用函数介绍
用到的函数需要#include <windows.h>
。下面将讨论“共享内存”相关的函数以及需要注意的参数:
CreateFileMapping
首先,要使用 CreateFileMapping创建一个file mapping object。
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。
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 可以将CreateFileMapping
或OpenFileMapping
返回的Handle映射到一块缓存中。
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.)
代码实践
写入共享内存的数据的进程:
#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;
}
读取共享内存的数据的进程:
#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中,还能通过上面这种方式访问吗?
可惜测试发现是不能的。
在另一个进程中读取,发现可能会有多种行为:
或
还有一次没中断,不过数据读到是“烫烫烫。。。”。之后试了几次没能再复现