原理介绍
在Windows平台中,常见的进程间通信机制包括管道、socket、WM_COPYDATA、邮槽等,这些在同一台机器上实现共享数据的最底层机制就是内存映射文件,如果要求低开销和高性能,内存映射文件无疑是最佳选择。
创建一个内存映射文件的步骤如下:
(1)创建一个文件映射内核对象(file-mapping kernel object)并指定系统文件大小以及访问方式。
(2)把文件映射对象的部分或者全部映射到进程的地址空间,被映射到进程地址空间的部分称为视图。
(3)操作内存映射文件,如同操作内存一样,可以读写。
(4)从进程的地址空间中卸载被映射的文件映射内核对象。
(5)关闭文件内核对象。
需要说明的是,被映射的对象可以是磁盘文件也可以是页交换文件,为了实现进程间通信,需要保证每个进程操作的是同一个对象,操作系统保证同一个文件映射对象的多个视图保持一致。
以下是内存映射文件的示意图:
文件映射内核对象
创建文件映射对象需要使用CreateFileMapping接口,其API如下:
HANDLE CreateFileMappingA(HANDLEhFile,LPSECURITY_ATTRIBUTES lpFileMappingAttributes,DWORD flProtect,DWORD dwMaximumSizeHigh,DWORD dwMaximumSizeLow,LPCSTRlpName);
其中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 fileNULL,// default securityPAGE_READWRITE,// read/write access0, // maximum object size (high-order DWORD)1024,// maximum object size (low-order DWORD)szName); // name of mapping objectif (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 accessFALSE, // do not inherit the nameszName);// name of mapping objectif (hMapFile == NULL){_tprintf(TEXT("Could not open file mapping object (%d).\n"), ::GetLastError());return -1;}//映射文件内核对象到进程空间char* pBuf = (char*)MapViewOfFile(hMapFile, // handle to map objectFILE_MAP_ALL_ACCESS, // read/write permission0,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返回的起始地址在每个进程空间可以是不相等的。