#include <windows.h> #include <string.h> #include <string> #include <iostream> using namespace std; int main(int argc, char *argv[]) { // 步驟1 打開文件FILE_FLAG_WRITE_THROUGH HANDLE hFile = CreateFile( "demo.txt", GENERIC_WRITE | GENERIC_READ,// 如果要映射文件:此處必設置為只讀(GENERIC_READ)或讀寫 0, // 此設為打開文件的任何嘗試均將失敗 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, //|FILE_FLAG_WRITE_THROUGH,【解1】 NULL); if (hFile != INVALID_HANDLE_VALUE)// 文件打開失敗返回句柄為-1 // 這步必須測試,詳細見步驟2 { cout<<"文件打開成功~!\n"; } else { cout<<"文件打開失敗!\n"; DWORD d = GetLastError(); cout<<d<<endl; return -1; } // 步驟2 建立內存映射文件 DWORD dwFileSize = GetFileSize(hFile, NULL); printf("文件大小為:%d\n", dwFileSize); HANDLE hFileMap = CreateFileMapping( hFile, // 如果這值為INVALID_HANDLE_VALUE,是合法的,上步一定測試啊 NULL, // 默認安全性 PAGE_READWRITE, // 可讀寫 0, // 2個32位數示1個64位數,最大文件字節數, // 高字節,文件大小小於4G時,高字節永遠為0 0,//dwFileSize, // 此為低字節,也就是最主要的參數,如果為0,取文件真實大小 NULL); if (hFileMap != NULL) { cout<<"內存映射文件創建成功~!\n"; } else { cout<<"內存映射文件創建失敗~!"<<endl; } // 步驟3:將文件數據映射到進程的地址空間 PVOID pvFile = MapViewOfFile( //pvFile就是得到的指針,用它來直接操作文件 hFileMap, FILE_MAP_WRITE, // 可寫 0, // 文件指針頭位置 高字節 0, // 文件指針頭位置 低字節 必為分配粒度的整倍數,windows的粒度為64K 0); // 要映射的文件尾,如果為0,則從指針頭到真實文件尾 if (pvFile != NULL) { cout<<"文件數據映射到進程的地址成功~!\n"; } else { cout<<"文件數據映射到進程的地址成功~!\n"; } // 步驟4: 像操作內存一樣操作文件,演示功能把整個文件倒序 char *p = (char*)pvFile; printf("%s\n", p); for (unsigned int i = 0; i <= dwFileSize / 2; i++) { int nTmp = p[i]; p[i] = p[dwFileSize - 1 - i]; p[dwFileSize - 1 - i] = nTmp; } printf("%s\n", p); cout<<"交換完畢,點擊Enter退出"<<endl; // 步驟5: 相關的釋放工作 UnmapViewOfFile(pvFile); // 釋放內存映射文件的頭指針 CloseHandle(hFileMap); // 內存映射文件句柄 CloseHandle(hFile); // 關閉文件 getchar(); return 0; }
使用內存映射數據文件
若要使用內存映射文件,必須執行下列操作步驟:
1) 創建或打開一個文件內核對象,該對象用於標識磁盤上你想用作內存映射文件的文件。
2) 創建一個文件映射內核對象,告訴系統該文件的大小和你打算如何訪問該文件。
3) 讓系統將文件映射對象的全部或一部分映射到你的進程地址空間中。
當完成對內存映射文件的使用時,必須執行下面這些步驟將它清除:
1) 告訴系統從你的進程的地址空間中撤消文件映射內核對象的映像。
2) 關閉文件映射內核對象。
3) 關閉文件內核對象。
下面將詳細介紹這些操作步驟。
步驟1:創建或打開文件內核對象
HANDLE CreateFile(
PCSTR pszFileName,
DWORD dwDesiredAccess,
DWORD dwShareMode,
PSECURITY_ATTRIBUTES psa,
DWORD dwCreationDisposition,
DWORD dwFlagsAndAttributes,
HANDLE hTemplateFile);
dwDesiredAccess的值
值 |
含義 |
0 |
不能讀取或寫入文件的內容。當只想獲得文件的屬性時,請設定0 |
GENERIC_READ |
可以從文件中讀取數據 |
GENERIC_WRITE |
可以將數據寫入文件 |
GENERIC_READ |GENERIC_WRITE |
可以從文件中讀取數據,也可以將數據寫入文件 |
dwShareMode 的值
值 |
含義 |
0 |
打開文件的任何嘗試均將失敗 |
FILE_SHARE_READ |
使用GENERIC_WRITE打開文件的其他嘗試將會失敗 |
FILE_SHARE_WRITE |
使用GENERIC_READ打開文件的其他嘗試將會失敗 |
FILE_SHARE_READ FILE_SHARE_WRITE| |
打開文件的其他嘗試將會取得成功 |
步驟2:創建一個文件映射內核對象
調用CreateFileMapping函數告訴系統,文件映射對象需要多少物理存儲器。
HANDLE CreateFileMapping(
HANDLE hFile,
PSECURITY_ATTRIBUTES psa,
DWORD fdwProtect,
DWORD dwMaximumSizeHigh,
DWORD dwMaximumSizeLow,
PCTSTR pszName);
第一個參數:hFile用於標識你想要映射到進程地址空間中的文件句柄。該句柄由前面調用的CreateFile函數返回。
第二個參數:psa參數是指向文件映射內核對象的SECURITY_ATTRIBUTES結構的指針,通常傳遞的值是NULL(它提供默認的安全特性,返回的句柄是不能繼承的)。
第三個參數:fdwProtect參數使你能夠設定這些保護屬性。大多數情況下,可以設定下表列出的3個保護屬性之一。
使用fdwProtect 參數設定的部分保護屬性
保護屬性 |
含義 |
PAGE_READONLY |
當文件映射對象被映射時,可以讀取文件的數據。必須已經將GENERIC_READ傳遞給CreateFile函數 |
PAGE_READWRITE |
當文件映射對象被映射時,可以讀取和寫入文件的數據。必須已經將GENERIC_READ | GENERIC_WRITE傳遞給Creat eFile |
PAGE_WRITECOPY |
當文件映射對象被映射時,可以讀取和寫入文件的數據。如果寫入數據,會導致頁面的私有拷貝得以創建。必須已經將GENERIC_READ或GENERIC_WRITE傳遞給CreateFile |
除了上面的頁面保護屬性外,還有4個節保護屬性
節的第一個保護屬性是SEC_NOCACHE,它告訴系統,沒有將文件的任何內存映射頁面放入高速緩存。因此,當將數據寫入該文件時,系統將更加經常地更新磁盤上的文件數據。供設備驅動程序開發人員使用的,應用程序通常不使用。
節的第二個保護屬性是SEC_IMAGE,它告訴系統,你映射的文件是個可移植的可執行(PE)文件映像。當系統將該文件映射到你的進程的地址空間中時,系統要查看文件的內容,以確定將哪些保護屬性賦予文件映像的各個頁面。例如, PE文件的代碼節( . text)通常用PAGE_ EXECUTE_READ屬性進行映射, 而PE 文件的數據節( .data) 則通常用PAGE_READW RITE屬性進行映射。如果設定的屬性是S E C _ I M A G E,則告訴系統進行文件映像的映射,並設置相應的頁面保護屬性。
最后兩個保護屬性是SEC_RESERVE和SEC_COMMIT,它們是兩個互斥屬性。只有當創建由系統的頁文件支持的文件映射對象時,這兩個標志才有意義。SEC_COMMIT標志能使CreateFileMapping從系統的頁文件中提交存儲器。如果兩個標志都不設定,其結果也一樣。
第四和五個參數:dwMaximumSizeHigh和dwMaximumSizeLow這兩個參數將告訴系統該文件的最大字節數
最后一個參數是pszName: 它是個以0結尾的字符串,用於給該文件映射對象賦予一個名字。該名字用於與其他進程共享文件映射對象。
步驟3:將文件數據映射到進程的地址空間
將文件的數據作為映射到該區域的物理存儲器進行提交。
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_ACCES S |
與FILE_MAP_WRITE相同 |
FILE_MAP_COPY |
可以讀取和寫入文件數據。如果寫入文件數據,可以創建一個頁面的私有拷貝。在Windows 2000中,CreateileMapping函數可以用PAGE_READONLY、PAGE_READWRITE或PAGE_WRITECOPY等保護屬性中的任何一個來調用。在Windows 98中,CreateFileMapping必須用PAGE_WRITECOPY來調用 |
(一個文件映射到你的進程的地址空間中時,你不必一次性地映射整個文件。相反,可以只將文件的一小部分映射到地址空間。被映射到進程的地址空間的這部分文件稱為一個視圖。)
第三四個參數:dwFileOfsetHigh和dwFileOfsetLow參數。指定哪個字節應該作為視圖中的第一個字節來映射。
第五個參數:dwNumberOfBytesToMap有多少字節要映射到地址空間。如果設定的值是0,那么系統將設法把從文件中的指定位移開始到整個文件的結尾的視圖映射到地址空間。
步驟4:從進程的地址空間中撤消文件數據的映像
當不再需要保留映射到進程地址空間區域中的文件數據時,可以通過調用下面的函數將它釋放:
BOOL UnmapViewOfFile(PVOID pvBaseAddress);
參數:pvBaseAddress由MapViewOfFile函數返回。
注意:如果沒有調用這個函數,那么在進程終止運行前,保留的區域就不會被釋放。每當調用MapViewOfFile時,系統總是在你的進程地址空間中保留一個新區域,而以前保留的所有區域將不被釋放。
為了提高速度,系統將文件的數據頁面進行高速緩存,並且在對文件的映射視圖進行操作時不立即更新文件的磁盤映像。如果需要確保你的更新被寫入磁盤,可以強制系統將修改過的數據的一部分或全部重新寫入磁盤映像中,方法是調用FlushViewOfFile函數:
BOOL FlushViewOfFile(
PVOID pvAddress,
SIZE_T dwNumberOfBytesToFlush);
第一個參數是包含在內存映射文件中的視圖的一個字節的地址。該函數將你在這里傳遞的地址圓整為一個頁面邊界值。
第二個參數用於指明你想要刷新的字節數。系統將把這個數字向上圓整,使得字節總數是頁面的整數。如果你調用FlushViewOfFile函數並且不修改任何數據,那么該函數只是返回,而不將任何信息寫入磁盤。
步驟5和步驟6:關閉文件映射對象和文件對象
用CloseHandle函數關閉相應的對象。
在代碼開始運行時關閉這些對象:
HANDLE hFile = CreateFile(...);
HANDLE hFileMapping = CreateFileMapping(hFile, ...);
CloseHandle(hFile);
PVOID pvFile = MapViewOfFile(hFileMapping, ...);
CloseHandle(hFileMapping);
// Use the memory-mapped file.
UnmapViewOfFile(pvFile);