原理介紹
在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返回的起始地址在每個進程空間可以是不相等的。