Windows进程通信之内存映射文件

本文详细介绍了内存映射文件的概念、生命周期及如何使用内存映射文件实现进程间通信,包括创建、映射、使用和关闭过程,以及在主进程与子进程中共享数据并接收通知的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本文由danny发表于 http://blog.youkuaiyun.com/danny_share 

说明:建议先下载本文配套工程,其中

FileMapMain工程、FileMapASub工程,FileMapBSub工程分别用于演示进程间通信的主进程和两个子进程

下载地址:http://download.youkuaiyun.com/detail/danny_share/7749135

注意:

1.不要F5直接运行

2.编译生成debug目录或者release目录以后,手动双击FileMapMain.exe点击Test按钮即可

 

 

一.   概念

1.       内存映射文件

1)将硬盘上的文件逻辑上映射成进程虚拟地址空间内的一块区域,此后应用程序就可以直接使用指针而非显式的IO来读写该文件。

2)详见http://msdn.microsoft.com/en-us/library/windows/desktop/aa366556(v=vs.85).aspx

3)英文原文成为File Mapping,但是中文貌似都叫“内存映射文件”而不是“文件映射”

 

2.       内存映射文件和标准IO读写的区别

(1)       内存映射文件将进程的虚拟地址空间直接和硬盘上的文件直接关联起来

(2)       标准IO读写是根据程序的需要,要读取某个页的时候,如该页不存在,则产生缺页中断,然后系统读取物理文件至系统缓冲,再给应用程序。

(3)       标准IO读写中应用程序使用显式IO在应用程序中分配buffer去存放将从页中读取的数据,而内存文件映射则是直接使用指针读写文件,就如同操作char*一样。

 

3.       内存映射文件和虚拟内存的区别

1)内存映射文件是将整个文件逻辑映射到应用程序的虚拟地址空间

2)虚拟内存则根据“程序的局部性原理”加载文件的一部分数据到页文件中

 

4.       内存映射文件和页文件的关系

(1)       内存映射文件中的文件既可以是硬盘上的物理文件

(2)       也可以是系统的页文件,此时系统从它的页文件中提交物理存储器

 

二.生命周期

1.       创建

1)通过CreateFileMapping函数创建一个文件映射对象

HANDLE WINAPI CreateFileMapping(

  _In_      HANDLE hFile,

  _In_opt_  LPSECURITY_ATTRIBUTES lpAttributes,

  _In_      DWORD flProtect,

  _In_      DWORD dwMaximumSizeHigh,

  _In_      DWORD dwMaximumSizeLow,

  _In_opt_  LPCTSTR lpName

);


 

(2)       如果想要创建硬盘上的文件映射对象,则此时hFile是由CreateFile函数返回的句柄,这种情况一般适用于打开大文件的情况

(3)       如果想要创建页文件支持的文件映射对象,则hFile设置成INVALID_HANDLE_VALUE,这种情况比较适合用来作为跨进程通信时共享数据适用

(4)       在跨进程中,另一方适用OpenFileMapping打开该映射对象

LPVOID WINAPI MapViewOfFile(
  _In_  HANDLE hFileMappingObject,
  _In_  DWORD dwDesiredAccess,
  _In_  DWORD dwFileOffsetHigh,
  _In_  DWORD dwFileOffsetLow,
  _In_  SIZE_T dwNumberOfBytesToMap
);

 

2.       映射

1)进程通过将自己的虚拟地址空间逻辑上和文件映射对象绑定起来后,就可以使用该文件映射对象了

LPVOID WINAPI MapViewOfFile(
  _In_  HANDLE hFileMappingObject,
  _In_  DWORD dwDesiredAccess,
  _In_  DWORD dwFileOffsetHigh,
  _In_  DWORD dwFileOffsetLow,
  _In_  SIZE_T dwNumberOfBytesToMap
);


 

2)其中的hFileMappingObject就是刚刚OpenFileMapping获得的句柄

 

3.       使用

通过MapViewOfFile后得到一个PVOID指针,读写该映射对象就如何操作一个堆指针一样简单了

 

4.       关闭

1UnmapViewOfFile,收回之前预定的虚拟地址空间

2CloseHanlde()关闭映射文件对象,如果打开的是硬盘上的文件,还要再CloseHandle那个文件

 

5.       因此一般的内存映射文件流程如下

HANDLE hFile = CreateFile(...);//如果需要的话
HANDLE hFileMapping = CreateFileMapping(hFile, ...);
PVOID pvFile = MapViewOfFile(hFileMapping, ...);
 
// 读写
 
UnmapViewOfFile(pvFile);
CloseHandle(hFileMapping);
CloseHandle(hFile);


 

 

三.使用文件映射实现进程通信

1.       设计

内存映射文件也不具备监听变化或通知的功能,因此我们还是采用内存映射文件+消息实现数据共享+通知的功能,架构如下,和进程内存读写是不是很相似,其实整体架构都类似

 

2.       实现

1)主进程创建两个内存映射文件对象

void CFileMapMainDlg::createMap()

{

    this->mapA=::CreateFileMapping(INVALID_HANDLE_VALUE,NULL,PAGE_READWRITE,0,100,"MapA");

    if(NULL!=mapA)

    {

       this->mapALP=::MapViewOfFile(mapA,FILE_MAP_READ,0,0,100);

    }

    this->mapB=::CreateFileMapping(INVALID_HANDLE_VALUE,NULL,PAGE_READWRITE,0,100,"MapB");

    if(NULL!=mapB)

    {

       this->mapBLP=::MapViewOfFile(mapB,FILE_MAP_READ,0,0,100);

    }

}


 

注意,这里我设计成由子进程写数据,主进程读数据,所以此处主进程映射的时候权限为FILE_MAP_READ

 

2)主进程在OnInitDialog里先createMap然后再openSubProcess

3)主进程定义两个用来接收通知的消息

#define WM_MPA (WM_USER+100)

#define WM_MPB (WM_USER+101)


 

然后映射消息处理函数

    ON_MESSAGE(WM_MPA, OnMyMessageA)

    ON_MESSAGE(WM_MPB, OnMyMessageB)


 

4)主进程响应子进程A的消息处理函数如下,即读取内存映射文件对象里的数据

 

LRESULT CFileMapMainDlg::OnMyMessageA(WPARAM wParam, LPARAM lParam)

{

    if(NULL!=mapALP)  

    {  

       CString info=(char*)this->mapALP;

       MessageBox(info,"Info",MB_OK);

    }

    else

    {

       MessageBox("A response Failed","Info",MB_OK);

    }

    return 0;

}


 

5)子进程中在OnInitDialog里打开内存文件映射对象

void CFileMapASubDlg::openMap()

{

    this->mapA=::OpenFileMapping(FILE_MAP_WRITE, FALSE, "MapA"); 

    if(NULL!=mapA)

    {

       this->mapLP=::MapViewOfFile(mapA,FILE_MAP_WRITE,0,0,100);

    }

}


 

6)子进程同时响应主进程发来的消息,向映射对象里写入数据(因而上面MapView的时候权限是FILE_MAP_WRITE)

LRESULT CFileMapASubDlg::OnMyMessageA(WPARAM wParam, LPARAM lParam)

{

    if(NULL!=mapLP)  

    { 

       ::strcpy((char*)mapLP,"hello,i am a");

       ::PostMessage(::FindWindow(NULL,"FileMapMain"),WM_MPA,0,0);

    }

    return 0;

}


 

7)万事俱备,只欠东风,主进程里Test按钮的响应函数点燃了引线

void CFileMapMainDlg::OnBnClickedButton1()

{

    // TODO: Add your control notification handler code here

    ::PostMessage(::FindWindow(NULL,"FileMapASub"),WM_MPA,0,0);

    ::PostMessage(::FindWindow(NULL,"FileMapBSub"),WM_MPB,0,0);

}


 

四.总结

1.内存映射文件API使用简单,不管处理大文件还是用来共享数据都是很好的选择

2.不提供通知或者监听功能,要实现共享数据+通知需要配合其他机制使用

3.实际很多进程通信机制的底层都是靠内存映射文件实现,比如我们之前的WM_COPYDATA

 

danny

2014年8月12号

于天津

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值