[VB6] 進程通信 - 內存共享

共享內存實現進程間大數據的交換

2003-08-11 11:21 來源:yesky 作者:中國電波傳播研究所青島分所郎銳 責任編輯:方舟yesky 評論(2)
引言
  進程間的數據交換和共享是一種非常重要和實用的技術。大、中型軟件的開發設計多是由眾多程序設計人員的合作完成,通常一個程序設計人員只負責其中一個或幾個模塊的開發,這些模塊可以是動態鏈接庫也可以是應用程序或是其他形式的程序組件。這些獨立開發出來的程序模塊最終需要作為一個整體來運行,即組成一個系統,在系統運行期間這些模塊往往需要頻繁地進行數據交換和數據共享,對於動態鏈接庫同其主調應用程序之間的數據交換是非常容易實現的,但是在兩個應用程序之間或是動態鏈接庫同其主調應用程序之外的其他應用程序進行數據交換就比較困難了。尤其是在交換數據量過大、交換過於頻繁的情況下更是難以實現,本文即對此展開討論,並提出了一種通過共享內存來實現進程見大數據量快速交換的一種方法。
  通訊方式的比較和選擇
  進程間通訊的方式有很多,常用的有共享內存、命名管道和匿名管道、發送消息等幾種方法來直接完成,另外還可以通過socket口、配置文件和註冊表等來間接實現進程間數據通訊任務。以上這幾種方法各有優缺點,具體到在進程間進行大數據量數據的快速交換問題上,則可以排除使用配置文件和註冊表的方法;另外,由於管道和socket套接字的使用需要有網卡的支持,因此也可以不予考慮。這樣,可供選擇的通訊方式只剩下共享內存和發送消息兩種。由於數據量比較大,這樣在使用消息進行通訊時就無法通過消息參數將數據直接攜帶到接收方,只能以地址傳送的方式進行。當一個應用程序向另一個應用程序發送數據時將會發出WM_COPYDATA系統消息,因此可以考慮通過向消息隊列插入WM_COPYDATA消息的方法來實現數據在進程間的拷貝。
  在使用WM_COPYDATA消息時,由第一個消息參數指定發送窗口的句柄,第二個消息參數則為一同數據相關的數據結構COPYDATASTRUCT的指針,此結構原形聲明如下:
typedef struct tagCOPYDATASTRUCT {
DWORD dwData;
DWORD cbData;
PVOID lpData;
} COPYDATASTRUCT;
  其中,只需將待發送數據的首地址賦予lpData、並由cbData指明數據塊長度即可。消息發出後,接收方程序在WM_COPYDATA消息的響應函數中通過隨消息傳遞進來的第二個參數完成對數據塊的接收。但是在使用WM_COPYDATA消息時,只能用SendMessage()函數發送而不能使用PostMessage(),這兩個函數雖然功能非常相似都是負責向指定的窗口發送消息,但是SendMessage()函數發出消息後不是馬上返回,而是在接收方的消息響應函數處理完之後才能返回,並能夠得到返回結果。在此期間發送方程序將被阻塞,SendMessage()後面的語句不能被繼續執行。而PostMessage()函數在發出消息後馬上返回,其後語句能夠被立即執行,但是無法獲取消息的執行結果。可見,在交換數據量較大的情況下實現數據頻繁而又快速的交換用發送WM_COPYDATA消息的方法也是不合適的,當數據傳輸過於頻繁時將有可能導致數據的丟失。
  比之以上幾種進程間通訊方法,共享內存有著明顯的優勢。共享內存是通過直接操作內存映射文件來進行的,而內存映射文件又是進行單機數據共享的最低層機制,前面幾種數據交換方式在低層都是通過內存映射文件來進行的。因此使用共享內存可以以較小的開銷獲取較高的性能,是進行大數據量數據快速交換的最佳方案。


共享內存的使用
  在Windows操作系統下,任何一個進程不允許讀取、寫入或是修改另一個進程的數據(包括變量、對象和內存分配等),但是在某個進程內創建的文件映射對象的視圖卻能夠為多個其他進程所映射,這些進程共享的是物理存儲器的同一個頁面。因此,當一個進程將數據寫入此共享文件映射對象的視圖時,其他進程可以立即獲取數據變更情況。為了進一步提高數據交換的速度,還可以採用由系統頁文件支持的內存映射文件而直接在內存區域使用,顯然這種共享內存的方式是完全可以滿足在進程間進行大數據量數據快速傳輸任務要求的。下面給出在兩個相互獨立的進程間通過文件映射對象來分配和訪問同一個共享內存塊的應用實例。在本例中,由發送方程序負責向接收方程序發送數據,文件映射對象由發送方創建和關閉,並且指定一個唯一的名字供接收程序使用。接收方程序直接通過這個唯一指定的名字打開此文件映射對象,並完成對數據的接收。
  在發送方程序中,首先通過CreateFileMapping()函數創建一個內存映射文件對象,如果創建成功則通過MapViewOfFile()函數將此文件映射對象的視圖映射進地址空間,同時得到此映射視圖的首址。可見,共享內存的創建主要是通過這兩個函數完成的。這兩個函數原形聲明如下:
HANDLE CreateFileMapping(HANDLE hFile,
LPSECURITY_ATTRIBUTES lpFileMappingAttributes,
DWORD flProtect,
DWORD dwMaximumSizeHigh,
DWORD dwMaximumSizeLow,
LPCTSTR lpName);
LPVOID MapViewOfFile(HANDLE hFileMappingObject,
DWORD dwDesiredAccess,
DWORD dwFileOffsetHigh,
DWORD dwFileOffsetLow,
DWORD dwNumberOfBytesToMap);
  CreateFileMapping()函數參數hFile指定了待映射到進程地址空間的文件句柄,如果為無效句柄則系統會創建一個使用來自頁文件而非指定磁盤文件存儲器的文件映射對象。很顯然,在本例中為了數據能快速交換,需要人為將此參數設定為INVALID_HANDLE_VALUE;參數flProtect設定了系統對頁面採取的保護屬性,由於需要進行讀寫操作,因此可以設置保護屬性PAGE_READWRITE;雙字型參數dwMaximumSizeHigh和dwMaximumSizeLow指定了所開闢共享內存區的最大字節數;最後的參數lpName用來給此共享內存設定一個名字,接收程序可以通過這個名字將其打開。MapViewOfFile()函數的參數hFileMappingObject為CreateFileMapping()返回的內存文件映像對象句柄;參數dwDesiredAccess再次指定對其數據的訪問方式,而且需要同CreateFileMapping()函數所設置的保護屬性相匹配。這裡對保護屬性的重複設置可以確保應用程序能更多的對數據的保護屬性進行有效控制。下面給出創建共享內存的部分關鍵代
hRecvMap = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE | SEC_COMMIT, 0, 1000000, "DataMap");
if (hRecvMap != NULL)
{
lpData = (LPBYTE)MapViewOfFile(hRecvMap, FILE_MAP_WRITE, 0, 0, 0);
if (lpData == NULL)
{
CloseHandle(hRecvMap);
hRecvMap = NULL;
}
}
// 通知接收程序內存文件映射對象的視圖已經打開
HWND hRecv = ::FindWindow(NULL, DECODE_PROGRAMM);
if (hRecv != NULL)
::PostMessage(hRecv, WM_MAP_OPEN, 0, 0);
  數據的傳送實際是將數據從發送方寫到共享內存中,然後由接收程序及時從中取走即可。數據從發送方程序寫到共享內存比較簡單,只需用memcpy()函數將數據拷貝過去,關鍵在於能及時通知接收程序數據已寫入到共享內存,並讓其即使取走。在這裡仍採取消息通知的方式,當數據寫入共享內存後通過PostMessage()函數向接收方程序發送消息,接收方在消息響應函數中完成對數據的讀取:
// 數據複製到共享內存
memcpy(lpData, RecvBuf, sizeof(RecvBuf));
// 通知接收方接收數據
HWND hDeCode = ::FindWindow(NULL, DECODE_PROGRAMM);
if (hDeCode != NULL)
::PostMessage(hDeCode, WM_DATA_READY, (WPARAM)0, (LPARAM)sizeof(RecvBuf));
  當數據傳輸結束,即將退出程序時,需要將映射進來的內存文件映射對象視圖卸載和資源的釋放等處理。這部分工作主要由UnmapViewOfFile()和CloseHandle()等函數完成:
HWND hDeCode = ::FindWindow(NULL, DECODE_PROGRAMM);
if (hDeCode != NULL)
::PostMessage(hDeCode, WM_MAP_CLOSE, 0, 0);
if (lpData != NULL)
{
UnmapViewOfFile(lpData);
lpData = NULL;
}
if (hRecvMap != NULL)
{
CloseHandle(hRecvMap);
hRecvMap = NULL;
}
  在接收程序中,在收到由發送放發出的WM_MAP_OPEN消息後,由OpenFileMapping()函數打開由名字"DataMap"指定的文件映射對象,如果執行成功,繼續用MapViewOfFile()函數將此文件映射對象的視圖映射到接收應用程序的地址空間並得到其首址:
m_hReceiveMap = OpenFileMapping(FILE_MAP_READ, FALSE, "DataMap");
if (m_hReceiveMap == NULL)
return;
m_lpbReceiveBuf = (LPBYTE)MapViewOfFile(m_hReceiveMap,FILE_MAP_READ,0,0,0);
if (m_lpbReceiveBuf == NULL)
{
CloseHandle(m_hReceiveMap);
m_hReceiveMap=NULL;
}
  當發送方程序將數據寫入到共享內存後,接收方將收到消息WM_DATA_READY,在響應函數中將數據從共享內存複製到本地緩存中,再進行後續的處理。同發送程序類似,在接收程序數據接收完畢後,也需要用UnmapViewOfFile()、CloseHandle()等函數完成對文件視圖等打開過資源的釋放:
// 從共享內存接收數據
memcpy(RecvBuf, (char*)(m_lpbReceiveBuf), (int)lParam);
……
// 程序退出前資源的釋放
UnmapViewOfFile(m_lpbReceiveBuf);
m_lpbReceiveBuf = NULL;
CloseHandle(m_hReceiveMap);
m_hReceiveMap = NULL;
  小結
  經實際測試,使用共享內存在處理大數據量數據的快速交換時表現出了良好的性能,在數據可靠性等方面要遠遠高於發送WM_COPYDATA消息的方式。這種大容量、高速的數據共享處理方式在設計高速數傳通訊類軟件中有著很好的使用效果。本文所述代碼在Windows 2000下由Microsoft Visual C++ 6.0編譯通過。



  1. Option Explicit

  2. Private Declare Function CreateFileMapping Lib "kernel32" Alias "CreateFileMappingA" (ByVal hfile As Long, ByVal lpFileMappingAttributes As Long, ByVal flProtect As Long, ByVal dwMaximumSizeHigh As Long, ByVal dwMaximumSizeLow As Long, ByVal lpName As String) As Long

  3. Private Declare Function MapViewOfFile Lib "kernel32" (ByVal hFileMappingObject As Long, ByVal dwDesiredAccess As Long, ByVal dwFileOffsetHigh As Long, ByVal dwFileOffsetLow As Long, ByVal dwNumberOfBytesToMap As Long) As Long
  4. Private Declare Function UnmapViewOfFile Lib "kernel32" (lpBaseAddress As Any) As Long

  5. Private Declare Function lstrcpyn Lib "kernel32" Alias "lstrcpynA" (DesStr As Any, SrcStr As Any, ByVal Maxlen As Long) As Long

  6. Private Declare Function CloseHandle Lib "kernel32" (ByVal hObject As Long) As Long

  7. Private Const FILE_MAP_WRITE = &H2
  8. Private Const PAGE_READWRITE = 4&

  9. Private hMemShare As Long '映射文件句柄
  10. Private lShareData As Long '映射地址

  11. Private sData As String

  12. Private Sub cmdExit_Click()
  13.     Unload Me
  14. End Sub

  15. Private Sub cmdReadMem_Click()
  16.     sData = String(&H100, &H0)
  17.     Call lstrcpyn(ByVal sData, ByVal lShareData, &H100)
  18.     Text_Memory.Text = sData
  19. End Sub

  20. Private Sub cmdWriteMem_Click()
  21.     sData = Text_Memory.Text
  22.     Call lstrcpyn(ByVal lShareData, ByVal sData, &H100)
  23. End Sub

  24. Private Sub Form_Load()

  25.     Text_Memory.Text = ""
  26.     
  27.     hMemShare = CreateFileMapping(&HFFFFFFFF, 0, PAGE_READWRITE, 0, &H100, "MappingMem")
  28.     '創建一個想共享的文件數據句柄
  29.     If hMemShare = 0 Then MsgBox "創建內存映射文件失敗!", vbCritical, "錯誤"

  30.     lShareData = MapViewOfFile(hMemShare, FILE_MAP_WRITE, 0, 0, 0)
  31.     
  32.     If lShareData = 0 Then MsgBox "為映射文件對像創建視失敗!", vbCritical, "錯誤"

  33.     sData = String(&H100, vbNullChar)

  34.     Call lstrcpyn(ByVal sData, ByVal lShareData, &H100)
  35.     
  36.     Text_Memory.Text = sData
  37.     
  38. End Sub

  39. Private Sub Form_Unload(Cancel As Integer)
  40.     If lShareData <> 0 Then
  41.         Call UnmapViewOfFile(ByVal lShareData)
  42.         lShareData = 0
  43.     End If
  44.     
  45.     If hMemShare <> 0 Then
  46.         Call CloseHandle(hMemShare)
  47.         hMemShare = 0
  48.     End If
  49. End Sub

本月最夯