CreateFileMapping的MSDN翻譯和使用心得編程
測試建立和打開文件映射的時候總是獲得"句柄無效"的錯誤, 仔細看了MSDN之後才發覺是函數認識不透, 這裏把相關的解釋翻譯出來緩存
HANDLE CreateFileMapping( 安全
HANDLE hFile, //物理文件句柄 網絡
LPSECURITY_ATTRIBUTES lpAttributes, //安全設置 app
DWORD flProtect, //保護設置 函數
DWORD dwMaximumSizeHigh, //高位文件大小 性能
DWORD dwMaximumSizeLow, //低位文件大小 測試
LPCTSTR lpName //共享內存名稱 );大數據
1) 物理文件句柄 任何能夠得到的物理文件句柄, 若是你須要建立一個物理文件無關的內存映射也無妨, 將它設置成爲 0xFFFFFFFF(INVALID_HANDLE_VALUE)就能夠了.spa
若是須要和物理文件關聯, 要確保你的物理文件建立的時候的訪問模式和"保護設置"匹配, 好比: 物理文件只讀, 內存映射須要讀寫就會發生錯誤. 推薦你的物理文件使用獨佔方式建立.
若是使用 INVALID_HANDLE_VALUE, 也須要設置須要申請的內存空間的大小, 不管物理文件句柄參數是否有效, 這樣 CreateFileMapping 就能夠建立一個和物理文件大小無關的內存空間給你, 甚至超過實際文件大小, 若是你的物理文件有效, 而大小參數爲0, 則返回給你的是一個和物理文件大小同樣的內存空間地址範圍. 返回給你的文件映射地址空間是能夠經過複製, 集成或者命名獲得, 初始內容爲0.
2) 保護設置 就是安全設置, 不過通常設置NULL就能夠了, 使用默認的安全配置. 在win2k下若是須要進行限制, 這是針對那些將內存文件映射共享給整個網絡上面的應用進程使用是, 能夠考慮進行限制.
3) 高位文件大小 弟兄們, 我想目前咱們的機器都是32位的東東, 不可能獲得超過32位進程所能尋址的私有32位地址空間, 通常仍是設置0吧, 我沒有也不想嘗試將它設置超過0的狀況. 4) 低位文件大小 這個仍是能夠進行設置的, 不過爲了讓其餘共享用戶知道你申請的文件映射的相關信息, 我使用的時候是在得到的地址空間頭部添加一個結構化描述信息, 記錄內存映射的大小, 名稱等, 這樣實際申請的空間就比輸入的增長了一個頭信息結構大小了, 我認爲這樣相似BSTR的方式應該是比較合理的.
5) 共享內存名稱 這個就是我今天測試的時候碰壁的禍根, 由於爲了對於內存進行互斥訪問, 我設置了一個互斥句柄, 而名稱我選擇和命名共享內存同名, 之下就是由於他們使用共同的namespace致使了錯誤, 呵呵.
7) 調用CreateFileMapping的時候GetLastError的對應錯誤 ERROR_FILE_INVALID 若是企圖建立一個零長度的文件映射, 應有此報 ERROR_INVALID_HANDLE 若是發現你的命名內存空間和現有的內存映射, 互斥量, 信號量, 臨界區同名就麻煩了 ERROR_ALREADY_EXISTS 表示內存空間命名已經存在
8) 相關服務或者平臺的命名保留 Terminal Services: 命名能夠包含 "Global\" 或者 "Local\" 前綴在全局或者會話名空間初級文件映射. 其餘部分能夠包含任何除了(\)之外的字符, 能夠參考 Kernel Object Name Spaces.
Windows 2000 or later: 若是 Terminal Services 沒有運行 "Global\" 和 "Local\" 前綴的特殊含義就被忽略了
CreateFileMapping 函數(轉載) 內存映射API函數CreateFileMapping建立一個有名的共享內存: HANDLE CreateFileMapping( HANDLE hFile, // 映射文件的句柄, //設爲0xFFFFFFFF以建立一個進程間共享的對象 LPSECURITY_ATTRIBUTES lpFileMappingAttributes, // 安全屬性 DWORD flProtect, // 保護方式 DWORD dwMaximumSizeHigh, //對象的大小 DWORD dwMaximumSizeLow, LPCTSTR lpName // 必須爲映射文件命名 );
與虛擬內存相似,保護方式能夠是PAGE_READONLY或是PAGE_READWRITE。若是多進程都對同一共享內存進行寫訪問,則必須保持相互間同步。映射文件還能夠指定PAGE_WRITECOPY標誌,能夠保證其原始數據不會遭到破壞,同時容許其餘進程在必要時自由的操做數據的拷貝。
在建立文件映射對象後使用能夠調用MapViewOfFile函數映射到本進程的地址空間內。
下面說明建立一個名爲MySharedMem的長度爲4096字節的有名映射文件: HANDLE hMySharedMapFile=CreateFileMapping((HANDLE)0xFFFFFFFF), NULL,PAGE_READWRITE,0,0x1000,"MySharedMem"); 並映射緩存區視圖: LPSTR pszMySharedMapView=(LPSTR)MapViewOfFile(hMySharedMapFile, FILE_MAP_READ|FILE_MAP_WRITE,0,0,0);
其餘進程訪問共享對象,須要得到對象名並調用OpenFileMapping函數。 HANDLE hMySharedMapFile=OpenFileMapping(FILE_MAP_WRITE, FALSE,"MySharedMem");
一旦其餘進程得到映射對象的句柄,能夠象建立進程那樣調用MapViewOfFile函數來映射對象視圖。用戶可使用該對象視圖來進行數據讀寫操做,以達到數據通信的目的。
當用戶進程結束使用共享內存後,調用UnmapViewOfFile函數以取消其地址空間內的視圖: if (!UnmapViewOfFile(pszMySharedMapView)) {
AfxMessageBox("could not unmap view of file");
} 建立文件視圖
要將文件中的數據映射到進程的虛擬內存中,你必須建立一個文件的視圖。 MapViewOfFile和MapViewOfFileEx函數使用CreateFileMapping返回的文件映射對象句柄來在進程的虛擬地址空間裏創建文件的視圖,或者文件的某個部分。若是這些函數指定的權限標誌和CreateFileMapping中的權限標誌不一致,則會執行失敗。 MapViewOfFile函數返回一個指向文件視圖的指針。利用MapViewOfFile中聲明的地址指針,程序就能夠從文件中讀以及向文件中寫入數據。向文件視圖中寫入數據會致使文件映射對象改變。真正將數據寫入到磁盤上的文件,由系統負責處理。數據並非立刻就別寫到磁盤上,不少文件的輸入輸出都被緩存起來,以改善系統的性能。程序能夠調用FlushViewOfFile函數來越過這個方式,強迫系統立刻將數據寫入到磁盤中去。 MapViewOfFileEx函數和MapViewOfFile函數做的工做是如出一轍的,只不過能夠利用MapViewOfFileEx函數的lpvBase參數,來指定文件視圖在進程虛擬地址空間中的基礎地址。若是在指定的地址處沒有足夠的空間,則調用失敗。 一、lpvBase參數必須是系統內存最小單位的整數倍,不然調用會失敗。要獲得系統內存的最小單位,使用GetSystemInfo函數,他將信息寫到SYSTEM_INFO結構的成員中。 程序能夠從同一個文件映射對象中建立多個文件視圖。文件視圖能夠是不一樣的大小,但他們必須小於文件映射對象。MapViewOfFile函數的dwOffsetHigh和dwOffsetLow參數必須是系統內存最小單位的整數倍。
文件映射問題 內存映射文件並非簡單的文件I/O操做,實際用到了Windows的核心編程技術--內存管理。因此,若是想對內存映射文件有更深入的認識,必須對Windows操做系統的內存管理機制有清楚的認識,內存管理的相關知識很是複雜,超出了本文的討論範疇,在此就再也不贅述,感興趣的讀者能夠參閱其餘相關書籍。下面給出使用內存映射文件的通常方法: 首先要經過CreateFile()函數來建立或打開一個文件內核對象,這個對象標識了磁盤上將要用做內存映射文件的文件。在用CreateFile()將文件映像在物理存儲器的位置通告給操做系統後,只指定了映像文件的路徑,映像的長度尚未指定。爲了指定文件映射對象須要多大的物理存儲空間還須要經過CreateFileMapping()函數來建立一個文件映射內核對象以告訴系統文件的尺寸以及訪問文件的方式。在建立了文件映射對象後,還必須爲文件數據保留一個地址空間區域,並把文件數據做爲映射到該區域的物理存儲器進行提交。由MapViewOfFile()函數負責經過系統的管理而將文件映射對象的所有或部分映射到進程地址空間。此時,對內存映射文件的使用和處理同一般加載到內存中的文件數據的處理方式基本同樣,在完成了對內存映射文件的使用時,還要經過一系列的操做完成對其的清除和使用過資源的釋放。這部分相對比較簡單,能夠經過UnmapViewOfFile()完成從進程的地址空間撤消文件數據的映像、經過CloseHandle()關閉前面建立的文件映射對象和文件對象。
內存映射文件相關函數 在使用內存映射文件時,所使用的API函數主要就是前面提到過的那幾個函數,下面分別對其進行介紹: HANDLE CreateFile(LPCTSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes, HANDLE hTemplateFile); 函數CreateFile()即便是在普通的文件操做時也常常用來建立、打開文件,在處理內存映射文件時,該函數來建立/打開一個文件內核對象,並將其句柄返回,在調用該函數時須要根據是否須要數據讀寫和文件的共享方式來設置參數dwDesiredAccess和dwShareMode,錯誤的參數設置將會致使相應操做時的失敗。 HANDLE CreateFileMapping(HANDLE hFile, LPSECURITY_ATTRIBUTES lpFileMappingAttributes, DWORD flProtect, DWORD dwMaximumSizeHigh, DWORD dwMaximumSizeLow, LPCTSTR lpName); CreateFileMapping()函數建立一個文件映射內核對象,經過參數hFile指定待映射到進程地址空間的文件句柄(該句柄由CreateFile()函數的返回值獲取)。因爲內存映射文件的物理存儲器實際是存儲於磁盤上的一個文件,而不是從系統的頁文件中分配的內存,因此係統不會主動爲其保留地址空間區域,也不會自動將文件的存儲空間映射到該區域,爲了讓系統可以肯定對頁面採起何種保護屬性,須要經過參數flProtect來設定,保護屬性PAGE_READONLY、PAGE_READWRITE和PAGE_WRITECOPY分別表示文件映射對象被映射後,能夠讀取、讀寫文件數據。在使用PAGE_READONLY時,必須確保CreateFile()採用的是GENERIC_READ參數;PAGE_READWRITE則要求CreateFile()採用的是GENERIC_READ|GENERIC_WRITE參數;至於屬性PAGE_WRITECOPY則只須要確保CreateFile()採用了GENERIC_READ和GENERIC_WRITE其中之一便可。DWORD型的參數dwMaximumSizeHigh和dwMaximumSizeLow也是至關重要的,指定了文件的最大字節數,因爲這兩個參數共64位,所以所支持的最大文件長度爲16EB,幾乎能夠知足任何大數據量文件處理場合的要求。 LPVOID MapViewOfFile(HANDLE hFileMappingObject, DWORD dwDesiredAccess, DWORD dwFileOffsetHigh, DWORD dwFileOffsetLow, DWORD dwNumberOfBytesToMap); MapViewOfFile()函數負責把文件數據映射到進程的地址空間,參數hFileMappingObject爲CreateFileMapping()返回的文件映像對象句柄。參數dwDesiredAccess則再次指定了對文件數據的訪問方式,並且一樣要與CreateFileMapping()函數所設置的保護屬性相匹配。雖然這裏一再對保護屬性進行重複設置看似多餘,但卻可使應用程序能更多的對數據的保護屬性實行有效控制。MapViewOfFile()函數容許所有或部分映射文件,在映射時,須要指定數據文件的偏移地址以及待映射的長度。其中,文件的偏移地址由DWORD型的參數dwFileOffsetHigh和dwFileOffsetLow組成的64位值來指定,並且必須是操做系統的分配粒度的整數倍,對於Windows操做系統,分配粒度固定爲64KB。固然,也能夠經過以下代碼來動態獲取當前操做系統的分配粒度: SYSTEM_INFO sinf; GetSystemInfo(&sinf); DWORD dwAllocationGranularity = sinf.dwAllocationGranularity; 參數dwNumberOfBytesToMap指定了數據文件的映射長度,這裏須要特別指出的是,對於Windows 9x操做系統,若是MapViewOfFile()沒法找到足夠大的區域來存放整個文件映射對象,將返回空值(NULL);可是在Windows 2000下,MapViewOfFile()只須要爲必要的視圖找到足夠大的一個區域便可,而無須考慮整個文件映射對象的大小。 在完成對映射到進程地址空間區域的文件處理後,須要經過函數UnmapViewOfFile()完成對文件數據映像的釋放,該函數原型聲明以下: BOOL UnmapViewOfFile(LPCVOID lpBaseAddress); 惟一的參數lpBaseAddress指定了返回區域的基地址,必須將其設定爲MapViewOfFile()的返回值。在使用了函數MapViewOfFile()以後,必需要有對應的UnmapViewOfFile()調用,不然在進程終止以前,保留的區域將沒法釋放。除此以外,前面還曾由CreateFile()和CreateFileMapping()函數建立過文件內核對象和文件映射內核對象,在進程終止以前有必要經過CloseHandle()將其釋放,不然將會出現資源泄漏的問題。 除了前面這些必須的API函數以外,在使用內存映射文件時還要根據狀況來選用其餘一些輔助函數。例如,在使用內存映射文件時,爲了提升速度,系統將文件的數據頁面進行高速緩存,並且在處理文件映射視圖時不當即更新文件的磁盤映像。爲解決這個問題能夠考慮使用FlushViewOfFile()函數,該函數強制系統將修改過的數據部分或所有從新寫入磁盤映像,從而能夠確保全部的數據更新能及時保存到磁盤。
共享內存對象方法(MapViewOfFile) 共享內存對象方法一般,將頁面文件支持的內存映射文件做爲在用戶進程之間共享內存的技術。可是,可使用相同的技術在用戶進程與設備驅動程序之間共享內存。使用這種技術有兩種方法。
第一種方法中,經過使用 OpenFileMapping,而後調用 MapViewOfFile 函數以獲取指向某個區域或全部共享內存的指針,驅動程序能夠建立命名內存對象(稱爲「區域對象」),而且一個或多個用戶應用程序能夠打開相同的對象。經過向區域對象指定保護屬性,能夠定義進程操縱內存的方式。
第二種方法中,應用程序能夠用 CreateFileMapping 在用戶模式下建立命名內存對象。驅動程序經過使用 ZwOpenSection 並調用 ZwMapViewOfSection 獲取指向它的指針,能夠打開相同的內存對象。始終用異常處理程序在內核模式下訪問此內存地址。
因爲該對象始終映射在進程的用戶地址空間(小於 0x80000000,不管對象是在內核模式仍是在用戶模式中建立的)中,所以只在進程上下文中訪問地址時,地址纔有效。每次在相同內存對象上調用 MapViewOfFile 或 ZwMapViewOfSection 時,都將返回不一樣的內存地址(即便是相同的進程,也是如此)。建議不要使用這種方法(尤爲是低級設備驅動程序),正如前面所述,這是由於地址範圍限定於進行對象映射的進程,而且不能在 DPC 或 ISR 中對地址進行訪問。另外,在 DDK 中沒有記載在內核模式下建立內存對象的 API。
可是,要在提升的 IRQL(如 DPC 或 ISR 中)上使用該地址,必須查明並鎖定緩衝區頁面,並獲取系統虛擬地址 MmGetSystemAddressForMdl(正如本文前面 IOCTL 方法中所述)。
僅當要在兩個(或更多)用戶進程與一個(或多個)設備驅動程序之間共享內存的狀況下,這種方法才比較簡便。不然,使用 IOCTL 技術在用戶進程與設備驅動程序之間共享內存更加簡單高效。
內存映射文件技術 1. 用途和基本操做 用於不一樣進程之間的內存共享操做, 能夠將一個物理文件映射到內存當中而後直接利用分配到的或者打開的命名共享內存的地址空間實現資源共享訪問
2. 相關流程 1) 新建命名共享內存 首先利用CreateFile或者CreateFileForMapping得到一個用於映射的物理文件句柄, 而後利用該文件句柄結合CreateFileMapping獲得一個命名的共享內存映射文件句柄。 //CreateFileMapping 爲指定文件建立一個有名或無名的文件映象; HANDLE CreateFileMapping( HANDLE hFile, // 映射文件的句柄 LPSECURITY_ATTRIBUTES lpFileMappingAttributes, // 安全描述符指針 DWORD flProtect, // 對映射對象的保護 DWORD dwMaximumSizeHigh, // 對象最大長度的高32位 DWORD dwMaximumSizeLow, // 對象最大長度的低32位 LPCTSTR lpName // 文件內存映射對象的名字 );
注意: hFile:映射文件的句柄,文件的打開模式必須與flProtect參數指定的相一致;若是這個參數值爲0xFFFFFFFF,那麼必須在dwMaximumSizeHigh和dwMaximumSizeLow參數中指定映射對象的大小。而且將在操做系統虛擬內存頁面替換文件中建立文件映射對象,而不是使用磁盤文件,同時必須給出這個映射對象的大小。文件映射對象經過副本,遺傳或名字來共享。 lpFileMappingAttributes:安全描述符指針,決定返回句柄是否能被子進程繼承,若是是NULL,那麼子進程不能繼承。WinNt中,若是是NULL,那麼文件映射對象獲得一個默認的安全描述符。 flProtect:爲獲得的文件試圖指定保護模式,能夠被設置爲下列值: PAGE_READONLY :只讀屬性,而且hFile對應的文件必須以GENERIC_READ形式打開。 PAGE_READWRITE:可讀可寫屬性,而且hFile對應的文件必須以GENERIC_READ 和 GENERIC_WRITE形式打開。 PAGE_WRITECOPY:對可寫區域複製後操做,而且hFile對應的文件必須以GENERIC_READ 和 GENERIC_WRITE形式打開。 dwMaximumSizeHigh,dwMaximumSizeLow:若是這兩個參數爲0,則文件映射對象的最大長度等於hFile指定的文件長度。 lpName:文件映射對象的名字,若是這個名字已存在,則按照flProtect指定的來處理映射對象。若是此參數爲空,則建立一個無名字的文件映射對象。若是此參數的名字與系統事件的名字相同,則函數執行失敗,GetLastError返回 ERROR_INVALID_HANDLE;
返回值:函數調用成功返回文件映射對象的句柄,若是文件映射對象已經存在則返回原有映射對象的句柄,GetLastError返回ERROR_ALREADY_EXISTS。函數執行失敗返回Null。
2) 打開命名共享內存 若是須要共享已經存在的命名共享內存映射文件, 使用OpenFileMapping函數。 //OpenFileMapping 打開一個已命名的文件映射對象 HANDLE OpenFileMapping( DWORD dwDesiredAccess, // 訪問模式 BOOL bInheritHandle, // 繼承標誌 LPCTSTR lpName // 文件映射對象名指針 ); 注意: dwDesiredAccess:訪問模式與MapViewOfFile中的訪問模式相同。 bInheritHandle:繼承標誌,是否能夠被一個新的進程繼承使用,若是爲TRUE,就能夠被一個新進程繼承句柄。 返回值: 成功返回一個已命名的文件映射對象,失敗返回NULL。
3) 得到地址空間指針 進行內存映射文件的讀寫和通常的文件讀寫不一樣, 是直接面對你申請的地址空間, 爲此須要使用MapViewOfFile獲得相關的地址LPVOID類型的指針。若是須要進行文件寫入, 能夠經過類型轉換直接對於內存地址進行賦值, 好比: memcpy( lpAddress, lpBuf, ....) 這裏天然須要防止內存溢出的狀況。 若是是讀取操做,將參數順序調整一下就能夠了。
MapViewOfFile 在調用進程的地址空間映射一個文件視圖 LPVOID MapViewOfFile( HANDLE hFileMappingObject, // 已建立的文件映射對象句柄 DWORD dwDesiredAccess, // 訪問模式 DWORD dwFileOffsetHigh, // 文件偏移的高32位 DWORD dwFileOffsetLow, // 文件偏移的低32位 DWORD dwNumberOfBytesToMap // 映射視圖的大小 ); 注意: hFileMappingObject: 由CreateFileMapping 或 OpenFileMapping 返回的文件映射對象句柄。 dwDesiredAccess:映射視圖的訪問模式,與建立文件映射對象的保護模式flProtect有關,能夠被設置爲下列值: FILE_MAP_WRITE:一個可讀寫屬性的文件視圖被建立,保護模式爲PAGE_READWRITE FILE_MAP_READ :一個只讀屬性的文件視圖被建立,保護模式爲PAGE_READWRITE 或 PAGE_READONLY FILE_MAP_ALL_ACCESS:與FILE_MAP_WRITE模式相同 FILE_MAP_COPY:保護模式爲PAGE_WRITECOPY時,獲得一個視圖文件,當你對視圖文件寫操做時,頁面自動交換,而且你所作的修改不會損壞原始數據資料。 dwNumberOfBytesToMap:映射文件部分的大小,若是爲0,則映射整個文件。 返回值: 若是成功返回返回映射視圖的起始地址,若是失敗返回NULL。
4)MapViewOfFileEx 在調用進程的地址空間映射一個文件視圖,而且容許調用進程爲映射視圖指定特殊的內存地址 LPVOID MapViewOfFileEx( HANDLE hFileMappingObject, // 文件映射對象的句柄 DWORD dwDesiredAccess, // 訪問模式 DWORD dwFileOffsetHigh, // 文件偏移的高32位 DWORD dwFileOffsetLow, // 文件偏移的低32位 DWORD dwNumberOfBytesToMap, // 映射視圖的大小 LPVOID lpBaseAddress // 指定映射視圖的其實內存地址 ); 注意: 與MapViewOfFile用法相同,可是若是指定的內存地址空間大小不夠,則函數執行失敗。
5) 將內存複製到所映射的物理文件上面 FlushMapViewOfFile函數能夠將內存裏面的內容DUMP到物理磁盤上面 FlushViewOfFile 把文件映射視圖中的修改的內容或所有寫回到磁盤文件中 BOOL FlushViewOfFile( LPCVOID lpBaseAddress, // 修改內容的起始地址 DWORD dwNumberOfBytesToFlush // 修改的字節數目 ); 函數執行成功返回非零。
6) 卸載內存映射文件地址指針 UnmapViewOffFile函數就是卸載 UnmapViewOfFile 刪除文件的映射視圖 BOOL UnmapViewOfFile( LPCVOID lpBaseAddress // 映射視圖起始地址 ); 注意: lpBaseAddress:映射視圖起始地址,由 MapViewOfFile 函數 MapViewOfFileEx產生。 返回值: 若是調用成功返回非零,而且全部指定地址內的髒頁面會被寫入硬盤。調用失敗返回零。
7) 關閉內存映射文件 太簡單了, CloseHandle搞定