共享內存的方式原理就是將一份物理內存映射到不一樣進程各自的虛擬地址空間上,這樣每一個進程均可以讀取同一份數據,從而實現進程通訊。由於是經過內存操做實現通訊,所以是一種最高效的數據交換方法。安全
共享內存在 Windows 中是用 FileMapping 實現的,從具體的實現方法上看主要經過如下幾步來實現:併發
一、調用 CreateFileMapping 建立一個內存文件映射對象;app
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;
二、調用 MapViewOfFile 映射到當前進程的虛擬地址上;this
若是調用CreateFileMapping成功,則調用MapViewOfFile函數,將內存映射文件映射到進程的虛擬地址中;spa
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表示映射整個文件映射對象。
三、在接收進程中打開對應的內存映射對象指針
在數據接收進程中,首先調用OpenFileMapping()函數打開一個命名的文件映射內核對象,獲得相應的文件映射內核對象句柄hFileMapping;若是打開成功,則調用MapViewOfFile()函數映射對象的一個視圖,將文件映射內核對象hFileMapping映射到當前應用程序的進程地址,進行讀取操做。(固然,這裏若是用CreateFileMapping也是能夠獲取對應的句柄)code
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 :指定要打開的文件映射對象名稱。
四、進行內存映射文件的讀寫對象
一旦MapViewOfFile調用成功,就能夠像讀寫本進程地址空間的內存區同樣,進行內存的讀寫操做了。blog
//讀操做: 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); }
五、清理內核對象
在用完後,要取消本進程地址空間的映射,並釋放內存映射對象。
//取消本進程地址空間的映射; UnmapViewOfFile(pLocalMem); pLocalMem=NULL; //關閉文件映射內核文件 CloseHandle(hFileMapping);
六、簡單例子:
下面寫一個簡單的例子來講明:
例子:在一個進程的文本對話框中輸入文本,同時在另外一個進程的文本對話框中顯示以前輸入的內容。
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_MAP END_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); } } }
至此,一個最簡單的經過內存映射文件的進程通訊的例子就完成了,可是這裏存在一個問題:讀和寫之間的衝突沒有很好的解決,內存映射文件是一個共享的資源,多個進程讀寫必然存在同步的問題,也許在這個例子中不會出現什麼問題,可是實際項目中存在較高頻率的併發讀寫的狀況下,如何進行同步是一個必須解決的問題,未完待續…