原文地址::http://hi.baidu.com/hongluobo77/blog/item/df8307ef37b7c8232cf534e8.html
1. 用途和基本操作
用于不同进程之间的内存共享操作, 可以将一个物理文件映射到内存当中然后直接利用分配到的或者打开的命名共享内存的地址空间实现资源共享访问
2. 相关流程
1) 新建命名共享内存
首先利用CreateFile或者CreateFileForMapping获得一个用于映射的物理文件句柄, 然后利用该文件句柄结合CreateFileMapping得到一个命名的共享内存映射文件句柄
2) 打开命名共享内存
如果需要共享已经存在的命名共享内存映射文件, 使用OpenFileMapping函数
3) 获得地址空间指针
进行内存映射文件的读写和一般的文件读写不同, 是直接面对你申请的地址空间, 为此需要使用MapViewOfFile得到相关的地址LPVOID类型的指针.
如果需要进行文件写入, 可以通过类型转换直接对于内存地址进行赋值, 比如:
memcpy( lpAddress, lpBuf, ....)
这里自然需要防止内存溢出的情况
如果是读取操作, 呵呵将参数顺序调整一下就可以了
4) 将内存复制到所映射的物理文件上面
FlushMapViewOfFile函数可以将内存里面的内容DUMP到物理磁盘上面
5) 卸载内存映射文件地址指针
UnmapViewOffFile函数就是卸载
6) 关闭内存映射文件
太简单了, CloseHandle搞定
2. 相关背景
其实内存映射技术本身没有什么难度, 关键在于共享内存映射的使用上面, 特别是进行不同进程之间的同步, 尤其是如果该内存映射是跨机器的情况下, 你需要考虑不同的异常情况.
详解 (付林林)
与虚拟内存一样,内存映射文件用来保留一个地址空间,并提交物理存储器。早期的内存映射文件并不是提交物理内存供调用者使用,而是提交永久存储器上的文件数据。当然操作系统会为永久存储器保留一个读缓冲区,这样读取文件数据就快多了。内存映射文件的特点使它很适合于加载EXE或DLL文件。这样可以节省内存又减少了加载所需时间。还可以使用它来映射大容量的文件,这样就不必在读取文件数据前设置很大的缓冲区。另外内存映射文件常用于进程间通信,也是进程间通信的主要手段,其它进程之间通信机制都是基于内存映射文件来实现。为了更快的在进程之间通信,现在的内存映射文件也可以提交物理内存,这样内存映射文件既可以提交物理内存又可以提交文件。
Windows CE.NET同样支持无名和有名的内存映射文件。我建议在开发软件的过程中,如果需要读写大容量的文件,或者需要在不同进程内的线程之间通信,最好采用内存映射文件,而且最好在全局地址空间内(0x4200 0000到0x7FFF FFFF)分配。这会使我们事半功倍。
1 映射数据文件
第一步:调用CreateFileForMapping函数。在Windows CE.NET中推荐使用这个函数替代CreateFile函数。CreateFileForMapping函数由内核执行并创建文件,它也可以打开由CreateFile函数创建的文件。其参数同CreateFile相似。参数1指定文件路径,注意文件路径的格式是没有盘符的,参数2指定访问方式(读或写),参数3指定共享模式,参数4指定安全属性(必须设置为NULL),参数5指定是创建还是打开文件,参数6指定文件属性,参数7忽略。具体参数细节参见Windows CE.NET帮助。函数返回创建或者打开的文件的句柄。
第二步:调用CreateFileMapping函数。这个函数创建一个无名的或者有名的内存映射文件对象。参数1为文件句柄。这个值由CreateFileForMapping函数返回。参数2为安全属性(必须设置为NULL),参数3指定要映射的文件的保护属性(只读或者读写),参数4和参数5共同用于指定要映射的文件的大小。文件的容量过大将导致32位整数也不能表示,所以这里用64位变量表示,其中参数4为高32位数,参数5为低32位数。最后一个参数指定内存映射文件的名称。这里可以设置为NULL,表示不需要名字。
第三步:调用MapViewOfFile函数。这个函数用于保留一段足够的地址空间,并且将永久存储器上的文件数据映射到这个地址空间。映射后这段地址空间又叫做文件视图,映射范围可以是全部文件,也可以是部分文件。这里需要注意的是如果文件很大,那这个函数将在全局地址空间内分配地址空间。参数1指定内存映射文件对象的句柄,这个值由CreateFileMapping函数返回。参数2和CreateFileMapping函数中参数3很相似,都是用于限定访问权限。参数3和参数4共同用于指定映射区域的开始位置。其中参数3为高32位数,参数4为低32位数。参数5指定映射区域的大小。需要注意的是参数3和参数4指定的64位数开始位置可以不是64KB的倍数。而其它Windows操作系统就必须限制以64KB为单位。另外还要注意的是帮助文档中说不能保证一个文件的映射视图是连续的,并建议为了防止访问非法,应该加入结构化异常处理机制。这个可能性我认为很小,一般对于大于2MB的虚拟地址空间的申请,内核都会在全局地址空间中分配。全局地址空间(0x4200 0000到0x7FFF FFFF)近1GB的空间应该足够用了。毕竟Windows CE下的文件都很小。不过在代码中加入结构化异常处理也不是坏事。我们应该养成凡是读写文件数据时都加入结构化异常处理的习惯。
第四步:进行读/写操作。MapViewOfFile函数如果成功执行,那么返回映射视图的首地址。这时就可以把视图当成是一个缓冲区,开始读或写操作了。
第五步:执行结束工作。先调用UnmapViewOfFile函数撤销文件映射视图。参数只有一个,指定视图首地址。然后调用CloseHandle函数关闭内存映射文件对象,参数为句柄。最后再次调用CloseHandle函数,关闭打开的文件的句柄。
2 进程之间通信
进程之间有时需要通信。系统提供的进程之间的通信机制比如COM、剪贴板等,在底层实现上都是利用内存映射文件技术。其实进程之间通信的思路很简单,在这里我顺便讲一下。在其它Windows操作系统中,每个进程独自占有4GB的地址空间,高2GB是内核的地址空间,而低2GB是进程的地址空间。一个进程所能访问的所有低2GB地址都是自己的地址空间,当访问内核地址空间时就会受到内核的限制。这样一个进程当然无法访问其它进程了。为解决进程间通信的问题,内存映射文件技术被利用作为解决方案。原来内存映射文件只映射类似磁盘一类的存储器上的文件。而为了更快速地在进程之间通信,内存映射文件还可以提交物理内存。实现方法是通过访问同一个内存映射文件对象(映射到物理内存),两个进程或多个进程就能够访问到同一块物理内存,这样一个进程写到物理内存的数据,其它进程就能够看到了。而Windows CE虽然每个进程只占有32MB的地址空间,而且所有进程全部处于4GB的地址空间中,但是彼此还是不能够随意访问的。在Windows CE下除了使用内存映射文件技术外,还有一种方法也很适合使用,就是利用对象存储。对象存储本身使用RAM文件系统,用普通的操作文件的API就可以创建、读取存在于对象存储区域内的文件。/Windows 目录就存在于对象存储区域内。我们可以利用在/Windows目录下创建文件来实现进程间通信。这种方法既实现简单,只需调用几个文件API函数,又可以减少通信时间,因为/Windows目录存在于物理内存中,数据I/O当然很快了。利用对象存储来实现进程之间的通信是我自己想出来的,MSDN或其它文档并没有这方面的说明。需要注意的就是对象存储区域的大小。另外从实现的代码量上看也不如内存映射文件技术。
下面讲解如何利用内存映射文件实现进程之间的通信。假设进程A和进程B需要通信,那么进程A需要先创建一个内存映射文件(之前不必调用CreateFileForMapping函数来创建文件,因为不需要创建文件)。这个内存映射文件可以是在永久存储器中,也可以是在内存中。为了减小通信时间,最好提交物理内存。进程A在调用CreateFileMapping函数时,参数1指定为INVALID_HANDLE_VALUE,这表示这个内存映射文件对象将要把物理内存提交到地址空间中。最后一个参数一定要指定一个名字。进程B也同样调用CreateFileMapping函数,而且参数相同。内核会根据名字来判断是否已经存在一个内存映射文件对象,如果创建了就返回原来的对象的句柄。接下去就不用细说了。参照5.1去执行就可以了。要注意的是进程B调用CreateFileMapping函数后要按如下代码检验函数执行结果:
HANDLE hMap; hMap = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, 1000, L"abc"); if (hMap == NULL || GetLastError() != ERROR_ALREADY_EXISTS) { MessageBox(L"create file mapping fail"); return; }