CreateFileMapping實現的共享內存及用法


 出處http://blog.sina.com.cn/s/blog_628af5cf01010d6r.html

 

 

在32位的Windows系統中,每一個進程都有權訪問他自己的4GB(232=4294967296)平面地址空間,沒有段,沒有選擇符,沒有near和far指針,沒有near和far函數調用,也沒有內存模式。

每個進程都有獨立的4GB邏輯地址空間,32位的Windows系統允許每一個進程獨立訪問自己的內存,即獨立於其它進程,也即它自己的32位邏輯地址空間。操作系統將把每一個進程的邏輯地址轉換成實際的物理地址,獨立的地址空間可以使其他已經出錯的進程之間相互隔離,入閣一個進程通過他自己的內存空間處理數據,其他的進程就比在DOS中安全,在DOS中的所有應用程序共享相同的物理內存空間,雖然這帶來了許多好處,但在不同進程之間轉的指針,就會出現一些麻煩。在一個進程中,一個給定的邏輯地址將與另一進程的指針不會有相同的邏輯地址。

那么怎么樣才能在32位的Windows系統中達到共享內存的目的呢?

隨着進程的分離內存空間的出現,進程不能簡單地使用GlobalAlloc()函數來分配內存,並把它轉遞給另一個進程來共享,一個進程檢查有另一個進程分配的指針,他只是指向隨機地址。然而,在32位的Windows系統支持在進程間共享內存映象文件,可以通過內存映象文件來達到內存共享的目的。

32位的Windows系統的虛擬內存系統具有把實際內存映象到頁面文件或交換文件的能力.通過把內存映象到任何想映象的文件,包括系統頁面本身,應用程序就可以拓展這種能力。文件影響能用來提供更快更簡捷的文件訪問方式,並提供內存共享。

把文件映像到內存,首先必須調用CreateFileMapping()函數,然后再調用MapViewOfFile函數,把文件視映像到進程地址空間上。

C:專家點評
要把文件映像到內存,首先必須調用CreateFileMapping()函數,它需要用一個由CreateFile()函數打開並返回的文件句柄,對大多數共享內存應用程序。必須把此句柄設置為0xFFFFFFFF,用來指定系統頁面文件。通過使用上面的特殊句柄,可以不調用CreateFile函數,當然在完成時,也不必有一個內存的磁盤文件拷貝。

CreateFileMapping()函數的第二個參數是一個指向SECURITY_ATTRIBUTES結構的指針,它指明返回的句柄是否可以被子進程所繼承。另外,在SECURITY_ATTRIBUTES結構中,也包括一個安全性描述的子指針,它由WinNT支持它的安全機制。

第三個參數允許指定內存塊的訪問權限,權限值有PAGE_READONLY、PAGE_READWRITE和PAGE_WRITECOPY,PAGE_WRITECOPY可以在指定頁面上拷貝並寫訪問。這意味着當一個進程映像此內存並寫入時,它將得到自己修改數據的拷貝,面不是寫到共享內存空間,另外,可以把幾個標志一起使用來指定其他部分的屬性。

其他參數允許指定內存塊的最大尺寸,如果內存塊的尺寸比第一個 參數中指定的文件尺寸還要大,這個文件就增大。

最后一個參數為內存映像對象指定名字,通過調用CreateFileMapping函數和OpenFileMapping函數,其他進程可用這個名字來訪問相同的文件映像。

一旦一個內存映像對象由CreateFileMapping()創建成功,可以調用MapViewOfFile函數把文件視映像到進程地址空間上,這個函數需要用一個由CreateFileMapping()函數或OpenFileMapping()函數返回的句柄,並允許指定訪問模式和映像的字節數,以及文件映像對象中的偏移量。另外,還可以使用MapViewOfFileEx()函數來實現同樣的功能,只是此函數還允許指定映像對象的起始地址。

當用完映像文件后,可以通過調用UnmapViewOfFile函數,釋放映像內存並把一些映像數據寫入文件(除非它是交換文件),如果想立即把數據寫回磁盤文件,那就需要調用FlushViewOfFile()函數把映像內存寫入文件。

為了便於讀者對以上程序的理解和進行下一步的學習,這里再簡單介紹一下Win32內存模式。在開始學習Win32內存管理之前,最好先了解一些16位Windows與32位Windows的區別,它們之間的主要區別是把寄存器和指針長度變為32位。

Win32 API允許應用程序訪問虛擬內存,如果想保存大量的數據,這十分有用,盡管大部分空間從來沒有用於物理存儲。虛擬內存頁有四種狀態,提交頁面被分配一個物理存儲單元,不管它是在是在交換文件中,提交頁面被鎖定后就強行留在內存中,直到解鎖為止。保留頁面存儲一塊保留地址國,它不被分配任何存儲單元,自由頁面都沒有用。

VirtualAlloc()函數可用來分配從指定邏輯內存開始的一定范圍內的地址,該函數的另一個參數允許指定訪問保護標志,並指定內存是否被保存或被用於物理存儲單元,也可以調用此函數來調配前面保存的虛擬內存頁。

可以通過調用GloballLock()函數來強制使內存塊留在物理內存中,應該謹慎使用此函數,因為它阻止系統管理這塊內存,直到調用GlobalUnlock()函數為止。如果一個進程鎖定了一大塊內存,其他進程就會終止,不管還有多少內存。

函數VirtualProtect()用於改變一塊內存的訪問權限,VirtualQuery()函數返回內存頁的住處。如果想訪問其他進程的頁面,可以通過調用VirtualProtecteEx和VirutalQueryEx兩個函數來實現。

D:測試創建和打開文件映射的時候老是得到"句柄無效"的錯誤, 仔細看了MSDN以后才發覺是函數認識不透, 這里把相關的解釋翻譯出來

HANDLE CreateFileMapping(
HANDLE hFile,                       //物理文件句柄
LPSECURITY_ATTRIBUTES lpAttributes, //安全設置
DWORD flProtect,                    //保護設置
DWORD dwMaximumSizeHigh,            //高位文件大小
DWORD dwMaximumSizeLow,             //低位文件大小
LPCTSTR lpName                      //共享內存名稱
);

1) 物理文件句柄
   任何可以獲得的物理文件句柄, 如果你需要創建一個物理文件無關的內存映射也無妨, 將它設置成為 0xFFFFFFFF(INVALID_HANDLE_VALUE)就可以了.

   如果需要和物理文件關聯, 要確保你的物理文件創建的時候的訪問模式和"保護設置"匹配, 比如: 物理文件只讀, 內存映射需要讀寫就會發生錯誤. 推薦你的物理文件使用獨占方式創建.

   如果使用 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" 前綴的特殊含義就被忽略了

              2

在開發軟件過程里,也經常碰到進程間共享數據的需求。比如A進程創建計算數據,B進程進行顯示數據的圖形。這樣的開發方式可以把一個大程序分開成獨立的小程序,提高軟件的成功率,也可以更加適合團隊一起開發,加快軟件的開發速度。下面就來使用文件映射的方式進行共享數據。先要使用函數CreateFileMapping來創建一個想共享的文件數據句柄,然后使用MapViewOfFile來獲取共享的內存地址,然后使用OpenFileMapping函數在另一個進程里打開共享文件的名稱,這樣就可以實現不同的進程共享數據。
函數CreateFileMapping、MapViewOfFile聲明如下:
WINBASEAPI
__out
HANDLE
WINAPI
CreateFileMappingA(
    __in     HANDLE hFile,
    __in_opt LPSECURITY_ATTRIBUTES lpFileMappingAttributes,
    __in     DWORD flProtect,
    __in     DWORD dwMaximumSizeHigh,
    __in      DWORD dwMaximumSizeLow,
    __in_opt LPCSTR lpName
    );
WINBASEAPI
__out
HANDLE
WINAPI
CreateFileMappingW(
    __in     HANDLE hFile,
    __in_opt LPSECURITY_ATTRIBUTES lpFileMappingAttributes,
    __in     DWORD flProtect,
    __in     DWORD dwMaximumSizeHigh,
    __in     DWORD dwMaximumSizeLow,
    __in_opt LPCWSTR lpName
    );
#ifdef UNICODE
#define CreateFileMapping CreateFileMappingW
#else
#define CreateFileMapping CreateFileMappingA
#endif // !UNICODE
WINBASEAPI
__out
LPVOID
WINAPI
MapViewOfFile(
    __in HANDLE hFileMappingObject,
    __in DWORD dwDesiredAccess,
    __in DWORD dwFileOffsetHigh,
    __in DWORD dwFileOffsetLow,
    __in SIZE_T dwNumberOfBytesToMap
    );
hFile是創建共享文件的句柄。
lpFileMappingAttributes是文件共享的屬性。
flProtect是當文件映射時讀寫文件的屬性。
dwMaximumSizeHigh是文件共享的大小高位字節。
dwMaximumSizeLow是文件共享的大小低位字節。
lpName是共享文件對象名稱。
hFileMappingObject是共享文件對象。
dwDesiredAccess是文件共享屬性。
dwFileOffsetHigh是文件共享區的偏移地址。
dwFileOffsetLow是文件共享區的偏移地址。
dwNumberOfBytesToMap是共享數據長度。
調用函數的例子如下:
#001   //文件共享。
#002   //蔡軍生 2007/10/27 QQ:9073204 深圳
#003   void FileMapping(void)
#004   {
#005         //打開共享的文件對象。
#006         m_hMapFile = OpenFileMapping(FILE_MAP_ALL_ACCESS,
#007              FALSE,_T("TestFileMap"));
#008         if (m_hMapFile)
#009         {
#010               //顯示共享的文件數據。
#011              LPTSTR lpMapAddr = (LPTSTR)MapViewOfFile(m_hMapFile,FILE_MAP_ALL_ACCESS,
#012                   0,0,0);
#013               OutputDebugString(lpMapAddr);
#014         }
#015         else
#016         {
#017               //創建共享文件。
#018               m_hMapFile = CreateFileMapping( (HANDLE)0xFFFFFFFF,NULL,
#019                   PAGE_READWRITE,0,1024,_T("TestFileMap"));
#020
#021               //拷貝數據到共享文件里。
#022               LPTSTR lpMapAddr = (LPTSTR)MapViewOfFile(m_hMapFile,FILE_MAP_ALL_ACCESS,
#023                    0,0,0);
#024               std::wstring strTest(_T("TestFileMap"));
#025               wcscpy(lpMapAddr,strTest.c_str());                
#026
#027               FlushViewOfFile(lpMapAddr,strTest.length()+1);
#028         }
#029   }

                          

 

             3

內存映射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");

}

http://www.itstudy.net/html/200903/19/20090319155603.htm


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM