内存映射文件实现进程间通信

原理介绍

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

发布了281 篇原创文章 · 获赞 327 · 访问量 66万+

猜你喜欢

转载自blog.csdn.net/xiao3404/article/details/102616458