標籤:des style blog http color io os ar for
共用記憶體的方式原理就是將一份實體記憶體映射到不同進程各自的虛擬位址空間上,這樣每個進程都可以讀取同一份資料,從而實現進程通訊。因為是通過記憶體操作實現通訊,因此是一種最高效的資料交換方法。
共用記憶體在 Windows 中是用 FileMapping 實現的,從具體的實現方法上看主要通過以下幾步來實現:
1、調用 CreateFileMapping 建立一個記憶體檔案對應物件;
HANDLE CreateFileMapping( HANDLE hFile, // handle to file to map LPSECURITY_ATTRIBUTES lpFileMappingAttributes, // optional security attributes DWORD flProtect, // protection for mapping object DWORD dwMaximumSizeHigh, // high-order 32 bits of object size DWORD dwMaximumSizeLow, // low-order 32 bits of object size LPCTSTR lpName // name of file-mapping object);
通過這個API函數 將建立一個記憶體對應檔的核心對象,用於對應檔到記憶體。與虛擬記憶體一樣,記憶體對應檔可以用來保留一個地址空間的地區,並將實體儲存體器提交
給該地區。它們之間的差別是,實體儲存體器來自一個已經位於磁碟上的檔案,而不是系統的頁檔案。
hFile:用於標識你想要映射到進程地址空間中的檔案控制代碼。該控制代碼可以通過調用C r e a t e F i l e函數返回。這裡,我們並不需要一個實際的檔案,所以,就不需要調用 CreateFile 建立一個檔案, hFile 這個參數可以填寫 INVALID_HANDLE_VALUE; lpFileMappingAttributes:參數是指向檔案對應核心對象的 SECURITY_ATTRIBUTES結構的指標,通常傳遞的值是 N U L L;flProtect:對記憶體對應檔的安全設定(PAGE_READONLY 以唯讀方式開啟映射;PAGE_READWRITE 以可讀、可寫方式開啟映射;PAGE_WRITECOPY 為寫操作留下備份)dwMaximumSizeHigh:檔案對應的最大長度的高32位。dwMaximumSizeLow:檔案對應的最大長度的低32位。如這個參數和dwMaximumSizeHigh都是零,就用磁碟檔案的實際長度。lpName:指定檔案對應物件的名字,別的進程就可以用這個名字去調用 OpenFileMapping 來開啟這個 FileMapping 對象。
如果建立成功,返回建立的記憶體對應檔的控制代碼,如果已經存在,則也返回其控制代碼,但是調用 GetLastError()返回的錯誤碼是:183(ERROR_ALREADY_EXISTS),如果建立失敗,則返回NULL;
2、調用 MapViewOfFile 映射到當前進程的虛擬位址上;
如果調用CreateFileMapping成功,則調用MapViewOfFile函數,將記憶體對應檔映射到進程的虛擬位址中;
LPVOID MapViewOfFile( HANDLE hFileMappingObject, // file-mapping object to map into // address space DWORD dwDesiredAccess, // access mode DWORD dwFileOffsetHigh, // high-order 32 bits of file offset DWORD dwFileOffsetLow, // low-order 32 bits of file offset DWORD dwNumberOfBytesToMap // number of bytes to map);
hFileMappingObject:CreateFileMapping()返回的檔案映像物件控點。
dwDesiredAccess: 映射對象的檔案資料的訪問方式,而且同樣要與CreateFileMapping()函數所設定的保護屬性相匹配。
dwFileOffsetHigh: 表示檔案對應起始位移的高32位.
dwFileOffsetLow: 表示檔案對應起始位移的低32位.
dwNumberOfBytesToMap :檔案中要映射的位元組數。為0表示映射整個檔案對應物件。
3、在接收進程中開啟對應的記憶體映射對象
在資料接收進程中,首先調用OpenFileMapping()函數開啟一個命名的檔案對應核心對象,得到相應的檔案對應核心物件控點hFileMapping;如果開啟成功,則調用MapViewOfFile()函數映射對象的一個視圖,將檔案對應核心對象hFileMapping映射到當前應用程式的進程地址,進行讀取操作。(當然,這裡如果用CreateFileMapping也是可以擷取對應的控制代碼)
HANDLE OpenFileMapping( DWORD dwDesiredAccess, // access mode BOOL bInheritHandle, // inherit flag LPCTSTR lpName // pointer to name of file-mapping object);
dwDesiredAccess:同MapViewOfFile函數的dwDesiredAccess參數
bInheritHandle :如這個函數返回的控制代碼能由當前進程啟動的新進程繼承,則這個參數為TRUE。
lpName :指定要開啟的檔案對應物件名稱。
4、進行記憶體對應檔的讀寫
一旦MapViewOfFile調用成功,就可以像讀寫本進程地址空間的記憶體區一樣,進行記憶體的讀寫操作了。
//讀操作:if ( m_pViewOfFile ){ // read text from memory-mapped file TCHAR s[dwMemoryFileSize]; lstrcpy(s, (LPCTSTR) m_pViewOfFile);}//寫操作:if ( m_pViewOfFile ) { TCHAR s[dwMemoryFileSize]; m_edit_box.GetWindowText(s, dwMemoryFileSize); lstrcpy( (LPTSTR) m_pViewOfFile, s); // Notify all running instances that text was changed ::PostMessage(HWND_BROADCAST, wm_Message, (WPARAM) m_hWnd, 0); }
5、清理核心對象
在用完後,要取消本進程地址空間的映射,並釋放記憶體映射對象。
//取消本進程地址空間的映射; UnmapViewOfFile(pLocalMem); pLocalMem=NULL; //關閉檔案對應核心檔案 CloseHandle(hFileMapping);
6、簡單例子:
下面寫一個簡單的例子來說明:
例子:在一個進程的文本對話方塊中輸入文本,同時在另一個進程的文本對話方塊中顯示之前輸入的內容。
const DWORD dwMemoryFileSize = 4 * 1024; //指定記憶體對應檔大小const LPCTSTR sMemoryFileName = _T("D9287E19-6F9E-45fa-897C-D392F73A0F2F");//指定記憶體對應檔名稱const UINT wm_Message = RegisterWindowMessage(_T("CC667211-7CE9-40c5-809A-1DA48E4014C4"));//註冊訊息
指定訊息處理函數
BEGIN_MESSAGE_MAP(CIpcDlg, CDialog) //{{AFX_MSG_MAP(CIpcDlg) ON_WM_SYSCOMMAND() ON_WM_PAINT() ON_WM_QUERYDRAGICON() ON_WM_DESTROY() ON_REGISTERED_MESSAGE(wm_Message, OnMessageTextChanged) ON_EN_CHANGE(IDC_EDT_TEXT, OnChangeEdtText) //}}AFX_MSG_MAPEND_MESSAGE_MAP()
LRESULT CIpcDlg::OnMessageTextChanged( WPARAM wParam, LPARAM lParam )
{
if ( wParam == (WPARAM) m_hWnd )
return 0;
// Get text from memory mapped file and set it to edit box
GetTextFromMemoryMappedFile();
return 0;
}
視窗初始化函數中進行記憶體對應檔的初始化:
void CIpcDlg::Initialize(){ m_edit_box.SetLimitText(dwMemoryFileSize - 1); m_hFileMapping = CreateFileMapping( INVALID_HANDLE_VALUE, // system paging file NULL, // security attributes PAGE_READWRITE, // protection 0, // high-order DWORD of size dwMemoryFileSize*sizeof(TCHAR), // low-order DWORD of size sMemoryFileName); // name DWORD dwError = GetLastError(); // if ERROR_ALREADY_EXISTS // this instance is not first (other instance created file mapping) if ( ! m_hFileMapping ) { MessageBox(_T("Creating of file mapping failed")); } else { m_pViewOfFile = MapViewOfFile( m_hFileMapping, // handle to file-mapping object FILE_MAP_ALL_ACCESS, // desired access 0, 0, 0); // map all file if ( ! m_pViewOfFile ) { MessageBox(_T("MapViewOfFile failed")); } // Now we have m_pViewOfFile memory block which is common for // all instances of this program } if ( dwError == ERROR_ALREADY_EXISTS ) { // Some other instance is already running, // get text from existing file mapping GetTextFromMemoryMappedFile(); }}
記憶體映射對象內容的讀取及顯示:
void CIpcDlg::GetTextFromMemoryMappedFile(){ if ( m_pViewOfFile ) { // read text from memory-mapped file TCHAR s[dwMemoryFileSize]; lstrcpy(s, (LPCTSTR) m_pViewOfFile); // Write text to edit box. // SetWindowText raises EN_CHANGE event and // OnChangeEditBox is called. Ensure that OnChangeEditBox // does nothing by setting m_bNotify to FALSE m_bNotify = FALSE; m_edit_box.SetWindowText(s); m_bNotify = TRUE; }}
在文字框的OnChangeEdtText事件中寫記憶體對應檔,並發wm_Message 進行通知:
void CIpcDlg::OnChangeEdtText() { if ( m_bNotify) // change is not done by SetWindowText { // write text to memory-mapped file if ( m_pViewOfFile ) { TCHAR s[dwMemoryFileSize]; m_edit_box.GetWindowText(s, dwMemoryFileSize); lstrcpy( (LPTSTR) m_pViewOfFile, s); // Notify all running instances that text was changed ::PostMessage(HWND_BROADCAST, wm_Message, (WPARAM) m_hWnd, 0); } } }
至此,一個最簡單的通過記憶體對應檔的進程通訊的例子就完成了,但是這裡存在一個問題:讀和寫之間的衝突沒有很好的解決,記憶體對應檔是一個共用的資源,多個進程讀寫必然存在同步的問題,也許在這個例子中不會出現什麼問題,但是實際項目中存在較高頻率的並發讀寫的情況下,如何進行同步是一個必須解決的問題,未完待續…
Windows進程通訊 -- 共用記憶體(1)