CreateFileMapping使用方法

CreateFileMapping的MSDN翻译和使用心得
 
測试创建和打开文件映射的时候老是得到"句柄无效"的错误, 细致看了MSDN以后才发觉是函数认识不透, 这里把相关的解释翻译出来

HANDLE CreateFileMapping(
  HANDLE hFile,                       //物理文件句柄
  LPSECURITY_ATTRIBUTES lpAttributes, //安全设置
  DWORD flProtect,                    //保护设置
  DWORD dwMaximumSizeHigh,            //高位文件大小
  DWORD dwMaximumSizeLow,             //低位文件大小
  LPCTSTR lpName                      //共享内存名称
);

1) 物理文件句柄
   不论什么能够获得的物理文件句柄, 假设你须要创建一个物理文件无关的内存映射也无妨, 将它设置成为 0xFFFFFFFF(INVALID_HANDLE_VALUE)就能够了.

   假设须要和物理文件关联, 要确保你的物理文件创建的时候的訪问模式和"保护设置"匹配, 比方: 物理文件仅仅读, 内存映射须要读写就会错误发生. 推荐你的物理文件使用独占方式创建.

   假设使用 INVALID_HANDLE_VALUE, 也须要设置须要申请的内存空间的大小, 不管物理文件句柄參数是否有效, 这样 CreateFileMapping 就能够创建一个和物理文件大小无关的内存空间给你, 甚至超过实际文件大小, 假设你的物理文件有效, 而大小參数为0, 则返回给你的是一个和物理文件大小一样的内存空间地址范围.  返回给你的文件映射地址空间是能够通过复制, 集成或者命名得到, 初始内容为0.

2) 保护设置
   就是安全设置, 只是一般设置NULL就能够了, 使用默认的安全配置. 在win2k下假设须要进行限制, 这是针对那些将内存文件映射共享给整个网络上面的应用进程使用是, 能够考虑进行限制.

3) 高位文件大小
   弟兄们, 我想眼下我们的机器都是32位的东东, 不可能得到超过32位进程所能寻址的私有32位地址空间, 一般还是设置0吧, 我没有也不想尝试将它设置超过0的情况.
4) 低位文件大小
   这个还是能够进行设置的, 只是为了让其它共享用户知道你申请的文件映射的相关信息, 我使用的时候是在获得的地址空间头部加入�一个结构化描写叙述信息, 记录内存映射的大小, 名称等, 这样实际申请的空间就比输入的添加�了一个头信息结构大小了, 我觉得这样类似BSTR的方式应该是比較合理的.

5) 共享内存名称
   这个就是我今天測试的时候碰壁的祸根, 由于为了对于内存进行相互排斥訪问, 我设置了一个相互排斥句柄, 而名称我选择和命名共享内存同名, 之下就是由于他们使用共同的namespace导致了错误, 呵呵.

7) 调用CreateFileMapping的时候GetLastError的相应错误
   ERROR_FILE_INVALID     假设企图创建一个零长度的文件映射, 应有此报
   ERROR_INVALID_HANDLE   假设发现你的命名内存空间和现有的内存映射, 相互排斥量, 信号量, 临界区同名就麻烦了
   ERROR_ALREADY_EXISTS   表示内存空间命名已经存在

8) 相关服务或者平台的命名保留
   Terminal Services:
   命名能够包括 "Global" 或者 "Local" 前缀在全局或者会话名空间0基础文件映射. 其它部分能够包括不论什么除了()以外的字符, 能够參考 Kernel Object Name Spaces.

   Windows 2000 or later:
   假设 Terminal Services 没有执行 "Global" 和 "Local" 前缀的特殊含义就被忽略了

 
CreateFileMapping 函数(转载)
内存映射API函数CreateFileMapping创建一个有名的共享内存:
HANDLE CreateFileMapping(
HANDLE hFile,                                    // 映射文件的句柄,
                                                 //设为0xFFFFFFFF以创建一个进程间共享的对象
LPSECURITY_ATTRIBUTES lpFileMappingAttributes,   // 安全属性
DWORD flProtect,                                 // 保护方式
DWORD dwMaximumSizeHigh,                         //对象的大小
DWORD dwMaximumSizeLow,
LPCTSTR lpName                                   // 必须为映射文件命名
);

与虚拟内存类似,保护方式能够是PAGE_READONLY或是PAGE_READWRITE。假设多进程都对同一共享内存进行写訪问,则必须保持相互间同步。映射文件还能够指定PAGE_WRITECOPY标志,能够保证其原始数据不会遭到破坏,同一时候同意其它进程在必要时自由的操作数据的拷贝。

在创建文件映射对象后使用能够调用MapViewOfFile函数映射到本进程的地址空间内。

以下说明创建一个名为MySharedMem的长度为4096字节的有名映射文件:
HANDLE hMySharedMapFile=CreateFileMapping((HANDLE)0xFFFFFFFF),
NULL,PAGE_READWRITE,0,0x1000,"MySharedMem");
并映射缓存区视图:
LPSTR pszMySharedMapView=(LPSTR)MapViewOfFile(hMySharedMapFile,
FILE_MAP_READ|FILE_MAP_WRITE,0,0,0);

其它进程訪问共享对象,须要获得对象名并调用OpenFileMapping函数。
HANDLE hMySharedMapFile=OpenFileMapping(FILE_MAP_WRITE,
FALSE,"MySharedMem");

一旦其它进程获得映射对象的句柄,能够象创建进程那样调用MapViewOfFile函数来映射对象视图。用户能够使用该对象视图来进行数据读写操作,以达到数据通讯的目的。

当用户进程结束使用共享内存后,调用UnmapViewOfFile函数以取消其地址空间内的视图:
if (!UnmapViewOfFile(pszMySharedMapView))
{

         AfxMessageBox("could not unmap view of file");

 }
 
创建文件视图

    要将文件里的数据映射到进程的虚拟内存中,你必须创建一个文件的视图。
    MapViewOfFile和MapViewOfFileEx函数使用CreateFileMapping返回的文件映射对象句柄来在进程的虚拟地址空间里建立文件的视图,或者文件的某个部分。假设这些函数指定的权限标志和CreateFileMapping中的权限标志不一致,则会运行失败。
    MapViewOfFile函数返回一个指向文件视图的指针。利用MapViewOfFile中声明的地址指针,程序就能够从文件里读以及向文件里写入数据。向文件视图中写入数据会导致文件映射对象改变。真正将数据写入到磁盘上的文件,由系统负责处理。数据并非立即就别写到磁盘上,非常多文件的输入输出都被缓存起来,以改善系统的性能。程序能够调用FlushViewOfFile函数来越过这个方式,强迫系统立即将数据写入到磁盘中去。
    MapViewOfFileEx函数和MapViewOfFile函数作的工作是一模一样的,仅仅只是能够利用MapViewOfFileEx函数的lpvBase參数,来指定文件视图在进程虚拟地址空间中的基础地址。假设在指定的地址处没有足够的空间,则调用失败。
    1、lpvBase參数必须是系统内存最小单位的整数倍,否则调用会失败。要得到系统内存的最小单位,使用GetSystemInfo函数,他将信息写到SYSTEM_INFO结构的成员中。
    程序能够从同一个文件映射对象中创建多个文件视图。文件视图能够是不同的大小,但他们必须小于文件映射对象。MapViewOfFile函数的dwOffsetHigh和dwOffsetLow參数必须是系统内存最小单位的整数倍。


文件映射问题
内存映射文件并非简单的文件I/O操作,实际用到了Windows的核心编程技术--内存管理。所以,假设想对内存映射文件有更深刻的认识,必须对Windows操作系统的内存管理机制有清楚的认识,内存管理的相关知识很复杂,超出了本文的讨论范畴,在此就不再赘述,感兴趣的读者能够參阅其它相关书籍。以下给出使用内存映射文件的一般方法:  
   
    首先要通过CreateFile()函数来创建或打开一个文件内核对象,这个对象标识了磁盘上将要用作内存映射文件的文件。在用CreateFile()将文件映像在物理存储器的位置通告给操作系统后,仅仅指定了映像文件的路径,映像的长度还没有指定。为了指定文件映射对象须要多大的物理存储空间还须要通过CreateFileMapping()函数来创建一个文件映射内核对象以告诉系统文件的尺寸以及訪问文件的方式。在创建了文件映射对象后,还必须为文件数据保留一个地址空间区域,并把文件数据作为映射到该区域的物理存储器进行提交。由MapViewOfFile()函数负责通过系统的管理而将文件映射对象的所有或部分映射到进程地址空间。此时,对内存映射文件的使用和处理同通常载入到内存中的文件数据的处理方式基本一样,在完毕了对内存映射文件的使用时,还要通过一系列的操作完毕对其的清除和使用过资源的释放。这部分相对照较简单,能够通过UnmapViewOfFile()完毕从进程的地址空间撤消文件数据的映像、通过CloseHandle()关闭前面创建的文件映射对象和文件对象。

内存映射文件相关函数  
   
    在使用内存映射文件时,所使用的API函数主要就是前面提到过的那几个函数,以下分别对其进行介绍:  
   
  HANDLE   CreateFile(LPCTSTR   lpFileName,  
  DWORD   dwDesiredAccess,  
  DWORD   dwShareMode,  
  LPSECURITY_ATTRIBUTES   lpSecurityAttributes,  
  DWORD   dwCreationDisposition,  
  DWORD   dwFlagsAndAttributes,    
  HANDLE   hTemplateFile);      
   
    函数CreateFile()即使是在普通的文件操作时也经经常使用来创建、打开文件,在处理内存映射文件时,该函数来创建/打开一个文件内核对象,并将其句柄返回,在调用该函数时须要依据是否须要数据读写和文件的共享方式来设置參数dwDesiredAccess和dwShareMode,错误的參数设置将会导致对应操作时的失败。  
   
  HANDLE   CreateFileMapping(HANDLE   hFile,  
  LPSECURITY_ATTRIBUTES   lpFileMappingAttributes,  
  DWORD   flProtect,  
  DWORD   dwMaximumSizeHigh,  
  DWORD   dwMaximumSizeLow,  
  LPCTSTR   lpName);      
   
    CreateFileMapping()函数创建一个文件映射内核对象,通过參数hFile指定待映射到进程地址空间的文件句柄(该句柄由CreateFile()函数的返回值获取)。因为内存映射文件的物理存储器实际是存储于磁盘上的一个文件,而不是从系统的页文件里分配的内存,所以系统不会主动为其保留地址空间区域,也不会自己主动将文件的存储空间映射到该区域,为了让系统能够确定对页面採取何种保护属性,须要通过參数flProtect来设定,保护属性PAGE_READONLY、PAGE_READWRITE和PAGE_WRITECOPY分别表示文件映射对象被映射后,能够读取、读写文件数据。在使用PAGE_READONLY时,必须确保CreateFile()採用的是GENERIC_READ參数;PAGE_READWRITE则要求CreateFile()採用的是GENERIC_READ|GENERIC_WRITE參数;至于属性PAGE_WRITECOPY则仅仅须要确保CreateFile()採用了GENERIC_READ和GENERIC_WRITE当中之中的一个就可以。DWORD型的參数dwMaximumSizeHigh和dwMaximumSizeLow也是相当重要的,指定了文件的最大字节数,因为这两个參数共64位,因此所支持的最大文件长度为16EB,差点儿能够满足不论什么大数据量文件处理场合的要求。  
   
  LPVOID   MapViewOfFile(HANDLE   hFileMappingObject,  
  DWORD   dwDesiredAccess,  
  DWORD   dwFileOffsetHigh,  
  DWORD   dwFileOffsetLow,  
  DWORD   dwNumberOfBytesToMap);    
   
    MapViewOfFile()函数负责把文件数据映射到进程的地址空间,參数hFileMappingObject为CreateFileMapping()返回的文件映像对象句柄。參数dwDesiredAccess则再次指定了对文件数据的訪问方式,并且相同要与CreateFileMapping()函数所设置的保护属性相匹配。尽管这里一再对保护属性进行反复设置看似多余,但却能够使应用程序能很多其它的对数据的保护属性实行有效控制。MapViewOfFile()函数同意所有或部分映射文件,在映射时,须要指定数据文件的偏移地址以及待映射的长度。当中,文件的偏移地址由DWORD型的參数dwFileOffsetHigh和dwFileOffsetLow组成的64位值来指定,并且必须是操作系统的分配粒度的整数倍,对于Windows操作系统,分配粒度固定为64KB。当然,也能够通过例如以下代码来动态获取当前操作系统的分配粒度:  
   
  SYSTEM_INFO   sinf;  
  GetSystemInfo(&sinf);  
  DWORD   dwAllocationGranularity   =   sinf.dwAllocationGranularity;    
   
    參数dwNumberOfBytesToMap指定了数据文件的映射长度,这里须要特别指出的是,对于Windows   9x操作系统,假设MapViewOfFile()无法找到足够大的区域来存放整个文件映射对象,将返回空值(NULL);可是在Windows   2000下,MapViewOfFile()仅仅须要为必要的视图找到足够大的一个区域就可以,而无须考虑整个文件映射对象的大小。  
   
    在完毕对映射到进程地址空间区域的文件处理后,须要通过函数UnmapViewOfFile()完毕对文件数据映像的释放,该函数原型声明例如以下:  
   
  BOOL   UnmapViewOfFile(LPCVOID   lpBaseAddress);    
   
    唯一的參数lpBaseAddress指定了返回区域的基地址,必须将其设定为MapViewOfFile()的返回值。在使用了函数MapViewOfFile()之后,必需要有相应的UnmapViewOfFile()调用,否则在进程终止之前,保留的区域将无法释放。除此之外,前面还曾由CreateFile()和CreateFileMapping()函数创建过文件内核对象和文件映射内核对象,在进程终止之前有必要通过CloseHandle()将其释放,否则将会出现资源泄漏的问题。  
   
    除了前面这些必须的API函数之外,在使用内存映射文件时还要依据情况来选用其它一些辅助函数。比如,在使用内存映射文件时,为了提快速度,系统将文件的数据页面进行快速缓存,并且在处理文件映射视图时不马上更新文件的磁盘映像。为解决问题能够考虑使用FlushViewOfFile()函数,该函数强制系统将改动过的数据部分或所有又一次写入磁盘映像,从而能够确保所有的数据更新能及时保存到磁盘。  

共享内存对象方法(MapViewOfFile)
共享内存对象方法通常,将页面文件支持的内存映射文件作为在用户进程之间共享内存的技术。可是,能够使用同样的技术在用户进程与设备驱动程序之间共享内存。使用这样的技术有两种方法。

第一种方法中,通过使用 OpenFileMapping,然后调用 MapViewOfFile 函数以获取指向某个区域或全部共享内存的指针,驱动程序能够创建命名内存对象(称为“区域对象”),而且一个或多个用户应用程序能够打开同样的对象。通过向区域对象指定保护属性,能够定义进程操纵内存的方式。

另外一种方法中,应用程序能够用 CreateFileMapping 在用户模式下创建命名内存对象。驱动程序通过使用 ZwOpenSection 并调用 ZwMapViewOfSection 获取指向它的指针,能够打开同样的内存对象。始终用异常处理程序在内核模式下訪问此内存地址。

由于该对象始终映射在进程的用户地址空间(小于 0x80000000,不管对象是在内核模式还是在用户模式中创建的)中,因此仅仅在进程上下文中訪问地址时,地址才有效。每次在同样内存对象上调用 MapViewOfFile 或 ZwMapViewOfSection 时,都将返回不同的内存地址(即使是同样的进程,也是如此)。建议不要使用这样的方法(尤其是低级设备驱动程序),正如前面所述,这是由于地址范围限定于进行对象映射的进程,而且不能在 DPC 或 ISR 中对地址进行訪问。另外,在 DDK 中没有记载在内核模式下创建内存对象的 API。

可是,要在提高的 IRQL(如 DPC 或 ISR 中)上使用该地址,必须查明并锁定缓冲区页面,并获取系统虚拟地址 MmGetSystemAddressForMdl(正如本文前面 IOCTL 方法中所述)。

仅当要在两个(或很多其它)用户进程与一个(或多个)设备驱动程序之间共享内存的情况下,这样的方法才比較简便。否则,使用 IOCTL 技术在用户进程与设备驱动程序之间共享内存更加简单高效。
 

内存映射文件技术
1. 用途和基本操作
用于不同进程之间的内存共享操作, 能够将一个物理文件映射到内存其中然后直接利用分配到的或者打开的命名共享内存的地址空间实现资源共享訪问

2. 相关流程
1) 新建命名共享内存
首先利用CreateFile或者CreateFileForMapping获得一个用于映射的物理文件句柄, 然后利用该文件句柄结合CreateFileMapping得到一个命名的共享内存映射文件句柄。
//CreateFileMapping 为指定文件创建一个有名或无名的文件映象;
HANDLE CreateFileMapping(
  HANDLE hFile,              // 映射文件的句柄
  LPSECURITY_ATTRIBUTES lpFileMappingAttributes, // 安全描写叙述符指针
  DWORD flProtect,           // 对映射对象的保护
  DWORD dwMaximumSizeHigh,   // 对象最大长度的高32位
  DWORD dwMaximumSizeLow,    // 对象最大长度的低32位
  LPCTSTR lpName             // 文件内存映射对象的名字
);

注意:
hFile:映射文件的句柄,文件的打开模式必须与flProtect參数指定的相一致;假设这个參数值为0xFFFFFFFF,那么必须在dwMaximumSizeHigh和dwMaximumSizeLow參数中指定映射对象的大小。而且将在操作系统虚拟内存页面替换文件里创建文件映射对象,而不是使用磁盘文件,同一时候必须给出这个映射对象的大小。文件映射对象通过副本,遗传或名字来共享。
lpFileMappingAttributes:安全描写叙述符指针,决定返回句柄能否被子进程继承,假设是NULL,那么子进程不能继承。WinNt中,假设是NULL,那么文件映射对象得到一个默认的安全描写叙述符。
flProtect:为得到的文件试图指定保护模式,能够被设置为下列值:
 PAGE_READONLY :仅仅读属性,而且hFile相应的文件必须以GENERIC_READ形式打开。
 PAGE_READWRITE:可读可写属性,而且hFile相应的文件必须以GENERIC_READ 和 GENERIC_WRITE形式打开。
 PAGE_WRITECOPY:对可写区域复制后操作,而且hFile相应的文件必须以GENERIC_READ 和 GENERIC_WRITE形式打开。
dwMaximumSizeHigh,dwMaximumSizeLow:假设这两个參数为0,则文件映射对象的最大长度等于hFile指定的文件长度。
lpName:文件映射对象的名字,假设这个名字已存在,则依照flProtect指定的来处理映射对象。假设此參数为空,则创建一个无名字的文件映射对象。假设此參数的名字与系统事件的名字同样,则函数运行失败,GetLastError返回 ERROR_INVALID_HANDLE;

返回值:函数调用成功返回文件映射对象的句柄,假设文件映射对象已经存在则返回原有映射对象的句柄,GetLastError返回ERROR_ALREADY_EXISTS。函数运行失败返回Null。

2) 打开命名共享内存
假设须要共享已经存在的命名共享内存映射文件, 使用OpenFileMapping函数。
//OpenFileMapping 打开一个已命名的文件映射对象
HANDLE OpenFileMapping(
  DWORD dwDesiredAccess,  // 訪问模式
  BOOL bInheritHandle,    // 继承标志
  LPCTSTR lpName          // 文件映射对象名指针
);
注意:
dwDesiredAccess:訪问模式与MapViewOfFile中的訪问模式同样。
bInheritHandle:继承标志,能否够被一个新的进程继承使用,假设为TRUE,就能够被一个新进程继承句柄。
返回值:
 成功返回一个已命名的文件映射对象,失败返回NULL。

3) 获得地址空间指针
进行内存映射文件的读写和一般的文件读写不同, 是直接面对你申请的地址空间, 为此须要使用MapViewOfFile得到相关的地址LPVOID类型的指针。假设须要进行文件写入, 能够通过类型转换直接对于内存地址进行赋值, 比方:
memcpy( lpAddress, lpBuf, ....)
这里自然须要防止内存溢出的情况。
假设是读取操作,将參数顺序调整一下就能够了。

MapViewOfFile 在调用进程的地址空间映射一个文件视图
LPVOID MapViewOfFile(
  HANDLE hFileMappingObject,  // 已创建的文件映射对象句柄
  DWORD dwDesiredAccess,      // 訪问模式
  DWORD dwFileOffsetHigh,     // 文件偏移的高32位
  DWORD dwFileOffsetLow,      // 文件偏移的低32位
  DWORD dwNumberOfBytesToMap  // 映射视图的大小
);
注意:
hFileMappingObject: 由CreateFileMapping 或 OpenFileMapping 返回的文件映射对象句柄。
dwDesiredAccess:映射视图的訪问模式,与创建文件映射对象的保护模式flProtect有关,能够被设置为下列值:
 FILE_MAP_WRITE:一个可读写属性的文件视图被创建,保护模式为PAGE_READWRITE
 FILE_MAP_READ :一个仅仅读属性的文件视图被创建,保护模式为PAGE_READWRITE 或 PAGE_READONLY
 FILE_MAP_ALL_ACCESS:与FILE_MAP_WRITE模式同样
 FILE_MAP_COPY:保护模式为PAGE_WRITECOPY时,得到一个视图文件,当你对视图文件写操作时,页面自己主动交换,而且你所做的改动不会损坏原始数据资料。
dwNumberOfBytesToMap:映射文件部分的大小,假设为0,则映射整个文件。
返回值:
假设成功返回返回映射视图的起始地址,假设失败返回NULL。

4)MapViewOfFileEx 在调用进程的地址空间映射一个文件视图,而且同意调用进程为映射视图指定特殊的内存地址
LPVOID MapViewOfFileEx(
  HANDLE hFileMappingObject,  // 文件映射对象的句柄
  DWORD dwDesiredAccess,      // 訪问模式
  DWORD dwFileOffsetHigh,     // 文件偏移的高32位
  DWORD dwFileOffsetLow,      // 文件偏移的低32位
  DWORD dwNumberOfBytesToMap, // 映射视图的大小
  LPVOID lpBaseAddress        // 指定映射视图的事实上内存地址
);
注意:
与MapViewOfFile使用方法同样,可是假设指定的内存地址空间大小不够,则函数运行失败。


5) 将内存拷贝到所映射的物理文件上面
FlushMapViewOfFile函数能够将内存里面的内容DUMP到物理磁盘上面
FlushViewOfFile 把文件映射视图中的改动的内容或所有写回到磁盘文件里
BOOL FlushViewOfFile(
  LPCVOID lpBaseAddress,       // 改动内容的起始地址
  DWORD dwNumberOfBytesToFlush // 改动的字节数目
);
函数运行成功返回非零。

6) 卸载内存映射文件地址指针
UnmapViewOffFile函数就是卸载
UnmapViewOfFile 删除文件的映射视图
BOOL UnmapViewOfFile(
  LPCVOID lpBaseAddress   // 映射视图起始地址
);
注意:
lpBaseAddress:映射视图起始地址,由 MapViewOfFile 函数 MapViewOfFileEx产生。
返回值:
假设调用成功返回非零,而且全部指定地址内的脏页面会被写入硬盘。调用失败返回零。

7) 关闭内存映射文件
太简单了, CloseHandle搞定

原文地址:https://www.cnblogs.com/hrhguanli/p/4007100.html