內存映射文件實現進程間通信

原理介紹

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章