内存映射修改大文件

转载自:http://www.vckbase.com/index.php/wv/1527

  • 文章概要:
  • 本文介绍利用内存映射文件修改大文件: 创建或打开一个文件内核对象,该对象用于标识磁盘上你想用作内存映射文件的文件; 创建一个文件映射内核对象,告诉系统该文件的大小和你打算如何访问该文件; 让系统将文件映射对象的全部或一部分映射到你的进程地址空间中;
  • 本文介绍利用内存映射文件修改大文件:在大文件内存前加入一段数据,若要使用内存映射文件,必须执行下列操作步骤:

    1.创建或打开一个文件内核对象,该对象用于标识磁盘上你想用作内存映射文件的文件;

    2.创建一个文件映射内核对象,告诉系统该文件的大小和你打算如何访问该文件;

    3.让系统将文件映射对象的全部或一部分映射到你的进程地址空间中;

    当完成对内存映射文件的使用时,必须执行下面这些步骤将它清除:

    1.告诉系统从你的进程的地址空间中撤消文件映射内核对象的映像;

    2.关闭文件映射内核对象;

    3.关闭文件内核对象;

    下面将用一个实例详细介绍这些操作步骤,(本实例的目的就是将一个文件A其内容前面加入一些内容存入文件B,我想大家在程序开发当中会遇到这种情况的)。

    一、我们打开关于A文件内核对象,并创建一个关于B文件的内核对象

    若要创建或打开一个文件内核对象,总是要调用CreateFile函数:

    1. HANDLE CreateFile(
    2. PCSTR pszFileName,
    3. DWORD dwDesiredAccess,
    4. DWORD dwShareMode,
    5. PSECURITY_ATTRIBUTES psa,
    6. DWORD dwCreationDisposition,
    7. DWORD dwFlagsAndAttributes,
    8. HANDLE hTemplateFile);

    CreateFile函数拥有好几个参数,这里只重点介绍前3个参数,即pszFileName,dwDesiredAccess和dwShareMode。

    你可能会猜到,第一个参数pszFileName用于指明要创建或打开的文件的名字(包括一个选项路径),第二个参数dwDesiredAccess用于设定如何访问该文件的内容,可以设定下表所列的4个值中的一个。

    含义
    0不能读取或写入文件的内容,当只想获得文件的属性时,请设定0
    GENERIC_READ可以从文件中读取数据
    GENERIC_WRITE可以将数据写入文件
    GENERIC_READ|GENERIC_WRITE可以从文件中读取数据,也可以将数据写入文件

    当创建或打开一个文件,将它作为一个内存映射文件来使用时,请选定最有意义的一个或多个访问标志,以说明你打算如何访问文件的数据,对内存映射文件来说,必须打开用于只读访问或读写访问的文件,因此,可以分别设定GENERIC_READ或GENERIC_READ|GENERIC_WRITE,第三个参数dwShareMode告诉系统你想如何共享该文件,可以为dwShareMode设定下表所列的4个值之一:

    含义
    0打开文件的任何尝试均将失败
    FILE_SHARE_READ使用GENERIC_WRITE打开文件的其他尝试将会失败
    FILE_SHARE_WRITE使用GENERIC_READ打开文件的其他尝试将会失败
    FILE_SHARE_READFILE_SHARE_WRITE打开文件的其他尝试将会取得成功

    如果CreateFile函数成功地创建或打开指定的文件,便返回一个文件内核对象的句柄,否则返回INVALID_HANDLE_VALUE,注意能够返回句柄的大多数Windows函数如果运行失败,那么就会返回NULL,但是,CreateFile函数将返回INVALID_HANDLE_VALUE,它定义为((HANDLE)-1),

    1. HANDLEhFile=CreateFile(".\\first.txt",GENERIC_READ,
    2. FILE_SHARE_READ,NULL,OPEN_EXISTING,
    3. FILE_FLAG_SEQUENTIAL_SCAN,NULL);
    4. HANDLEhmyfile=CreateFile("E:\\my.txt",
    5. GENERIC_READ|GENERIC_WRITE,
    6. 0,NULL,OPEN_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);

    二、我们要分别创建两个文件映射内核对象

    调用CreateFile函数,就可以将文件映像的物理存储器的位置告诉操作系统,你传递的路径名用于指明支持文件映像的物理存储器在磁盘(或网络或光盘)上的确切位置,这时,必须告诉系统,文件映射对象需要多少物理存储器,若要进行这项操作,可以调用CreateFileMapping函数:

    1. HANDLE CreateFileMapping(
    2. HANDLE hFile,
    3. PSECURITY_ATTRIBUTES psa,
    4. DWORD fdwProtect,
    5. DWORD dwMaximumSizeHigh,
    6. DWORD dwMaximumSizeLow,
    7. PCTSTR pszName);

    第一个参数hFile用于标识你想要映射到进程地址空间中的文件句柄,该句柄由前面调用的CreateFile函数返回,psa参数是指向文件映射内核对象的SECURITY_ATTRIBUTES结构的指针,通常传递的值是NULL(它提供默认的安全特性,返回的句柄是不能继承的)。

    本章开头讲过,创建内存映射文件就像保留一个地址空间区域然后将物理存储器提交给该区域一样,因为内存映射文件的物理存储器来自磁盘上的一个文件,而不是来自从系统的页文件中分配的空间,当创建一个文件映射对象时,系统并不为它保留地址空间区域,也不将文件的存储器映射到该区域(下一节将介绍如何进行这项操作),但是,当系统将存储器映射到进程的地址空间中去时,系统必须知道应该将什么保护属性赋予物理存储器的页面,CreateFileMapping函数的fdwProtect参数使你能够设定这些保护属性,大多数情况下,可以设定下表中列出的3个保护属性之一。

    使用fdwProtect参数设定的部分保护属性

    保护属性含义
    PAGE_READONLY当文件映射对象被映射时,可以读取文件的数据,必须已经将GENERIC_READ传递给CreateFile函数。
    PAGE_READWRITE当文件映射对象被映射时,可以读取和写入文件的数据,必须已经将GENERIC_READ|GENERIC_WRITE传递给CreateFile。
    PAGE_WRITECOPY当文件映射对象被映射时,可以读取和写入文件的数据,如果写入数据,会导致页面的私有拷贝得以创建,必须已经将GENERIC_READ或GENERIC_WRITE传递给CreateFile。

    在Windows98下,可以将PAGE_WRITECOPY标志传递给CreateFileMapping,这将告诉系统从页文件中提交存储器,该页文件存储器是为数据文件的数据拷贝保留的,只有修改过的页面才被写入页文件,你对该文件的数据所作的任何修改都不会重新填入原始数据文件,其最终结果是,PAGE_WRITECOPY标志的作用在Windows2000和Windows98上是相同的。

    除了上面的页面保护属性外,还有4个节保护属性,你可以用OR将它们连接起来放入CreateFileMapping函数的fdwProtect参数中,节只是用于内存映射的另一个术语。

    节的第一个保护属性是SEC_NOCACHE,它告诉系统,没有将文件的任何内存映射页面放入高速缓存,因此,当将数据写入该文件时,系统将更加经常地更新磁盘上的文件数据,这个标志与PAGE_NOCACHE保护属性标志一样,是供设备驱动程序开发人员使用的,应用程序通常不使用,

    Windows98将忽略SEC_NOCACHE标志。

    节的第二个保护属性是SEC_IMAGE,它告诉系统,你映射的文件是个可移植的可执行(PE)文件映像,当系统将该文件映射到你的进程的地址空间中时,系统要查看件的内容,以确定将哪些保护属性赋予文件映像的各个页面,例如,PE文件的代码节(.text)通常用PAGE_EXECUTE_READ属性进行映射,而PE文件的数据节(.data)则通常用PAGE_READWRITE属性进行映射,如果设定的属性是SEC_IMAGE,则告诉系统进行文件映像的映射,并设置相应的页面保护属性。

    Windows98将忽略SEC_IMAGE标志

    最后两个保护属性是SEC_RESERVE和SEC_COMMIT,它们是两个互斥属性,当使用内存映射数据文件时,它们不能使用,这两个标志将在本章后面介绍,当创建内存映射数据文件时,不应该设定这些标志中的任何一个标志,CreateFileMapping将忽略这些标志。

    CreateFileMapping的另外两个参数是dwMaximumSizeHigh和dwMaximumSizeLow,它们是两个最重要的参数,CreateFileMapping函数的主要作用是保证文件映射对象能够得到足够的物理存储器,这两个参数将告诉系统该文件的最大字节数,它需要两个32位的值,因为Windows支持的文件大小可以用64位的值来表示,dwMaximumSizeHigh参数用于设定较高的32位,而dwMaximumSizeLow参数则用于设定较低的32位值,对于4GB或小于4GB的文件来说,dwMaximumSizeHigh的值将始终是0。

    使用64位的值,意味着Windows能够处理最大为16EB(1018字节)的文件,如果想要创建一个文件映射对象,使它能够反映文件当前的大小,那么可以为上面两个参数传递0,如果只打算读取该文件或者访问文件而不改变它的大小,那么为这两个参数传递0,如果打算将数据附加给该文件,可以选择最大的文件大小,以便为你留出一些富裕的空间,如果当前磁盘上的文件包含0字节,那么可以给CreateFileMapping函数的dwMaximumSizeHigh和dwMaximumSizeLow传递两个0,这样做就可以告诉系统,你要的文件映射对象里面的存储器为0字节,这是个错误,CreateFileMapping将返回NULL。

    如果你对我们讲述的内容一直非常关注,你一定认为这里存在严重的问题,Windows支持最大为16EB的文件和文件映射对象,这当然很好,但是,怎样将这样大的文件映射到32位进程的地址空间(32位地址空间是4GB文件的上限)中去呢,下一节介绍解决这个问题的办法,当然,64位进程拥有16EB的地址空间,因此可以进行更大的文件的映射操作,但是,如果文件是个超大规模的文件,仍然会遇到类似的问题。

    若要真正理解CreateFile和CreateFileMapping两个函数是如何运行的,建议你做一个下面的实验,建立下面的代码,对它进行编译,然后在一个调试程序中运行它,当你一步步执行每个语句时,你会跳到一个命令解释程序,并执行C:\目录上的“dir”命令,当执行调试程序中的每个语句时,请注意目录中出现的变化。

    01. int WINAPI _tWinMain(HINSTANCE hinstExe, HINSTANCE,
    02. PTSTR pszCmdLine, int nCmdShow)
    03. {
    04. //Before executing the line below, C:\ does not have
    05. //a file called "MMFTest.Dat."
    06. HANDLE hfile = CreateFile("C:\\MMFTest.dat",
    07. GENERIC_READ | GENERIC_WRITE,
    08. FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, CREATE_ALWAYS,
    09. FILE_ATTRIBUTE_NORMAL, NULL);
    10.  
    11. //Before executing the line below, the MMFTest.Dat
    12. //file does exist but has a file size of 0 bytes.
    13. HANDLE hfilemap = CreateFileMapping(hfile, NULL, PAGE_READWRITE,
    14. 0, 100, NULL);
    15.  
    16. //After executing the line above, the MMFTest.Dat
    17. //file has a size of 100 bytes.
    18.  
    19. //Cleanup
    20. CloseHandle(hfilemap);
    21. CloseHandle(hfile);
    22.  
    23. //When the process terminates, MMFTest.Dat remains
    24. //on the disk with a size of 100 bytes.
    25. return(0);
    26. }

    如果调用CreateFileMapping函数,传递PAGE_READWRITE标志,那么系统将设法确保磁盘上的相关数据文件的大小至少与dwMaximumSizeHigh和dwMaximumSizeLow参数中设定的大小相同,如果该文件小于设定的大小,CreateFileMapping函数将扩展该文件的大小,使磁盘上的文件变大,这种扩展是必要的,这样,当以后将该文件作为内存映射文件使用时,物理存储器就已经存在了,如果正在用PAGE_READONLY或PAGE_WRITECOPY标志创建该文件映射对象,那么CreateFileMapping特定的文件大小不得大于磁盘文件的物理大小,这是因为你无法将任何数据附加给该文件。

    CreateFileMapping函数的最后一个参数是pszName,它是个以0结尾的字符串,用于给该文件映射对象赋予一个名字,该名字用于与其他进程共享文件映射对象(本章后面展示了它的一个例子,第3章详细介绍了内核对象的共享操作),内存映射数据文件通常并不需要被共享,因此这个参数通常是NULL。

    系统创建文件映射对象,并将用于标识该对象的句柄返回该调用线程,如果系统无法创建文件映射对象,便返回一个NULL句柄值,记住,当CreateFile运行失败时,它将返回INVALID_HANDLE_VALUE(定义为-1),当CreateFileMapping运行失败时,它返回NULL,请不要混淆这些错误值。

    在本实例中创建文件映射内核对象代码如下:

    01. HANDLE hFileMapping = CreateFileMapping(hFile, NULL,
    02. PAGE_READONLY, 0, 0, NULL);
    03. DWORD dwFileSizeHigh;
    04. __int64 qwFileSize = GetFileSize(hFile, &dwFileSizeHigh);
    05. qwFileSize += (((__int64) dwFileSizeHigh) << 32);
    06. char AddMsg[]="Girl,you love me?, I love you very much!";       
    07. //加入的文件内容
    08.  
    09. __int64 myFilesize=qwFileSize+sinf.dwAllocationGranularity;           
    10. //合并后的文件大小
    11. HANDLE hmyfilemap = CreateFileMapping(hmyfile, NULL, PAGE_READWRITE,  
    12. //合并文件大小的内存映射对象
    13. (DWORD)(myFilesize>>32), (DWORD)(myFilesize& 0xFFFFFFFF), NULL);

    三、将文件数据映射到地址空间

    当创建了一个文件映射对象后,仍然必须让系统为文件的数据保留一个地址空间区域,并将文件的数据作为映射到该区域的物理存储器进行提交,可以通过调用MapViewOfFile函数来进行这项操作:

    1. PVOID MapViewOfFile(
    2. HANDLE hFileMappingObject,
    3. DWORD dwDesiredAccess,
    4. DWORD dwFileOffsetHigh,
    5. DWORD dwFileOffsetLow,
    6. SIZE_T dwNumberOfBytesToMap);

    参数hFileMappingObject用于标识文件映射对象的句柄,该句柄是前面调用CreateFileMapping或OpenFileMapping(本章后面介绍)函数返回的,参数dwDesiredAccess用于标识如何访问该数据,不错,必须再次设定如何访问文件的数据,可以设定下表所列的4个值中的一个。

    表17-6值及其含义

    含义
    FILE_MAP_WRITE可以读取和写入文件数据,CreateFileMapping函数必须通过传递PAGE_READWRITE标志来调用
    FILE_MAP_READ可以读取文件数据,CreateFileMapping函数可以通过传递下列任何一个保护属性来调用:PAGE_READONLY,PAGE_READWRITE或PAGE_WRITECOPY。
    FILE_MAP_ALL_ACCESS与FILE_MAP_WRITE相同
    FILE_MAP_COPY可以读取和写入文件数据,如果写入文件数据,可以创建一个页面的私有拷贝,在Windows2000中,CreateFileMapping函数可以用PAGE_READONLY,PAGE_READWRITE或PAGE_WRITECOPY等保护属性中的任何一个来调用,在Windows98中,CreateFileMapping必须用PAGE_WRITECOPY来调用。

    Windows要求所有这些保护属性一次又一次地重复设置,这当然有些奇怪和烦人,我认为这样做可以使应用程序更多地对数据保护属性进行控制,剩下的3个参数与保留地址空间区域及将物理存储器映射到该区域有关,当你将一个文件映射到你的进程的地址空间中时,你不必一次性地映射整个文件,相反,可以只将文件的一小部分映射到地址空间,被映射到进程的地址空间的这部分文件称为一个视图,这可以说明MapViewOfFile是如何而得名的,当将一个文件视图映射到进程的地址空间中时,必须规定两件事情,首先,必须告诉系统,数据文件中的哪个字节应该作为视图中的第一个字节来映射,你可以使用dwFileOffsetHigh和dwFileOffsetLow参数来进行这项操作,由于Windows支持的文件最大可达16EB,因此必须用一个64位的值来设定这个字节的位移值,这个64位值中,较高的32位传递给参数dwFileOffsetHigh,较低的32位传递给参数dwFileOffsetLow,注意,文件中的这个位移值必须是系统的分配粒度的倍数(迄今为止,Windows的所有实现代码的分配粒度均为64KB),第14章介绍了如何获取某个系统的分配粒度。

    第二,必须告诉系统,数据文件有多少字节要映射到地址空间,这与设定要保留多大的地址空间区域的情况是相同的,可以使用dwNumberOfBytesToMap参数来设定这个值,如果设定的值是0,那么系统将设法把从文件中的指定位移开始到整个文件的结尾的视图映射到地址空间。

    在Windows98中,如果MapViewOfFile无法找到足够大的区域来存放整个文件映射对象,那么无论需要的视图是多大,MapViewOfFile均将返回NULL。

    在Windows2000中,MapViewOfFile只需要为必要的视图找到足够大的一个区域,而不管整个文件映射对象是多大。

    如果在调用MapViewOfFile函数时设定了FILE_MAP_COPY标志,系统就会从系统的页文件中提交物理存储器,提交的地址空间数量由dwNumberOfBytesToMap参数决定,只要你不进行其他操作,只是从文件的映像视图中读取数据,那么系统将决不会使用页文件中的这些提交的页面,但是,如果进程中的任何线程将数据写入文件的映像视图中的任何内存地址,那么系统将从页文件中抓取已提交页面中的一个页面,将原始数据页面拷贝到该页交换文件中,然后将该拷贝的页面映射到你的进程的地址空间,从这时起,你的进程中的线程就要访问数据的本地拷贝,不能读取或修改原始数据。

    当系统制作原始页面的拷贝时,系统将把页面的保护属性从PAGE_WRITECOPY改为PAGE_READWRITE,下面这个代码段就说明了这个情况:

    01. // Open the file that we want to map.
    02. HANDLE hFile = CreateFile(pszFileName, GENERIC_READ | GENERIC_WRITE, 0, NULL,
    03. OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
    04.  
    05. // Create a file-mapping object for the file.
    06. HANDLE hFileMapping = CreateFileMapping(hFile, NULL, PAGE_WRITECOPY,
    07. 0, 0, NULL);
    08.  
    09. // Map a copy-on-write view of the file; the system will commit
    10. // enough physical storage from the paging file to accommodate
    11. // the entire file. All pages in the view will initially have
    12. // PAGE_WRITECOPY access.
    13.  
    14. PBYTE pbFile = (PBYTE) MapViewOfFile(hFileMapping, FILE_MAP_COPY,
    15. 0, 0, 0);
    16.  
    17. // Read a byte from the mapped view.
    18. BYTE bSomeByte = pbFile[0];
    19.  
    20. // When reading, the system does not touch the committed pages in
    21. // the paging file. The page keeps its PAGE_WRITECOPY attribute.
    22.  
    23. // Write a byte to the mapped view.
    24. pbFile[0] = 0;
    25.  
    26. // When writing for the first time, the system grabs a committed
    27. // page from the paging file, copies the original contents of the
    28. // page at the accessed memory address, and maps the new page
    29. // (the copy) into the process's address space. The new page has
    30. // an attribute of PAGE_READWRITE.
    31.  
    32. // Write another byte to the mapped view.
    33. pbFile[1] = 0;
    34.  
    35. // Because this byte is now in a PAGE_READWRITE page, the system
    36. // simply writes the byte to the page (backed by the paging file).
    37.  
    38. // When finished using the file's mapped view, unmap it.
    39. // UnmapViewOfFile is discussed in the next section.
    40. UnmapViewOfFile(pbFile);
    41.  
    42. // The system decommits the physical storage from the paging file.
    43. // Any writes to the pages are lost.
    44.  
    45. // Clean up after ourselves.
    46. CloseHandle(hFileMapping);
    47. CloseHandle(hFile);

    Windows98前面讲过,Windows98必须预先为内存映射文件提交页文件中的存储器,然而,它只有在必要时才将修改后的页面写入页文件,

    四、从进程的地址空间撤消文件数据的映射

    当不再需要保留映射到你的进程地址空间区域中的文件数据时,可以通过调用下面的函数将它释放:

    1. BOOLUnmapViewOfFile(PVOIDpvBaseAddress);

    该函数的唯一的参数pvBaseAddress用于设定返回区域的基地址,该值必须与调用MapViewOfFile函数返回的值相同,必须记住要调用UnmapViewOfFile函数,如果没有调用这个函数,那么在你的进程终止运行前,保留的区域就不会被释放,每当你调用MapViewOfFile时,系统总是在你的进程地址空间中保留一个新区域,而以前保留的所有区域将不被释放。

    为了提高速度,系统将文件的数据页面进行高速缓存,并且在对文件的映射视图进行操作时不立即更新文件的磁盘映像,如果需要确保你的更新被写入磁盘,可以强制系统将修改过的数据的一部分或全部重新写入磁盘映像中,方法是调用FlushViewOfFile函数:

    1. BOOLFlushViewOfFile(
    2. PVOIDpvAddress,
    3. SIZE_TdwNumberOfBytesToFlush);

    第一个参数是包含在内存映射文件中的视图的一个字节的地址,该函数将你在这里传递的地址圆整为一个页面边界值,第二个参数用于指明你想要刷新的字节数,系统将把这个数字向上圆整,使得字节总数是页面的整数,如果你调用FlushViewOfFile函数并且不修改任何数据,那么该函数只是返回,而不将任何信息写入磁盘。

    对于存储器是在网络上的内存映射文件来说,FlushViewOfFile能够保证文件的数据已经从工作站写入存储器,但是FlushViewOfFile不能保证正在共享文件的服务器已经将数据写入远程磁盘,因为服务器也许对文件的数据进行了高速缓存,若要保证服务器写入文件的数据,每当你为文件创建一个文件映射对象并且映射该文件映射对象的视图时,应该将FILE_FLAG_WRITE_THROUGH标志传递给CreateFile函数,如果你使用该标志打开该文件,那么只有当文件的全部数据已经存放在服务器的磁盘驱动器中的时候,FlushViewOfFile函数才返回。

    记住UnmapViewOfFile函数的一个特殊的特性,如果原先使用FILE_MAP_COPY标志来映射视图,那么你对文件的数据所作的任何修改,实际上是对存放在系统的页文件中的文件数据的拷贝所作的修改,在这种情况下,如果调用UnmapViewOfFile函数,该函数在磁盘文件上就没有什么可以更新,而只会释放页文件中的页面,从而导致数据丢失。

    如果想保留修改后的数据,必须采用别的措施,例如,你可以用同一个文件创建另一个文件映射对象(使用PAGE_READWRITE),然后使用FILE_MAP_WRITE标志将这个新文件映射对象映射到进程的地址空间,之后,你可以扫描第一个视图,寻找带有PAGE_READWRITE保护属性的页面,每当你找到一个带有该属性的页面时,可以查看它的内容,并且确定是否将修改了的数据写入该文件,如果不想用新数据更新该文件,那么继续对视图中的剩余页面进行扫描,直到视图的结尾,但是,如果你确实想要保存修改了的数据页面,那么只需要调用MoveMemory函数,将数据页面从第一个视图拷贝到第二个视图,由于第二个视图是用PAGE_READWRITE保护属性映射的,因此MoveMemory函数将更新磁盘上的实际文件内容,可以使用这种方法来确定文件的变更并保存你的文件的数据。

    Windows98不支持copy-on-write(写入时拷贝)保护属性,因此,当扫描内存映射文件的第一个视图时,无法测试用PAGE_READWRITE标志做上标记的页面,你必须设计一种方法来确定第一个视图中的哪些页面已经做了修改。

    五、关闭文件映射对象和文件对象

    不用说,你总是要关闭你打开了的内核对象,如果忘记关闭,在你的进程继续运行时会出现资源泄漏的问题,当然,当你的进程终止运行时,系统会自动关闭你的进程已经打开但是忘记关闭的任何对象,但是如果你的进程暂时没有终止运行,你将会积累许多资源句柄,因此你始终都应该编写清楚而又“正确的”代码,以便关闭你已经打开的任何对象,若要关闭文件映射对象和文件对象,只需要两次调用CloseHandle函数,每个句柄调用一次:

    让我们更加仔细地观察一下这个进程,下面的伪代码显示了一个内存映射文件的例子:

    01. HANDLEhFile=CreateFile(...);
    02. HANDLEhFileMapping=CreateFileMapping(hFile,...);
    03. PVOIDpvFile=MapViewOfFile(hFileMapping,...);
    04.  
    05. //Usethememory-mappedfile.
    06.  
    07. UnmapViewOfFile(pvFile);
    08. CloseHandle(hFileMapping);
    09. CloseHandle(hFile);

    上面的代码显示了对内存映射文件进行操作所用的“预期”方法,但是,它没有显示,当你调用MapViewOfFile时系统对文件对象和文件映射对象的使用计数的递增情况,这个副作用是很大的,因为它意味着我们可以将上面的代码段重新编写成下面的样子:

    01. HANDLEhFile=CreateFile(...);
    02. HANDLEhFileMapping=CreateFileMapping(hFile,...);
    03. CloseHandle(hFile);
    04. PVOIDpvFile=MapViewOfFile(hFileMapping,...);
    05. CloseHandle(hFileMapping);
    06.  
    07. //Usethememory-mappedfile.
    08.  
    09. UnmapViewOfFile(pvFile);

    当对内存映射文件进行操作时,通常要打开文件,创建文件映射对象,然后使用文件映射对象将文件的数据视图映射到进程的地址空间,由于系统递增了文件对象和文件映射对象的内部使用计数,因此可以在你的代码开始运行时关闭这些对象,以消除资源泄漏的可能性,

    如果用同一个文件来创建更多的文件映射对象,或者映射同一个文件映射对象的多个视图,那么就不能较早地调用CloseHandle函数——以后你可能还需要使用它们的句柄,以便分别对CreateFileMapping和MapViewOfFile函数进行更多的调用,

    本实例中第三到第六步代码如下:

    01. CloseHandle(hFile);//Wenolongerneedaccesstothefileobject'shandle.
    02. CloseHandle(hmyfile);
    03.  
    04. PBYTEpbmyFile=(PBYTE)MapViewOfFile(hmyfilemap,FILE_MAP_WRITE,//内存映射视图
    05. 0,//Startingbyte
    06. 0,//infile
    07. sizeof(AddMsg));
    08. memcpy(pbmyFile,AddMsg,sizeof(AddMsg));//加入内容
    09. UnmapViewOfFile(pbmyFile);
    10.  
    11. __int64qwFileOffset=0;//A文件视图的偏移量
    12. __int64qwmyFileOffset=sinf.dwAllocationGranularity;//合并文件视图的遍移量
    13.  
    14. while(qwFileSize>0)
    15. {
    16. //Determinethenumberofbytestobemappedinthisview
    17. DWORDdwBytesInBlock=sinf.dwAllocationGranularity;
    18.  
    19. if(qwFileSize>32),//Startingbyte
    20. (DWORD)(qwFileOffset&0xFFFFFFFF),//infile
    21. dwBytesInBlock);//#ofbytestomap
    22.  
    23. PBYTEpbmyFile=(PBYTE)MapViewOfFile(hmyfilemap,FILE_MAP_WRITE,
    24. (DWORD)(qwmyFileOffset>>32),//Startingbyte
    25. (DWORD)(qwmyFileOffset&0xFFFFFFFF),//infile
    26. dwBytesInBlock);
    27.  
    28. memcpy(pbmyFile,pbFile,dwBytesInBlock);
    29.  
    30.  
    31. //Unmaptheview;wedon'twantmultipleviews
    32. //inouraddressspace.
    33. UnmapViewOfFile(pbFile);
    34. UnmapViewOfFile(pbmyFile);
    35.  
    36. //Skiptothenextsetofbytesinthefile.
    37. qwmyFileOffset+=dwBytesInBlock;
    38. qwFileOffset+=dwBytesInBlock;
    39. qwFileSize-=dwBytesInBlock;
    40. }
    41.  
    42. CloseHandle(hFileMapping);
    43. CloseHandle(hmyfilemap);

    参考资料:《Windows核心编程》



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值