共用記憶體的使用
在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編譯通過。
//附:在dll中共用資料,在宿主進程中讀取共用的資料;以下程式在WinXP Pro + VC6 通過測試
//dll建立共用資料;
// create mapping file
HANDLE hSockSrvRecMap;
LPBYTE lpData;
char DataBuf[128];
hSockSrvRecMap = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE | SEC_COMMIT, 0, 1024, "SockSrvDataMap");
if (hSockSrvRecMap != NULL)
{
lpData = (LPBYTE)MapViewOfFile(hSockSrvRecMap, FILE_MAP_WRITE, 0, 0, 0);
if (lpData == NULL)
{
CloseHandle(hSockSrvRecMap);
hSockSrvRecMap = NULL;
}
}
for (int i=0; i<128; i++) {
DataBuf[i] = i;
}
memcpy(lpData,DataBuf,sizeof(DataBuf));
//宿主進程讀取共用資料;
m_hReceiveMap = OpenFileMapping(FILE_MAP_READ, FALSE, "SockSrvDataMap");
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;
}
for (int i=0; i<18; i++) {
TRACE("%d ",*(m_lpbReceiveBuf++));
}
TRACE("\n mapping data dump complete.\n");
// 不需要記憶體映射的時候要關閉,兩邊都需要關閉記憶體對應檔
if (m_lpbReceiveBuf != NULL)
{
UnmapViewOfFile(m_lpbReceiveBuf);
m_lpbReceiveBuf = NULL;
}
if (m_hReceiveMap != NULL)
{
CloseHandle(m_hReceiveMap);
m_hReceiveMap = NULL;
}