原理介绍
在Windows平台中,常见的进程间通信机制包括管道、socket、WM_COPYDATA、邮槽等,这些在同一台机器上实现共享数据的最底层机制就是内存映射文件,如果要求低开销和高性能,内存映射文件无疑是最佳选择。
创建一个内存映射文件的步骤如下:
(1)创建一个文件映射内核对象(file-mapping kernel object)并指定系统文件大小以及访问方式。
(2)把文件映射对象的部分或者全部映射到进程的地址空间,被映射到进程地址空间的部分称为视图。
(3)操作内存映射文件,如同操作内存一样,可以读写。
(4)从进程的地址空间中卸载被映射的文件映射内核对象。
(5)关闭文件内核对象。
需要说明的是,被映射的对象可以是磁盘文件也可以是页交换文件,为了实现进程间通信,需要保证每个进程操作的是同一个对象,操作系统保证同一个文件映射对象的多个视图保持一致。
以下是内存映射文件的示意图:
文件映射内核对象
创建文件映射对象需要使用CreateFileMapping接口,其API如下:
HANDLE CreateFileMappingA(
HANDLE hFile,
LPSECURITY_ATTRIBUTES lpFileMappingAttributes,
DWORD flProtect,
DWORD dwMaximumSizeHigh,
DWORD dwMaximumSizeLow,
LPCSTR lpName
);
其中hFile可以是以下两种:
- CreateFile打开的磁盘文件,并传入句柄,数据可以保存在磁盘中
- 直接传入INVALID_HANDLE_VALUE,页交换文件,下电数据丢失
来自磁盘文件用法:
HANDLE hFile = CreateFile(_T("data.txt"),
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (hFile == INVALID_HANDLE_VALUE)
{
return 4;
}
进程间通信实例
进程一主要代码:
int CreateFileMapView()
{
TCHAR szName[]=TEXT("Global\\MyFileMappingObject");
//创建文件映射内核对象
HANDLE hMapHandle = CreateFileMapping(
INVALID_HANDLE_VALUE, // use paging file
NULL, // default security
PAGE_READWRITE, // read/write access
0, // maximum object size (high-order DWORD)
1024, // maximum object size (low-order DWORD)
szName); // name of mapping object
if (NULL == hMapHandle)
{
printf("create file map error...\n");
return -1;
}
//映射到进程空间,范围从0-文件尾巴
char* pBuf = (char*)MapViewOfFile(hMapHandle,FILE_MAP_ALL_ACCESS,0,0,0);
if (pBuf == NULL)
{
CloseHandle(hMapHandle);
return -1;
}
//映射到进程空间的起始地址
printf("pBufVal = %x\n",pBuf);
HANDLE hNotifyHandle = ::CreateEvent(NULL,TRUE,FALSE,_T("file_map_handle"));
//操作内存映射文件,可以像操作内存一样
strncpy(pBuf,"12345",strlen("12345") + 1);
//等待另一个进程读取数据
WaitForSingleObject(hNotifyHandle, INFINITE);
//释放被映射的虚拟内存空间
UnmapViewOfFile(pBuf);
CloseHandle(hMapHandle);
return 0;
}
进程二主要代码:
int OpenFileMapView()
{
TCHAR szName[]=TEXT("Global\\MyFileMappingObject");
//打开文件映射内核对象
HANDLE hMapFile = OpenFileMapping(
FILE_MAP_ALL_ACCESS, // read/write access
FALSE, // do not inherit the name
szName); // name of mapping object
if (hMapFile == NULL)
{
_tprintf(TEXT("Could not open file mapping object (%d).\n"), ::GetLastError());
return -1;
}
//映射文件内核对象到进程空间
char* pBuf = (char*)MapViewOfFile(hMapFile, // handle to map object
FILE_MAP_ALL_ACCESS, // read/write permission
0,
0,
0);
if (pBuf == NULL)
{
_tprintf(TEXT("Could not map view of file (%d).\n"),GetLastError());
CloseHandle(hMapFile);
return 1;
}
//被映射到进程地址空间的起始地址以及数据打印
printf("get share data %s pBufVal = %x", pBuf,pBuf);
//通知进程完成数据读取
HANDLE hNotifyHandle = ::OpenEvent(EVENT_ALL_ACCESS,FALSE,_T("file_map_handle"));
if (NULL != hNotifyHandle)
{
::SetEvent(hNotifyHandle);
}
//从进程空间中释放被映射的文件
UnmapViewOfFile(pBuf);
CloseHandle(hMapFile);
return 0;
}
运行结果:
这样就完成了两个进程间的数据通信,从中我们也可以发现从MapViewOfFile返回的起始地址在每个进程空间可以是不相等的。