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


共享內存的方式原理就是將一份物理內存映射到不同進程各自的虛擬地址空間上,這樣每個進程都可以讀取同一份數據,從而實現進程通信。因為是通過內存操作實現通信,因此是一種最高效的數據交換方法。

共享內存在 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_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

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


免責聲明!

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



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