Windows進程通訊 -- 共享內存(1)

共享內存的方式原理就是將一份物理內存映射到不一樣進程各自的虛擬地址空間上,這樣每一個進程均可以讀取同一份數據,從而實現進程通訊。由於是經過內存操做實現通訊,所以是一種最高效的數據交換方法。安全

共享內存在 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);    
        }
    }
    
}

image

至此,一個最簡單的經過內存映射文件的進程通訊的例子就完成了,可是這裏存在一個問題:讀和寫之間的衝突沒有很好的解決,內存映射文件是一個共享的資源,多個進程讀寫必然存在同步的問題,也許在這個例子中不會出現什麼問題,可是實際項目中存在較高頻率的併發讀寫的狀況下,如何進行同步是一個必須解決的問題,未完待續…

相關文章
相關標籤/搜索