探討Win32應用進程間數據通訊技術

作者:周毅

一、 前言

衆所周知,Windows是一個多任務操作系統。所謂多任務,就是在同一時間可以運行多個應用程序,如當我使用WORD程序書寫本篇文章的同時運行MP3播放器欣賞阿杜的憂鬱情歌。多任務機制確實使PC 的世界變得更加豐富多彩,但是同時也帶給我們程序員許多技術難題,如本文將來探討的主題――Win32應用進程間數據通訊問題,就是這些技術難題中的比較典型的一種。

二、 問題的提出

Win32爲提高系統的安全性,在內存管理方面採用安全的獨立編址機制。在其中運行的每個進程都有自己的獨立的地址空間,一個進程不能訪問另外進程的地址空間。每個進程都擁有4GB的虛擬地址空間,多個進程可能具有相同的虛擬地址指針,但每個進程的指針所指向的物理內存則不同。

Win32這樣處理進程地址空間,卻是提高了系統的安全性,使進程數據得到有效的保護,確保只有本進程才能訪問到進程數據。這種機制很好地防止非法程序對進程數據的讀取和修改,但同時也阻止了合法程序對進行數據的存取,這也是造成了Win32應用進程間數據通訊難題的主要原因。

本文我們將重點探討解決Win32應用進程間數據通訊問題的有效方法,並同時編寫進程短信服務程序實例,以幫助大家理解本文介紹的相關技術。

三、 解決的方法

任何情況下的數據通訊問題,都需要解決兩個問題,即數據傳輸和通告機制。數據傳輸解決如何將進程A的數據通過一些中間環節正確無誤的傳送到進程B的內存中。通告機制解決進程A如何將數據準備狀態信息告之進程B,而進程B如何將數據需求信息告之進程A等問題。

1、 數據傳輸問題的解決方法

數據傳輸是解決應用進程間數據通訊問題的核心。我們可以採用Windows提供的多種數據通訊機制來實現數據傳輸的解決方案,但迄今爲止沒有哪一種方案是完美無缺的。因此,只有學習並瞭解了它們的優缺點後,才能在特定的情況下選擇最佳方案,以滿足最終的要求。下面我們將對常用的數據傳輸方法進行一下系統的介紹,讀者可根據應用的特點選擇最佳的方法。

● 使用內存映射文件

Windows中的內存映射文件的機制爲我們高效地操作文件提供了一種途徑,它允許我們在WIN32進程中保留一段內存區域,把目標文件映射到這段虛擬內存中。

實現內存映射文件可按以上步驟:

第一步,在發送數據的進程中使用CreateFileMapping 函數創建一個的共享內存,可以爲其取一個公共名稱。

HANDLECreateFileMapping(

HANDLEhFile,//文件的句柄, 爲OxFFFFFFFF爲創建進程間共享的對象。

LPSECURITY_ATTRIBUTESlpFileMappingAttributes,//安全屬性。

DWORDflProtect,//保護方式。

DWORDdwMaximumSizeHigh,//長度的最大值。

DWORDdwMaximumSizeLow, //長度的最小值。

LPCTSTRlpName//映射文件名稱。

);

其中,flProtect 可以是PAGE_READONLY或是PAGE_READWRITE。如果多進程都對同一共享內存進行寫訪問,則必須保持相互間同步。映射文件還可以指定 PAGE_WRITECOPY標誌,可以保證其原始數據不會遭到破壞,同時允許其他進程在必要時自由地操作數據的拷貝。

在創建文件映射對象後使用可以調用MapViewOfFile函數映射到本進程的地址空間內。

下面說明創建一個名爲MySharedMem的長度爲4096字節的有名映射文件:

HANDLEhMySharedMapFile=CreateFileMapping((HANDLE)0xFFFFFFFF),

NULL,PAGE_READWRITE,0,0x1000,“MySharedMem”);

並映射緩存區視圖:

LPSTRpszMySharedMapView=(LPSTR)MapViewOfFile(hMySharedMapFile,

FILE_MAP_READ|FILE_MAP_WRITE,0,0,0);

其他進程訪問共享對象,需要獲得對象名並調用OpenFileMapping函數。

HANDLEhMySharedMapFile=OpenFileMapping(FILE_MAP_WRITE,

FALSE,“MySharedMem");

一旦其他進程獲得映射對象的句柄,可以像創建進程那樣調用MapViewOfFile函數來映射對象視圖。用戶可以使用該對象視圖來進行數據讀寫操作,以達到數據通訊的目的。

當用戶進程結束使用共享內存後,調用UnmapViewOfFile函數以取消其地址空間內的視圖。

● 使用WM_COPYDATA消息

當一個應用程序向另一個應用程序傳遞數據時所發出的消息時,Windows將會產生WM_COPYDATA消息。我們可以將數據附在該消息中傳送給另外的進程,一般情況下,可以將源進程中的數據存在一個結構體中,再將該結構的指針做爲消息的參數進行數據傳遞。聰明的讀者一定很問,爲什麼偏偏只用 WM_COPYDATA,如果定義一個公共的消息是否也可以進行數據傳遞呢?我的回答是NO,僅僅只有WM_COPYDATA才能完成這個工作。

上面我們說過,我們是通過使用消息函數的參數來傳遞數據結構的指針,進而實現數據的傳送的。對於一般的消息處理函數而說,它們僅僅只能通過參數獲得源進程的數據存放的虛擬地址,如果以此地址訪問的數據是自己進程地址空間的數據,不可能訪問到源進程的地址空間。所以使用一般的消息來傳遞數據的說,只能傳遞數據所在內存的地址值,而不能訪問到真正的數據內容。消息WM_COPYDATA則不同,它不僅可以傳遞數據所在內存的地址,而且還能訪問(僅僅只讀,不能寫)數據內容,其中真正的數據傳輸過程由Windows內部實現,其實Windows內部也是通過內存映射機制的實現的,不過對開發者來說是透明的。

● 使用DDE(動態數據交換)

每個Windows程序員都不會對DDE(動態數據交換)感到陌生,它是最早的基於Windows的數據交換方法,有三種方式可供選擇:冷連接、溫連接和熱連接。一般都是由客戶端向服務器端發出連接申請,並且必須指明服務器端的名字和標題。在連接建立後,數據可以雙向流動。典型的例子如抓圖軟件 SnagIt,它提供了DDE接口,能夠讓其它應用程序來控制它。DDE是完全向後兼容的,從16位平臺轉到32位,源代碼幾乎不用修改。

DDE 還有網絡功能。使用過Win311ForWorkgroup的人大概都還記得,它自帶一個非常吸引人的小程序“Chat”,能使兩臺計算機通過網絡實時交談,這在當時幾乎是一項創舉。可是很少有人知道“Chat”使用的是一種特殊的DDE,即NetDDE。它的基本工作原理仍然是DDE,但它能使一臺計算機向在同一個網絡中的另一臺終端發消息,而不像普通DDE只能侷限在同一臺機器上。與其它的數據交換方式相比,DDE已不夠先進,而且Microsoft 也不再積極支持DDE,所以它的前景不被看好。

除以上三者方法以外,還有使用Mailslot/Pipe、共享DLL、Clipboard、DCOM等方法來實現數據傳輸。

2、 通告機制的解決方法

通告機制一般都採用Windows的消息機制來實現。但它與一般在進程內定義的消息有所不同,因這個消息是要傳送到進程外的,當進程通過 SendMessage函數將自定義的消息發送給另外進程時,該進程消息處理器必需能夠識別出此消息。爲此,數據通訊雙方進程都必須先進行消息註冊。消息註冊可使用Windows API RegisterWindowsMessage()函數來實現。

一般情況下,數據通訊雙方進程按以下幾個步驟來實現通告:

源進程:

//定義消息ID。

const UINT ugMsg;

//註冊消息。

ugMsg = :: RegisterWindowsMessage(“MyNotify”);

//尋找目標進程中接收消息的窗口。

CWnd*pWndRecv=FindWindow(lpClassName,“Receive”);

//發送消息。

PWndRecv->SendMessage(ugMsg,0,0);

目標進程:

//定義消息ID。

const UINT ugMsg;

//註冊消息。

ugMsg = :: RegisterWindowsMessage(“MyNotify”);

另外,使用MFC消息映射機制創建一個消息處理函數,在消息處理函數中使用上面介紹的數據傳輸方法操作數據。

四、 實現實例

實踐是檢驗真理的標準,下面我們將創建一個實例來檢驗以上所述的原理。該實例由兩應用進程組成,即Send進程和Receive進程。實例模擬Windows信使服務,將Send進程中輸入的字符串信息通過進程間通訊機制傳送到Receive進程。

由於在這兩個進程間需要進行大量的字符串數據交換,並且對通訊的實時性要求不高,所以選擇使用內存映射文件(或稱共享內存)方法來完成進程間的數據傳輸工作。內存映射文件方式的主要優點是將共享的虛擬內存空間使用類似文件的模式來管理,使其概念清晰、實現簡單以及交換數據量大,但是其通訊效率較低,並且在網絡環境中可靠性較差。當用戶在Send進程中輸入發送字符串後,Send進程首先將這些字符串拷貝到共享內存空間中,接着向Receive進程發送自定義的全局消息(或廣播消息)通告Receive進程已有數據發送過來。Receive進程使用消息處理函數來監偵接收數據的狀態,一旦有通告消息,則將從共享內存中拷貝字符串到進程變量中,並顯示於消息框中。

實例項目創建步驟如下:

1、啓動Visual C++,創建空的工程SMS。

2、插入新項目Send,選擇對話框模式。

3、編輯Send項目資源IDD_SEND_DIALOG,加入文本框IDC_EDIT1和按鈕IDC_SEND,並使用ClassWizard爲IDC_EDIT1加入CString類型的成員變量m_szSend,爲IDC_SEND按鈕添加CLICK事件。

4、打開SendDlg.h文件,在CsendDlg類聲明中加入如下私有變量:

private:

HANDLE hMySharedMapFile;

LPSTR pszMySharedMapView;

UINT ugMsg;

5、打開SendDlg.cpp文件,在 CSendDlg::OnInitDialog()的return TRUE語句前加入如下代碼:

//創建內存映射文件。

hMySharedMapFile=CreateFileMapping((HANDLE)0xFFFFFFFF,NULL,

PAGE_READWRITE,0,0x1000,"MySharedMem");

//映射到進程內存中。

pszMySharedMapView=(LPSTR)MapViewOfFile(hMySharedMapFile,

FILE_MAP_READ|FILE_MAP_WRITE,0,0,0);

//註冊消息。

ugMsg=::RegisterWindowMessage("SMS");

在Send按鈕CLICK事件處理函數中加入如下代碼:

void CSendDlg::OnSend()

{

// TODO: Add your control notification handler code here

UpdateData(TRUE);

strcpy(pszMySharedMapView,(LPTSTR)(LPCTSTR)m_szSend);

//尋找目標進程中接收消息的窗口,#32770爲Receive窗口類名。

CWnd* pWndRecv=FindWindow("#32770","Receive");

//發送消息。

pWndRecv->SendMessage(ugMsg,0,0);

//或使用廣播發送消息:

//::SendMessage(HWND_BROADCAST,ugMsg,0,0);

}

6、編譯Send項目。

7、在工程SMS中新添項目Receive,選擇對話框模式。

8、編輯Send項目資源IDD_RECEIVE_DIALOG,加入文本框IDC_RECEIVE,並使用ClassWizard爲IDC_RECEIVE加入CString類型的成員變量m_szReceive。

9、打開ReceiveDlg.h文件,在CreceiveDlg類聲明中加入如下私有變量:

private:

HANDLE hMySharedMapFile;

LPSTR pszMySharedMapView;

10、打開ReceiveDlg.cpp文件,首先加入全局變量聲明如下:

UINT ugMsg;

11、加入全局ugMsg消息處理機制代碼,首先打開ReceiveDlg.h文件, //{{AFX_MSG(CReceiveDlg)與//}}AFX_MSG代碼之間插入如下代碼:

afx_msg LRESULT OnReceiveSMS(WPARAM wParam, LPARAM lParam);

接着打開ReceiveDlg.cpp文件, //{{AFX_MSG_MAP(CReceiveDlg)與//{{AFX_MSG_MAP代碼之間插入如下代碼:

ON_REGISTERED_MESSAGE(ugMsg,OnReceiveSMS )

最後,在ReceiveDlg.cpp文件中加入消息處理函數,代碼如下:

LRESULT CReceiveDlg::OnReceiveSMS(WPARAM wParam,LPARAM lParam)

{

m_szReceive = (LPCTSTR)pszMySharedMapView;

UpdateData(FALSE);

AfxMessageBox("Receive SMS OK!");

return 0;

}

12、打開ReceiveDlg.cpp文件,在CReceiveDlg::OnInitDialog()函數的return TRUE代碼前插入如下代碼:

//打開由Send進程創建的內存映射文件。

hMySharedMapFile=OpenFileMapping(FILE_MAP_WRITE,FALSE,

"MySharedMem");

//映射到進程內存中。

pszMySharedMapView=(LPSTR)MapViewOfFile(hMySharedMapFile,

FILE_MAP_READ|FILE_MAP_WRITE,0,0,0);

//註冊消息。

ugMsg=::RegisterWindowMessage("SMS");

13、編譯Receive項目。

14、測試兩個程序的運行情況。首先運行Send.exe程序,再運行Receive.exe程序,在Send中的短信輸入框中輸入“您好!我是中國人。”後,在Receiver中的短信接受框馬上就會出現這句話。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章