http://www.vckbase.com/document/viewdoc/?id=1831
下載源代碼
本文介紹利用內(nèi)存映射文件修改大文件:在大文件內(nèi)存前加入一段數(shù)據(jù),若要使用內(nèi)存映射文件,必須執(zhí)行下列操作步驟:
- 創(chuàng)建或打開一個文件內(nèi)核對象,該對象用于標識磁盤上你想用作內(nèi)存映射文件的文件;
- 創(chuàng)建一個文件映射內(nèi)核對象,告訴系統(tǒng)該文件的大小和你打算如何訪問該文件;
- 讓系統(tǒng)將文件映射對象的全部或一部分映射到你的進程地址空間中;
當完成對內(nèi)存映射文件的使用時,必須執(zhí)行下面這些步驟將它清除:
- 告訴系統(tǒng)從你的進程的地址空間中撤消文件映射內(nèi)核對象的映像;
- 關閉文件映射內(nèi)核對象;
- 關閉文件內(nèi)核對象;
下面將用一個實例詳細介紹這些操作步驟,(本實例的目的就是將一個文件A其內(nèi)容前面加入一些內(nèi)容存入文件B,我想大家在程序開發(fā)當中會遇到這種情況的)。
一、我們打開關于A文件內(nèi)核對象,并創(chuàng)建一個關于B文件的內(nèi)核對象
若要創(chuàng)建或打開一個文件內(nèi)核對象,總是要調(diào)用CreateFile函數(shù):
HANDLE CreateFile(
PCSTR pszFileName,
DWORD dwDesiredAccess,
DWORD dwShareMode,
PSECURITY_ATTRIBUTES psa,
DWORD dwCreationDisposition,
DWORD dwFlagsAndAttributes,
HANDLE hTemplateFile);
CreateFile函數(shù)擁有好幾個參數(shù),這里只重點介紹前3個參數(shù),即pszFileName,dwDesiredAccess和dwShareMode。
你可能會猜到,第一個參數(shù)pszFileName用于指明要創(chuàng)建或打開的文件的名字(包括一個選項路徑),第二個參數(shù)dwDesiredAccess用于設定如何訪問該文件的內(nèi)容,可以設定下表所列的4個值中的一個。
值 |
含義 |
0 |
不能讀取或?qū)懭胛募膬?nèi)容,當只想獲得文件的屬性時,請設定0 |
GENERIC_READ |
可以從文件中讀取數(shù)據(jù) |
GENERIC_WRITE |
可以將數(shù)據(jù)寫入文件 |
GENERIC_READ|GENERIC_WRITE |
可以從文件中讀取數(shù)據(jù),也可以將數(shù)據(jù)寫入文件 |
當創(chuàng)建或打開一個文件,將它作為一個內(nèi)存映射文件來使用時,請選定最有意義的一個或多個訪問標志,以說明你打算如何訪問文件的數(shù)據(jù),對內(nèi)存映射文件來說,必須打開用于只讀訪問或讀寫訪問的文件,因此,可以分別設定GENERIC_READ或GENERIC_READ|GENERIC_WRITE,
第三個參數(shù)dwShareMode告訴系統(tǒng)你想如何共享該文件,可以為dwShareMode設定下表所列的4個值之一:
值 |
含義 |
0 |
打開文件的任何嘗試均將失敗 |
FILE_SHARE_READ |
使用GENERIC_WRITE打開文件的其他嘗試將會失敗 |
FILE_SHARE_WRITE |
使用GENERIC_READ打開文件的其他嘗試將會失敗 |
FILE_SHARE_READFILE_SHARE_WRITE |
打開文件的其他嘗試將會取得成功 |
如果CreateFile函數(shù)成功地創(chuàng)建或打開指定的文件,便返回一個文件內(nèi)核對象的句柄,否則返回INVALID_HANDLE_VALUE,
注意能夠返回句柄的大多數(shù)Windows函數(shù)如果運行失敗,那么就會返回NULL,但是,CreateFile函數(shù)將返回INVALID_HANDLE_VALUE,它定義為((HANDLE)-1),
HANDLEhFile=CreateFile(".\\first.txt",GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_FLAG_SEQUENTIAL_SCAN,NULL);
HANDLEhmyfile=CreateFile("E:\\my.txt",GENERIC_READ|GENERIC_WRITE,0,NULL,OPEN_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);
二、我們要分別創(chuàng)建兩個文件映射內(nèi)核對象
調(diào)用CreateFile函數(shù),就可以將文件映像的物理存儲器的位置告訴操作系統(tǒng),你傳遞的路徑名用于指明支持文件映像的物理存儲器在磁盤(或網(wǎng)絡或光盤)上的確切位置,這時,必須告訴系統(tǒng),文件映射對象需要多少物理存儲器,若要進行這項操作,可以調(diào)用CreateFileMapping函數(shù):
HANDLE CreateFileMapping(
HANDLE hFile,
PSECURITY_ATTRIBUTES psa,
DWORD fdwProtect,
DWORD dwMaximumSizeHigh,
DWORD dwMaximumSizeLow,
PCTSTR pszName);
第一個參數(shù)hFile用于標識你想要映射到進程地址空間中的文件句柄,該句柄由前面調(diào)用的CreateFile函數(shù)返回,psa參數(shù)是指向文件映射內(nèi)核對象的SECURITY_ATTRIBUTES結構的指針,通常傳遞的值是NULL(它提供默認的安全特性,返回的句柄是不能繼承的)。
本章開頭講過,創(chuàng)建內(nèi)存映射文件就像保留一個地址空間區(qū)域然后將物理存儲器提交給該區(qū)域一樣,因為內(nèi)存映射文件的物理存儲器來自磁盤上的一個文件,而不是來自從系統(tǒng)的頁文件中分配的空間,當創(chuàng)建一個文件映射對象時,系統(tǒng)并不為它保留地址空間區(qū)域,也不將文件的存儲器映射到該區(qū)域(下一節(jié)將介紹如何進行這項操作),但是,當系統(tǒng)將存儲器映射到進程的地址空間中去時,系統(tǒng)必須知道應該將什么保護屬性賦予物理存儲器的頁面,CreateFileMapping函數(shù)的fdwProtect參數(shù)使你能夠設定這些保護屬性,大多數(shù)情況下,可以設定下表中列出的3個保護屬性之一。
使用fdwProtect參數(shù)設定的部分保護屬性
保護屬性 |
含義 |
PAGE_READONLY |
當文件映射對象被映射時,可以讀取文件的數(shù)據(jù),必須已經(jīng)將GENERIC_READ傳遞給CreateFile函數(shù)。 |
PAGE_READWRITE |
當文件映射對象被映射時,可以讀取和寫入文件的數(shù)據(jù),必須已經(jīng)將GENERIC_READ|GENERIC_WRITE傳遞給CreateFile。 |
PAGE_WRITECOPY |
當文件映射對象被映射時,可以讀取和寫入文件的數(shù)據(jù),如果寫入數(shù)據(jù),會導致頁面的私有拷貝得以創(chuàng)建,必須已經(jīng)將GENERIC_READ或GENERIC_WRITE傳遞給CreateFile。 |
在Windows98下,可以將PAGE_WRITECOPY標志傳遞給CreateFileMapping,這將告訴系統(tǒng)從頁文件中提交存儲器,該頁文件存儲器是為數(shù)據(jù)文件的數(shù)據(jù)拷貝保留的,只有修改過的頁面才被寫入頁文件,你對該文件的數(shù)據(jù)所作的任何修改都不會重新填入原始數(shù)據(jù)文件,其最終結果是,PAGE_WRITECOPY標志的作用在Windows2000和Windows98上是相同的。
除了上面的頁面保護屬性外,還有4個節(jié)保護屬性,你可以用OR將它們連接起來放入CreateFileMapping函數(shù)的fdwProtect參數(shù)中,節(jié)只是用于內(nèi)存映射的另一個術語。
節(jié)的第一個保護屬性是SEC_NOCACHE,它告訴系統(tǒng),沒有將文件的任何內(nèi)存映射頁面放入高速緩存,因此,當將數(shù)據(jù)寫入該文件時,系統(tǒng)將更加經(jīng)常地更新磁盤上的文件數(shù)據(jù),這個標志與PAGE_NOCACHE保護屬性標志一樣,是供設備驅(qū)動程序開發(fā)人員使用的,應用程序通常不使用,
Windows98將忽略SEC_NOCACHE標志。
節(jié)的第二個保護屬性是SEC_IMAGE,它告訴系統(tǒng),你映射的文件是個可移植的可執(zhí)行(PE)文件映像,當系統(tǒng)將該文件映射到你的進程的地址空間中時,系統(tǒng)要查看文件的內(nèi)容,以確定將哪些保護屬性賦予文件映像的各個頁面,例如,PE文件的代碼節(jié)(.text)通常用PAGE_EXECUTE_READ屬性進行映射,而PE文件的數(shù)據(jù)節(jié)(.data)則通常用PAGE_READWRITE屬性進行映射,如果設定的屬性是SEC_IMAGE,則告訴系統(tǒng)進行文件映像的映射,并設置相應的頁面保護屬性。
Windows98將忽略SEC_IMAGE標志
最后兩個保護屬性是SEC_RESERVE和SEC_COMMIT,它們是兩個互斥屬性,當使用內(nèi)存映射數(shù)據(jù)文件時,它們不能使用,這兩個標志將在本章后面介紹,當創(chuàng)建內(nèi)存映射數(shù)據(jù)文件時,不應該設定這些標志中的任何一個標志,CreateFileMapping將忽略這些標志。
CreateFileMapping的另外兩個參數(shù)是dwMaximumSizeHigh和dwMaximumSizeLow,它們是兩個最重要的參數(shù),CreateFileMapping函數(shù)的主要作用是保證文件映射對象能夠得到足夠的物理存儲器,這兩個參數(shù)將告訴系統(tǒng)該文件的最大字節(jié)數(shù),它需要兩個32位的值,因為Windows支持的文件大小可以用64位的值來表示,dwMaximumSizeHigh參數(shù)用于設定較高的32位,而dwMaximumSizeLow參數(shù)則用于設定較低的32位值,對于4GB或小于4GB的文件來說,dwMaximumSizeHigh的值將始終是0。
使用64位的值,意味著Windows能夠處理最大為16EB(1018字節(jié))的文件,如果想要創(chuàng)建一個文件映射對象,使它能夠反映文件當前的大小,那么可以為上面兩個參數(shù)傳遞0,如果只打算讀取該文件或者訪問文件而不改變它的大小,那么為這兩個參數(shù)傳遞0,如果打算將數(shù)據(jù)附加給該文件,可以選擇最大的文件大小,以便為你留出一些富裕的空間,如果當前磁盤上的文件包含0字節(jié),那么可以給CreateFileMapping函數(shù)的dwMaximumSizeHigh和dwMaximumSizeLow傳遞兩個0,這樣做就可以告訴系統(tǒng),你要的文件映射對象里面的存儲器為0字節(jié),這是個錯誤,CreateFileMapping將返回NULL。
如果你對我們講述的內(nèi)容一直非常關注,你一定認為這里存在嚴重的問題,Windows支持最大為16EB的文件和文件映射對象,這當然很好,但是,怎樣將這樣大的文件映射到32位進程的地址空間(32位地址空間是4GB文件的上限)中去呢,下一節(jié)介紹解決這個問題的辦法,當然,64位進程擁有16EB的地址空間,因此可以進行更大的文件的映射操作,但是,如果文件是個超大規(guī)模的文件,仍然會遇到類似的問題。
若要真正理解CreateFile和CreateFileMapping兩個函數(shù)是如何運行的,建議你做一個下面的實驗,建立下面的代碼,對它進行編譯,然后在一個調(diào)試程序中運行它,當你一步步執(zhí)行每個語句時,你會跳到一個命令解釋程序,并執(zhí)行C:\目錄上的“dir”命令,當執(zhí)行調(diào)試程序中的每個語句時,請注意目錄中出現(xiàn)的變化。
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);
}
如果調(diào)用CreateFileMapping函數(shù),傳遞PAGE_READWRITE標志,那么系統(tǒng)將設法確保磁盤上的相關數(shù)據(jù)文件的大小至少與dwMaximumSizeHigh和dwMaximumSizeLow參數(shù)中設定的大小相同,如果該文件小于設定的大小,CreateFileMapping函數(shù)將擴展該文件的大小,使磁盤上的文件變大,這種擴展是必要的,這樣,當以后將該文件作為內(nèi)存映射文件使用時,物理存儲器就已經(jīng)存在了,如果正在用PAGE_READONLY或PAGE_WRITECOPY標志創(chuàng)建該文件映射對象,那么CreateFileMapping特定的文件大小不得大于磁盤文件的物理大小,這是因為你無法將任何數(shù)據(jù)附加給該文件。
CreateFileMapping函數(shù)的最后一個參數(shù)是pszName,它是個以0結尾的字符串,用于給該文件映射對象賦予一個名字,該名字用于與其他進程共享文件映射對象(本章后面展示了它的一個例子,第3章詳細介紹了內(nèi)核對象的共享操作),內(nèi)存映射數(shù)據(jù)文件通常并不需要被共享,因此這個參數(shù)通常是NULL。
系統(tǒng)創(chuàng)建文件映射對象,并將用于標識該對象的句柄返回該調(diào)用線程,如果系統(tǒng)無法創(chuàng)建文件映射對象,便返回一個NULL句柄值,記住,當CreateFile運行失敗時,它將返回INVALID_HANDLE_VALUE(定義為-1),當CreateFileMapping運行失敗時,它返回NULL,請不要混淆這些錯誤值。
在本實例中創(chuàng)建文件映射內(nèi)核對象代碼如下:
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!"; //加入的文件內(nèi)容
__int64 myFilesize=qwFileSize+sinf.dwAllocationGranularity; //合并后的文件大小
HANDLE hmyfilemap = CreateFileMapping(hmyfile, NULL, PAGE_READWRITE, //合并文件大小的內(nèi)存映射對象
(DWORD)(myFilesize>>32), (DWORD)(myFilesize& 0xFFFFFFFF), NULL);
三、將文件數(shù)據(jù)映射到地址空間
當創(chuàng)建了一個文件映射對象后,仍然必須讓系統(tǒng)為文件的數(shù)據(jù)保留一個地址空間區(qū)域,并將文件的數(shù)據(jù)作為映射到該區(qū)域的物理存儲器進行提交,可以通過調(diào)用MapViewOfFile函數(shù)來進行這項操作:
PVOID MapViewOfFile(
HANDLE hFileMappingObject,
DWORD dwDesiredAccess,
DWORD dwFileOffsetHigh,
DWORD dwFileOffsetLow,
SIZE_T dwNumberOfBytesToMap);
參數(shù)hFileMappingObject用于標識文件映射對象的句柄,該句柄是前面調(diào)用CreateFileMapping或OpenFileMapping(本章后面介紹)函數(shù)返回的,參數(shù)dwDesiredAccess用于標識如何訪問該數(shù)據(jù),不錯,必須再次設定如何訪問文件的數(shù)據(jù),可以設定下表所列的4個值中的一個。
表17-6值及其含義
值 |
含義 |
FILE_MAP_WRITE |
可以讀取和寫入文件數(shù)據(jù),CreateFileMapping函數(shù)必須通過傳遞PAGE_READWRITE標志來調(diào)用 |
FILE_MAP_READ |
可以讀取文件數(shù)據(jù),CreateFileMapping函數(shù)可以通過傳遞下列任何一個保護屬性來調(diào)用:PAGE_READONLY,PAGE_READWRITE或PAGE_WRITECOPY。 |
FILE_MAP_ALL_ACCESS |
與FILE_MAP_WRITE相同 |
FILE_MAP_COPY |
可以讀取和寫入文件數(shù)據(jù),如果寫入文件數(shù)據(jù),可以創(chuàng)建一個頁面的私有拷貝,在Windows2000中,CreateFileMapping函數(shù)可以用PAGE_READONLY,PAGE_READWRITE或PAGE_WRITECOPY等保護屬性中的任何一個來調(diào)用,在Windows98中,CreateFileMapping必須用PAGE_WRITECOPY來調(diào)用。 |
Windows要求所有這些保護屬性一次又一次地重復設置,這當然有些奇怪和煩人,我認為這樣做可以使應用程序更多地對數(shù)據(jù)保護屬性進行控制,
剩下的3個參數(shù)與保留地址空間區(qū)域及將物理存儲器映射到該區(qū)域有關,當你將一個文件映射到你的進程的地址空間中時,你不必一次性地映射整個文件,相反,可以只將文件的一小部分映射到地址空間,被映射到進程的地址空間的這部分文件稱為一個視圖,這可以說明MapViewOfFile是如何而得名的,
當將一個文件視圖映射到進程的地址空間中時,必須規(guī)定兩件事情,首先,必須告訴系統(tǒng),數(shù)據(jù)文件中的哪個字節(jié)應該作為視圖中的第一個字節(jié)來映射,你可以使用dwFileOffsetHigh和dwFileOffsetLow參數(shù)來進行這項操作,由于Windows支持的文件最大可達16EB,因此必須用一個64位的值來設定這個字節(jié)的位移值,這個64位值中,較高的32位傳遞給參數(shù)dwFileOffsetHigh,較低的32位傳遞給參數(shù)dwFileOffsetLow,注意,文件中的這個位移值必須是系統(tǒng)的分配粒度的倍數(shù)(迄今為止,Windows的所有實現(xiàn)代碼的分配粒度均為64KB),第14章介紹了如何獲取某個系統(tǒng)的分配粒度。
第二,必須告訴系統(tǒng),數(shù)據(jù)文件有多少字節(jié)要映射到地址空間,這與設定要保留多大的地址空間區(qū)域的情況是相同的,可以使用dwNumberOfBytesToMap參數(shù)來設定這個值,如果設定的值是0,那么系統(tǒng)將設法把從文件中的指定位移開始到整個文件的結尾的視圖映射到地址空間。
在Windows98中,如果MapViewOfFile無法找到足夠大的區(qū)域來存放整個文件映射對象,那么無論需要的視圖是多大,MapViewOfFile均將返回NULL。
在Windows2000中,MapViewOfFile只需要為必要的視圖找到足夠大的一個區(qū)域,而不管整個文件映射對象是多大。
如果在調(diào)用MapViewOfFile函數(shù)時設定了FILE_MAP_COPY標志,系統(tǒng)就會從系統(tǒng)的頁文件中提交物理存儲器,提交的地址空間數(shù)量由dwNumberOfBytesToMap參數(shù)決定,只要你不進行其他操作,只是從文件的映像視圖中讀取數(shù)據(jù),那么系統(tǒng)將決不會使用頁文件中的這些提交的頁面,但是,如果進程中的任何線程將數(shù)據(jù)寫入文件的映像視圖中的任何內(nèi)存地址,那么系統(tǒng)將從頁文件中抓取已提交頁面中的一個頁面,將原始數(shù)據(jù)頁面拷貝到該頁交換文件中,然后將該拷貝的頁面映射到你的進程的地址空間,從這時起,你的進程中的線程就要訪問數(shù)據(jù)的本地拷貝,不能讀取或修改原始數(shù)據(jù)。
當系統(tǒng)制作原始頁面的拷貝時,系統(tǒng)將把頁面的保護屬性從PAGE_WRITECOPY改為PAGE_READWRITE,下面這個代碼段就說明了這個情況:
// 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);
Windows98前面講過,Windows98必須預先為內(nèi)存映射文件提交頁文件中的存儲器,然而,它只有在必要時才將修改后的頁面寫入頁文件,
四、從進程的地址空間撤消文件數(shù)據(jù)的映射
當不再需要保留映射到你的進程地址空間區(qū)域中的文件數(shù)據(jù)時,可以通過調(diào)用下面的函數(shù)將它釋放:
BOOLUnmapViewOfFile(PVOIDpvBaseAddress);
該函數(shù)的唯一的參數(shù)pvBaseAddress用于設定返回區(qū)域的基地址,該值必須與調(diào)用MapViewOfFile函數(shù)返回的值相同,必須記住要調(diào)用UnmapViewOfFile函數(shù),如果沒有調(diào)用這個函數(shù),那么在你的進程終止運行前,保留的區(qū)域就不會被釋放,每當你調(diào)用MapViewOfFile時,系統(tǒng)總是在你的進程地址空間中保留一個新區(qū)域,而以前保留的所有區(qū)域?qū)⒉槐会尫拧?span lang="EN-US">
為了提高速度,系統(tǒng)將文件的數(shù)據(jù)頁面進行高速緩存,并且在對文件的映射視圖進行操作時不立即更新文件的磁盤映像,如果需要確保你的更新被寫入磁盤,可以強制系統(tǒng)將修改過的數(shù)據(jù)的一部分或全部重新寫入磁盤映像中,方法是調(diào)用FlushViewOfFile函數(shù):
BOOLFlushViewOfFile(
PVOIDpvAddress,
SIZE_TdwNumberOfBytesToFlush);
第一個參數(shù)是包含在內(nèi)存映射文件中的視圖的一個字節(jié)的地址,該函數(shù)將你在這里傳遞的地址圓整為一個頁面邊界值,第二個參數(shù)用于指明你想要刷新的字節(jié)數(shù),系統(tǒng)將把這個數(shù)字向上圓整,使得字節(jié)總數(shù)是頁面的整數(shù),如果你調(diào)用FlushViewOfFile函數(shù)并且不修改任何數(shù)據(jù),那么該函數(shù)只是返回,而不將任何信息寫入磁盤。
對于存儲器是在網(wǎng)絡上的內(nèi)存映射文件來說,FlushViewOfFile能夠保證文件的數(shù)據(jù)已經(jīng)從工作站寫入存儲器,但是FlushViewOfFile不能保證正在共享文件的服務器已經(jīng)將數(shù)據(jù)寫入遠程磁盤,因為服務器也許對文件的數(shù)據(jù)進行了高速緩存,若要保證服務器寫入文件的數(shù)據(jù),每當你為文件創(chuàng)建一個文件映射對象并且映射該文件映射對象的視圖時,應該將FILE_FLAG_WRITE_THROUGH標志傳遞給CreateFile函數(shù),如果你使用該標志打開該文件,那么只有當文件的全部數(shù)據(jù)已經(jīng)存放在服務器的磁盤驅(qū)動器中的時候,FlushViewOfFile函數(shù)才返回。
記住UnmapViewOfFile函數(shù)的一個特殊的特性,如果原先使用FILE_MAP_COPY標志來映射視圖,那么你對文件的數(shù)據(jù)所作的任何修改,實際上是對存放在系統(tǒng)的頁文件中的文件數(shù)據(jù)的拷貝所作的修改,在這種情況下,如果調(diào)用UnmapViewOfFile函數(shù),該函數(shù)在磁盤文件上就沒有什么可以更新,而只會釋放頁文件中的頁面,從而導致數(shù)據(jù)丟失。
如果想保留修改后的數(shù)據(jù),必須采用別的措施,例如,你可以用同一個文件創(chuàng)建另一個文件映射對象(使用PAGE_READWRITE),然后使用FILE_MAP_WRITE標志將這個新文件映射對象映射到進程的地址空間,之后,你可以掃描第一個視圖,尋找?guī)в?span lang="EN-US">PAGE_READWRITE保護屬性的頁面,每當你找到一個帶有該屬性的頁面時,可以查看它的內(nèi)容,并且確定是否將修改了的數(shù)據(jù)寫入該文件,如果不想用新數(shù)據(jù)更新該文件,那么繼續(xù)對視圖中的剩余頁面進行掃描,直到視圖的結尾,但是,如果你確實想要保存修改了的數(shù)據(jù)頁面,那么只需要調(diào)用MoveMemory函數(shù),將數(shù)據(jù)頁面從第一個視圖拷貝到第二個視圖,由于第二個視圖是用PAGE_READWRITE保護屬性映射的,因此MoveMemory函數(shù)將更新磁盤上的實際文件內(nèi)容,可以使用這種方法來確定文件的變更并保存你的文件的數(shù)據(jù)。
Windows98不支持copy-on-write(寫入時拷貝)保護屬性,因此,當掃描內(nèi)存映射文件的第一個視圖時,無法測試用PAGE_READWRITE標志做上標記的頁面,你必須設計一種方法來確定第一個視圖中的哪些頁面已經(jīng)做了修改。
五、關閉文件映射對象和文件對象
不用說,你總是要關閉你打開了的內(nèi)核對象,如果忘記關閉,在你的進程繼續(xù)運行時會出現(xiàn)資源泄漏的問題,當然,當你的進程終止運行時,系統(tǒng)會自動關閉你的進程已經(jīng)打開但是忘記關閉的任何對象,但是如果你的進程暫時沒有終止運行,你將會積累許多資源句柄,因此你始終都應該編寫清楚而又“正確的”代碼,以便關閉你已經(jīng)打開的任何對象,若要關閉文件映射對象和文件對象,只需要兩次調(diào)用CloseHandle函數(shù),每個句柄調(diào)用一次:
讓我們更加仔細地觀察一下這個進程,下面的偽代碼顯示了一個內(nèi)存映射文件的例子:
HANDLEhFile=CreateFile(...);
HANDLEhFileMapping=CreateFileMapping(hFile,...);
PVOIDpvFile=MapViewOfFile(hFileMapping,...);
//Usethememory-mappedfile.
UnmapViewOfFile(pvFile);
CloseHandle(hFileMapping);
CloseHandle(hFile);
上面的代碼顯示了對內(nèi)存映射文件進行操作所用的“預期”方法,但是,它沒有顯示,當你調(diào)用MapViewOfFile時系統(tǒng)對文件對象和文件映射對象的使用計數(shù)的遞增情況,這個副作用是很大的,因為它意味著我們可以將上面的代碼段重新編寫成下面的樣子:
HANDLEhFile=CreateFile(...);
HANDLEhFileMapping=CreateFileMapping(hFile,...);
CloseHandle(hFile);
PVOIDpvFile=MapViewOfFile(hFileMapping,...);
CloseHandle(hFileMapping);
//Usethememory-mappedfile.
UnmapViewOfFile(pvFile);
當對內(nèi)存映射文件進行操作時,通常要打開文件,創(chuàng)建文件映射對象,然后使用文件映射對象將文件的數(shù)據(jù)視圖映射到進程的地址空間,由于系統(tǒng)遞增了文件對象和文件映射對象的內(nèi)部使用計數(shù),因此可以在你的代碼開始運行時關閉這些對象,以消除資源泄漏的可能性,
如果用同一個文件來創(chuàng)建更多的文件映射對象,或者映射同一個文件映射對象的多個視圖,那么就不能較早地調(diào)用CloseHandle函數(shù)——以后你可能還需要使用它們的句柄,以便分別對CreateFileMapping和MapViewOfFile函數(shù)進行更多的調(diào)用,
本實例中第三到第六步代碼如下:
CloseHandle(hFile);//Wenolongerneedaccesstothefileobject'shandle.
CloseHandle(hmyfile);
PBYTEpbmyFile=(PBYTE)MapViewOfFile(hmyfilemap,FILE_MAP_WRITE,//內(nèi)存映射視圖
0,//Startingbyte
0,//infile
sizeof(AddMsg));
memcpy(pbmyFile,AddMsg,sizeof(AddMsg));//加入內(nèi)容
UnmapViewOfFile(pbmyFile);
__int64qwFileOffset=0;//A文件視圖的偏移量
__int64qwmyFileOffset=sinf.dwAllocationGranularity;//合并文件視圖的遍移量
while(qwFileSize>0)
{
//Determinethenumberofbytestobemappedinthisview
DWORDdwBytesInBlock=sinf.dwAllocationGranularity;
if(qwFileSize<sinf.dwAllocationGranularity)//文件小于系統(tǒng)分配粒度
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);
參考資料:《Windows核心編程》
Richter是一位讓人佩服的前輩,本文99%是引用前輩原文,原創(chuàng)部份甚少,請各位莫見笑(本人純屬初學者),者作深厚的技術功底與精湛的語言,讓我對內(nèi)存映射文件操作有了清晰的認識,若大家有什么不清楚之處,可以聯(lián)系我,小弟愿與你們探討,