本文由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. 关闭
(1)UnmapViewOfFile,收回之前预定的虚拟地址空间
(2)CloseHanlde()关闭映射文件对象,如果打开的是硬盘上的文件,还要再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号
于天津