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