本文介绍利用内存映射文件修改大文件:在大文件内存前加入一段数据,若要使用内存映射文件,必须执行下列操作步骤:
创建或打开一个文件内核对象,该对象用于标识磁盘上你想用作内存映射文件的文件;
创建一个文件映射内核对象,告诉系统该文件的大小和你打算如何访问该文件;
让系统将文件映射对象的全部或一部分映射到你的进程地址空间中;
当完成对内存映射文件的使用时,必须执行下面这些步骤将它清除:
告诉系统从你的进程的地址空间中撤消文件映射内核对象的映像;
关闭文件映射内核对象;
关闭文件内核对象;
HANDLE CreateFile( PCSTR pszFileName,//用于指明要创建或打开的文件的名字 DWORD dwDesiredAccess,//用于设定如何访问该文件的内容 DWORD dwShareMode,//如何共享该文件 PSECURITY_ATTRIBUTES psa,//指向安全属性的指针 DWORD dwCreationDisposition,//如何创建 DWORD dwFlagsAndAttributes,//文件属性 HANDLE hTemplateFile);//用于复制文件句柄
值 | 含义 |
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 | 打开文件的其他尝试将会取得成功 |
HANDLE CreateFileMapping( HANDLE hFile,//想要映射到进程地址空间中的文件句柄,该句柄由前面调用的CreateFile函数返回 PSECURITY_ATTRIBUTES psa,//指向安全属性的指针 DWORD fdwProtect,//保护设置 DWORD dwMaximumSizeHigh,//高位文件大小 DWORD dwMaximumSizeLow,//低位文件大小 PCTSTR pszName//共享内存名称 );第一个参数hFile用于标识你想要映射到进程地址空间中的文件句柄,该句柄由前面调用的CreateFile函数返回,任何可以获得的物理文件句柄,如果你需要创建一个物理文件无关的内存映射也无妨,将它设置成为0xFFFFFFFF(INVALID_HANDLE_VALUE)就可以了。如果需要和物理文件关联,要确保你的物理文件创建的时候的访问模式和"保护设置"匹配,比如:物理文件只读,内存映射需要读写就会发生错误。推荐你的物理文件使用独占方式创建。如果使用 INVALID_HANDLE_VALUE, 也需要设置需要申请的内存空间的大小,无论物理文件句柄参数是否有效,这样CreateFileMapping 就可以创建一个和物理文件大小无关的内存空间给你,甚至超过实际文件大小,如果你的物理文件有效,而大小参数为0,则返回给你的是一个和物理文件大小一样的内存空间地址范围。返回给你的文件映射地址空间是可以通过复制,集成或者命名得到,初始内容为0。当高地位表示的这个值大于实际的文件大小时,系统会扩大文件到这个值,因为系统需要保证进程空间能完全被映射。值为0默认为文件的大小,这时候如果文件大小为0,创建失败。
psa参数是指向文件映射内核对象的SECURITY_ATTRIBUTES结构的指针,通常传递的值是NULL(它提供默认的安全特性,返回的句柄是不能继承的)。
本章开头讲过,创建内存映射文件就像保留一个地址空间区域然后将物理存储器提交给该区域一样,因为内存映射文件的物理存储器来自磁盘上的一个文件,而不是来自从系统的页文件中分配的空间,当创建一个文件映射对象时,系统并不为它保留地址空间区域,也不将文件的存储器映射到该区域(下一节将介绍如何进行这项操作),但是,当系统将存储器映射到进程的地址空间中去时,系统必须知道应该将什么保护属性赋予物理存储器的页面,CreateFileMapping函数的fdwProtect参数使你能够设定这些保护属性,大多数情况下,可以设定下表中列出的3个保护属性之一。
保护属性 | 含义 |
PAGE_READONLY | 当文件映射对象被映射时,可以读取文件的数据,必须已经将GENERIC_READ传递给CreateFile函数。 |
PAGE_READWRITE | 当文件映射对象被映射时,可以读取和写入文件的数据,必须已经将GENERIC_READ|GENERIC_WRITE传递给CreateFile。 |
PAGE_WRITECOPY | 当文件映射对象被映射时,可以读取和写入文件的数据,如果写入数据,会导致页面的私有拷贝得以创建,必须已经将GENERIC_READ或GENERIC_WRITE传递给CreateFile。 |
int WINAPI _tWinMain(HINSTANCE hinstExe, HINSTANCE, PTSTR pszCmdLine, int nCmdShow) { //Before executing the line below, C:/ does not have //a file called "MMFTest.Dat." HANDLE hfile = CreateFile("C://MMFTest.dat", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); //Before executing the line below, the MMFTest.Dat //file does exist but has a file size of 0 bytes. HANDLE hfilemap = CreateFileMapping(hfile, NULL, PAGE_READWRITE, 0, 100, NULL); //After executing the line above, the MMFTest.Dat //file has a size of 100 bytes. //Cleanup CloseHandle(hfilemap); CloseHandle(hfile); //When the process terminates, MMFTest.Dat remains //on the disk with a size of 100 bytes. return(0); }如果调用CreateFileMapping函数,传递PAGE_READWRITE标志,那么系统将设法确保磁盘上的相关数据文件的大小至少与dwMaximumSizeHigh和dwMaximumSizeLow参数中设定的大小相同,如果该文件小于设定的大小,CreateFileMapping函数将扩展该文件的大小,使磁盘上的文件变大,这种扩展是必要的,这样,当以后将该文件作为内存映射文件使用时,物理存储器就已经存在了,如果正在用PAGE_READONLY或PAGE_WRITECOPY标志创建该文件映射对象,那么CreateFileMapping特定的文件大小不得大于磁盘文件的物理大小,这是因为你无法将任何数据附加给该文件。
CreateFileMapping函数的最后一个参数是pszName,它是个以0结尾的字符串,用于给该文件映射对象赋予一个名字,该名字用于与其他进程共享文件映射对象(本章后面展示了它的一个例子,第3章详细介绍了内核对象的共享操作),内存映射数据文件通常并不需要被共享,因此这个参数通常是NULL。系统创建文件映射对象,并将用于标识该对象的句柄返回该调用线程,如果系统无法创建文件映射对象,便返回一个NULL句柄值,记住,当CreateFile运行失败时,它将返回INVALID_HANDLE_VALUE(定义为-1),当CreateFileMapping运行失败时,它返回NULL,请不要混淆这些错误值。 在本实例中创建文件映射内核对象代码如下:HANDLE hFileMapping = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL); DWORD dwFileSizeHigh; __int64 qwFileSize = GetFileSize(hFile, &dwFileSizeHigh); qwFileSize += (((__int64) dwFileSizeHigh) << 32); char AddMsg[]="Girl,you love me?, I love you very much!"; //加入的文件内容 __int64 myFilesize=qwFileSize+sinf.dwAllocationGranularity; //合并后的文件大小 HANDLE hmyfilemap = CreateFileMapping(hmyfile, NULL, PAGE_READWRITE, //合并文件大小的内存映射对象 (DWORD)(myFilesize>>32), (DWORD)(myFilesize& 0xFFFFFFFF), NULL);三、将文件数据映射到地址空间
当创建了一个文件映射对象后,仍然必须让系统为文件的数据保留一个地址空间区域,并将文件的数据作为映射到该区域的物理存储器进行提交,可以通过调用MapViewOfFile函数来进行这项操作:PVOID MapViewOfFile( HANDLE hFileMappingObject, DWORD dwDesiredAccess, DWORD dwFileOffsetHigh, DWORD dwFileOffsetLow, SIZE_T dwNumberOfBytesToMap, );参数hFileMappingObject用于标识文件映射对象的句柄,该句柄是前面调用CreateFileMapping或OpenFileMapping(本章后面介绍)函数返回的,参数dwDesiredAccess用于标识如何访问该数据,不错,必须再次设定如何访问文件的数据,可以设定下表所列的4个值中的一个。
值 | 含义 |
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来调用。 |
// Open the file that we want to map. HANDLE hFile = CreateFile(pszFileName, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); // Create a file-mapping object for the file. HANDLE hFileMapping = CreateFileMapping(hFile, NULL, PAGE_WRITECOPY, 0, 0, NULL); // Map a copy-on-write view of the file; the system will commit // enough physical storage from the paging file to accommodate // the entire file. All pages in the view will initially have // PAGE_WRITECOPY access. PBYTE pbFile = (PBYTE) MapViewOfFile(hFileMapping, FILE_MAP_COPY, 0, 0, 0); // Read a byte from the mapped view. BYTE bSomeByte = pbFile[0]; // When reading, the system does not touch the committed pages in // the paging file. The page keeps its PAGE_WRITECOPY attribute. // Write a byte to the mapped view. pbFile[0] = 0; // When writing for the first time, the system grabs a committed // page from the paging file, copies the original contents of the // page at the accessed memory address, and maps the new page // (the copy) into the process's address space. The new page has // an attribute of PAGE_READWRITE. // Write another byte to the mapped view. pbFile[1] = 0; // Because this byte is now in a PAGE_READWRITE page, the system // simply writes the byte to the page (backed by the paging file). // When finished using the file's mapped view, unmap it. // UnmapViewOfFile is discussed in the next section. UnmapViewOfFile(pbFile); // The system decommits the physical storage from the paging file. // Any writes to the pages are lost. // Clean up after ourselves. CloseHandle(hFileMapping); CloseHandle(hFile);四、从进程的地址空间撤消文件数据的映射
当不再需要保留映射到你的进程地址空间区域中的文件数据时,可以通过调用下面的函数将它释放:BOOLUnmapViewOfFile(PVOIDpvBaseAddress);
该函数的唯一的参数pvBaseAddress用于设定返回区域的基地址,该值必须与调用MapViewOfFile函数返回的值相同,必须记住要调用UnmapViewOfFile函数,如果没有调用这个函数,那么在你的进程终止运行前,保留的区域就不会被释放,每当你调用MapViewOfFile时,系统总是在你的进程地址空间中保留一个新区域,而以前保留的所有区域将不被释放。五、关闭文件映射对象和文件对象
不用说,你总是要关闭你打开了的内核对象,如果忘记关闭,在你的进程继续运行时会出现资源泄漏的问题,当然,当你的进程终止运行时,系统会自动关闭你的进程已经打开但是忘记关闭的任何对象,但是如果你的进程暂时没有终止运行,你将会积累许多资源句柄,因此你始终都应该编写清楚而又“正确的”代码,以便关闭你已经打开的任何对象,若要关闭文件映射对象和文件对象,只需要两次调用CloseHandle函数,每个句柄调用一次:
让我们更加仔细地观察一下这个进程,下面的伪代码显示了一个内存映射文件的例子:HANDLEhFile=CreateFile(...); HANDLEhFileMapping=CreateFileMapping(hFile,...); PVOIDpvFile=MapViewOfFile(hFileMapping,...); //Usethememory-mappedfile. UnmapViewOfFile(pvFile); CloseHandle(hFileMapping); CloseHandle(hFile);上面的代码显示了对内存映射文件进行操作所用的“预期”方法,但是,它没有显示,当你调用MapViewOfFile时系统对文件对象和文件映射对象的使用计数的递增情况,这个副作用是很大的,因为它意味着我们可以将上面的代码段重新编写成下面的样子:
HANDLEhFile=CreateFile(...); HANDLEhFileMapping=CreateFileMapping(hFile,...); CloseHandle(hFile); PVOIDpvFile=MapViewOfFile(hFileMapping,...); CloseHandle(hFileMapping); //Usethememory-mappedfile. UnmapViewOfFile(pvFile);当对内存映射文件进行操作时,通常要打开文件,创建文件映射对象,然后使用文件映射对象将文件的数据视图映射到进程的地址空间,由于系统递增了文件对象和文件映射对象的内部使用计数,因此可以在你的代码开始运行时关闭这些对象,以消除资源泄漏的可能性,如果用同一个文件来创建更多的文件映射对象,或者映射同一个文件映射对象的多个视图,那么就不能较早地调用CloseHandle函数——以后你可能还需要使用它们的句柄,以便分别对CreateFileMapping和MapViewOfFile函数进行更多的调用,本实例中第三到第六步代码如下:CloseHandle(hFile);//Wenolongerneedaccesstothefileobject'shandle. CloseHandle(hmyfile); PBYTEpbmyFile=(PBYTE)MapViewOfFile(hmyfilemap,FILE_MAP_WRITE,//内存映射视图 0,//Startingbyte 0,//infile sizeof(AddMsg)); memcpy(pbmyFile,AddMsg,sizeof(AddMsg));//加入内容 UnmapViewOfFile(pbmyFile); __int64qwFileOffset=0;//A文件视图的偏移量 __int64qwmyFileOffset=sinf.dwAllocationGranularity;//合并文件视图的遍移量 while(qwFileSize>0) { //Determinethenumberofbytestobemappedinthisview DWORDdwBytesInBlock=sinf.dwAllocationGranularity; if(qwFileSize<sinf.dwAllocationGranularity)//文件小于系统分配粒度 dwBytesInBlock=(DWORD)qwFileSize;//偏移量为文件大小 PBYTEpbFile=(PBYTE)MapViewOfFile(hFileMapping,FILE_MAP_READ, (DWORD)(qwFileOffset>>32),//Startingbyte (DWORD)(qwFileOffset&0xFFFFFFFF),//infile dwBytesInBlock);//#ofbytestomap PBYTEpbmyFile=(PBYTE)MapViewOfFile(hmyfilemap,FILE_MAP_WRITE, (DWORD)(qwmyFileOffset>>32),//Startingbyte (DWORD)(qwmyFileOffset&0xFFFFFFFF),//infile dwBytesInBlock); memcpy(pbmyFile,pbFile,dwBytesInBlock); //Unmaptheview;wedon'twantmultipleviews //inouraddressspace. UnmapViewOfFile(pbFile); UnmapViewOfFile(pbmyFile); //Skiptothenextsetofbytesinthefile. qwmyFileOffset+=dwBytesInBlock; qwFileOffset+=dwBytesInBlock; qwFileSize-=dwBytesInBlock; } CloseHandle(hFileMapping); CloseHandle(hmyfilemap);续:
在软件开发过程中,也经常碰到进程间共享数据的需求,比如A进程创建计算数据,B进程进行显示数据的图形。这样的开发方式可以把一个大程序开发成独立的小程序,提高软件的成功率,也可以更加适合团队一起开发,加快软件开发速度。下面就来使用文件映射的方式进行共享数据:先要使用函数CreateFileMapping来创建一个想共享的文件数据句柄,然后使用MapViewOfFile来获取共享的内存地址,然后使用OpenFileMapping函数在另一个进程里打开共享文件的名称,这样就可以实现不同的进程共享数据了。
调用函数的例子如下://文件共享。 //蔡军生 2007/10/27 QQ:9073204 深圳 void FileMapping(void) { //打开共享的文件对象。 m_hMapFile = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE,_T("TestFileMap")); if (m_hMapFile) { //显示共享的文件数据。 LPTSTR lpMapAddr = (LPTSTR)MapViewOfFile(m_hMapFile,FILE_MAP_ALL_ACCESS, 0,0,0); OutputDebugString(lpMapAddr); } else { //创建共享文件。 m_hMapFile = CreateFileMapping( (HANDLE)0xFFFFFFFF,NULL, PAGE_READWRITE,0,1024,_T("TestFileMap")); //拷贝数据到共享文件里。 LPTSTR lpMapAddr = (LPTSTR)MapViewOfFile(m_hMapFile,FILE_MAP_ALL_ACCESS, 0,0,0); std::wstring strTest(_T("TestFileMap")); wcscpy(lpMapAddr,strTest.c_str()); FlushViewOfFile(lpMapAddr,strTest.length()+1); } }我做了格式的调整。
原址:http://blog.csdn.net/go_michael/article/details/6127174
这个链接跟大文件处理相关,也作收藏:http://www.yesky.com/20030117/1649013.shtml