windows 进程通信(使用DDE)
動態數據交換(Dynamic Data Exchange,DDE)也是一種進程間通信形式。它最早是隨著Windows
3.1由美國微軟公司提出的。當前大部分軟件仍就支持DDE,但近10年間微軟公司已經停止發展DDE技術,只保持對DDE技術給予兼容和支持。但我們仍然可以利用DDE技術編寫自己的數據交換程序。
3.8.1? 使用DDE技術通信原理
兩個同時運行的程序間通過DDE方式交換數據時是客戶/服務器關系,一旦客戶和服務器建立起來連接關系,則當服務器中的數據發生變化后就會馬上通知客戶。通過DDE方式建立的數據連接通道是雙向的,即客戶不但能夠讀取服務器中的數據,而且可以對其進行修改。
DDE和剪貼板一樣既支持標準數據格式(如文本、位圖等),又可以支持自定義的數據格式。但它們的數據傳輸機制卻不同,一個明顯區別是剪貼板操作幾乎總是 用作對用戶指定操作的一次性應答,如從菜單中選擇粘貼命令。盡管DDE也可以由用戶啟動,但它繼續發揮作用,一般不必用戶進一步干預。
DDE有三種數據交換方式,即
(1)冷連接(Cool Link):數據交換是一次性數據傳輸,與剪貼板相同。當服務器中的數據發生變化后不通知客戶,但客戶可以隨時從服務器讀寫數據;
(2)溫連接(Warm Link):當服務器中的數據發生變化后馬上通知客戶,客戶得到通知后將數據取回;
(3)熱連接(Hot Link):當服務器中的數據發生變化后馬上通知客戶,同時將變化的數據直接送給客戶。
DDE 客戶程序向DDE 服務器程序請求數據時,它必須首先知道服務器的名稱(即DDE
Service名)、DDE主題名稱(Topics名),還要知道請求哪一個數據項的項目名稱(Items名)。DDE
Service名應該具有唯一性,否則容易產生混亂。通常DDE
Service就是服務器的程序名稱,但不是絕對的,它是由程序設計人員在程序內部設定好的,并不是通過修改程序名稱就可以改變的。Topics名和Items名也是由DDE
Service在其內部設定好的,所有服務程序的Service名、Topics名都是注冊在系統中,當一個客戶向一個服務器請求數據時,客戶必須向系統 報告服務器的Service名和Topics名。只有當Service名、Topics名與服務器內部設定的名稱一致時,系統才將客戶的請求傳達給服務 器。
當服務名和Topics名相符時,服務器馬上判斷Items名是否合法。如果請求的Item名是服務器中的合法數據項,服務器即建立此項連接,建立連接的數據發生數值變化后,服務器會及時通知客戶。一個服務器可以有多個Topics名,Items名的數量也不受限制。
DDE交換可以發生在單機或網絡中不同計算機的應用程序之間。開發者還可以定義定制的DDE數據格式,進行應用程序之間特別目的IPC,它們有更緊密耦合 的通信要求。大多數基于Windows的應用程序都支持DDE。但DDE有個明顯的缺點就是,通信效率低下,當通信量較大時數據刷新速度慢,在數據較少時 DDE較實用。
3.8.2? 如何使用DDEML編寫程序
早期的DDE基于消息機制,應用程序間的消息傳遞需程序員調度。由于DDE消息通信牽涉的操作細節頗多,實現完全的DDE協議不是非常容易的事情,而且不同的開發者對協議的解釋也略有不同。為了使用方便起見,微軟提供DDE管理庫(The
DDE Management Library,
簡稱DDEML)。DDEML專門協調DDE通信,給DDE應用程序提供句柄字符串和數據交換的服務,消除了早期由于DDE協議不一致所引起的問題。
使用DDEML開發的應用程序(客戶/服務器)無論在運行一致性方面,還是在程序相互通信方面,性能均優于沒有使用DDEML的應用程序。而且DDEML的應用使得開發支持DDE的應用程序容易了許多,因為
DDEML(這是個
DLL)擔起了內務府總管的工作。使用DDEML后,實際上客戶和服務器之間的多數會話并不是直達對方的,而是經由DDEML中轉,即用Callback函數處理DDE交易(Transaction),而早期的消息通信是直接的。
在調用其他DDEML函數前,客戶/服務器必須調用DdeInitialize()函數,以獲取實例標識符,注冊DDE
Callback函數,并為Callback函數指定事務過濾。對于服務器,在使用DdeInitialize()初始化后,調用 DdeCreateStringHandle()建立Service名、Topics名和Items名等標識的句柄,再通過DdeNameService ()在操作系統中注冊服務器的名字。根據這些句柄,客戶就可以使用它提供的DDE服務了。
為了執行某個DDE任務,許多DDEML函數需要獲得字符串的訪問權。例如:一個客戶在調用DdeConnect()函數來請求同服務器建立會話時,必須 指定Service名和Topics名。可以通過調用DdeCreateStringHandle()函數來獲取特定字符串句柄。例如:
HSZ hszServName = DdeCreateStringHandle(idInst,"MyServer",CP_WINANSI);
HSZ hszSysTopic = DdeCreateStringHandle(idInst,SZDDESYS_TOPIC,CP_WINANSI);
一個應用程序的DDE回調函數在大多DDE事務中接收多個字符串句柄。比如:在XTYP_REQUEST事務處理期間,一個DDE
服務器接收兩個字符串句柄:一個標識Topics名字符串,另一個標識Items名字符串。可以通過調用DdeQueryString()函數來獲取相應于字符串句柄的字符串長度,并且復制字符串到應用程序定義的buffer中。例如:
DWORD idInst;
DWORD cb;
HSZ hszServ;
PSTR pszServName;
cb = DdeQueryString(idInst, hszServ, (LPSTR) NULL, 0,? CP_WINANSI) + 1;
pszServName = (PSTR) LocalAlloc(LPTR, (UINT) cb);
DdeQueryString(idInst, hszServ, pszServName, cb, CP_WINANSI);
根據微軟MSDN,現有的基于消息DDE協議的應用程序與DDEML應用程序是相容的,也就是說,基于消息通信的DDE應用程序可以與DDEML應用程序 對話和交易。在使用DDEML時,必須在源程序文件中包括ddeml.h頭文件,連接user32.lib文件,并保證ddeml.dll文件正確的系統 路徑。
使用DDE通信的實例
由上面的介紹可知,可以編寫基于消息DDE應用程序,也可以編寫應用DDEML的應用程序。對于前者,實現的方法較復雜,這里不做介紹。這里介紹一個應用DDEML編寫的DDE通信實例。
為了便于管理,這里把這個程序封裝成一個CMyDde類,下面介紹這個類。CMyDde類頭文件如下:
// DDE.h: 定義CMyDde類
//
#ifndef _DDE_H_INCLUDED
#define _DDE_H_INCLUDED
#include <ddeml.h>
class CMyDde?
{
public:
??? CMyDde();
??? ~CMyDde();
??? // 靜態回調成員函數
??? static HDDEDATA CALLBACK DdeCallback(UINT iType,UINT iFmt,
??????? HCONV hConv,HSZ hsz1,HSZ hsz2,
??????? HDDEDATA hData,DWORD dwData1,DWORD data2);
??? void DdeCall(UINT iType, LPCSTR szSvr,LPCSTR szTopic,LPCSTR szAtom);
??? void DdeServer(CString strReply);
??? void DdeClient(CString strRequest);
???
??? CString GetReply()?? { return m_strReply;}
??? CString GetRequest() { return m_strRequest;}
private:
??? static CMyDde* fakeThis;
??? DWORD??? idInst;
??? CString? AppName;
??? CString m_strReply;
??? CString m_strRequest;
};
#endif
其中包含了ddeml.h頭文件,DdeCallback()為static回調函數。之所以使用static,是因為DdeInitialize()函數的需要,否則編譯會出錯。
對于服務程序,使用類中的DdeServer()函數。在這個函數中用DdeInitialize()調用回調函數DdeCallback(),注冊服務 名MyDDEService,以便客戶程序與服務程序取得聯系。在DdeInitialize()中設置事務過濾,例如以下的DdeServer()函數 中,在DdeInitialize()中設置CBF_FAIL_POKES,表示XTYP_
POKES事件將被過濾掉。DdeServer()函數的代碼?? 如下:
void CMyDde::DdeServer(CString strReply)
{
??? m_strReply=strReply;
??? fakeThis=this;
??? // 建立DDE
??? DdeInitialize(&idInst,DdeCallback,APPCLASS_STANDARD|
??????? CBF_FAIL_ADVISES|
??????? CBF_FAIL_POKES|
??????? CBF_SKIP_REGISTRATIONS|
??????? CBF_SKIP_UNREGISTRATIONS,0L);
??? // 注冊服務名MyDDEService,使該程序作為DDE服務器
??? AppName="MyDDEService";
??? HSZ hszService=DdeCreateStringHandle(idInst,AppName,0);
??? DdeNameService(idInst,hszService,NULL,DNS_REGISTER);
}
回調函數(Callback
function)大量用于Windows的系統服務,通過它,程序員可以安裝設備驅動程序和消息過濾系統,以控制Windows的有效使用。以下是DDE服務程序的回調函數源代碼:
HDDEDATA CALLBACK CMyDde::DdeCallback(UINT iType,
??????????????? UINT iFmt,HCONV hConv,
??????????????? HSZ hsz1,?? // Topic.
??????????????? HSZ hsz2,?? // atom.
??????????????? HDDEDATA hData,DWORD dwData1,DWORD data2)
{
??? char szBuffer[100];
??? switch(iType)
??? {
??? // 建立交易連接
??? case XTYP_CONNECT:
??????? // 獲得應用名
??????? DdeQueryString(fakeThis->idInst,hsz2,
??????????? szBuffer,sizeof(szBuffer),0);
???????
??????? // 如果此應用不能被此服務器支持,返回NULL
??????? if(strcmp(szBuffer,fakeThis->AppName))? return NULL;
??????? // 獲得topic名
??????? DdeQueryString(fakeThis->idInst,hsz1,
??????????? szBuffer,sizeof(szBuffer),0);
???????
??????? // 如果連接成功,返回1
??????? return (HDDEDATA)1;
???????
??? case XTYP_REQUEST:
??????? // 獲得topic名
??????? DdeQueryString(fakeThis->idInst,hsz1,
??????????? szBuffer,sizeof(szBuffer),0);
???????
??????? if(strcmp(szBuffer,"query")==0)
??????? {
??????????? // 獲得Item 名
??????????? DdeQueryString(fakeThis->idInst,hsz2,
??????????????? szBuffer,sizeof(szBuffer),0);
???????
??????????? strcpy(szBuffer,fakeThis->m_strReply);
??????????? return DdeCreateDataHandle(fakeThis->idInst,
??????????????? (LPBYTE)szBuffer,sizeof(szBuffer),0,hsz2,CF_TEXT,0);
??????? }
??????? break;
???????
??? case XTYP_EXECUTE:
??????? // 獲得topic名
??????? DdeQueryString(fakeThis->idInst,hsz1,
??????????? szBuffer,sizeof(szBuffer),0);
??????? if(strcmp(szBuffer,"data")==0)
??????? {
??????????? // 獲得數據
??????????? DdeGetData(hData, (LPBYTE)szBuffer, 40L, 0L);
??????????? fakeThis->m_strRequest=szBuffer;
??????????? return (HDDEDATA)1;
??????? }
??????? break;
??? }
???
??? return NULL;
}
其中只使用了三個選項,即XTYP_CONNECT、XTYP_REQUEST和XTYP_
EXECUTE,還有其他的一些選項,見微軟的MSDN說明。XTYP_CONNECT響應于客戶程序使用的DdeConnect()函數。 XTYP_REQUEST和XTYP_EXECUTE分別響應于客戶程序中使用DdeClientTransaction()函數的 XTYP_REQUEST和XTYP_
EXECUTE選項。在服務程序中,對于XTYP_REQUEST選項,可以用DdeCreateDataHandle函數向客戶程序發送數據,而 XTYP_EXECUTE則不能。而對于XTYP_EXECUTE選項,可以用DdeGetData()函數從客戶獲取數據,而XTYP_REQUEST 則不能。
在服務程序中用DdeQueryString()函數從客戶程序中獲得Topics名和Items名,先得到Topics名,然后得到Items名。在本 實例中XTYP_REQUEST選項的Topics名是“query”,Items名為“1”,而XTYP_EXECUTE選項的Topics名是 “data”,Items名為“1”,但Items名都沒有被利用。
以下是用于客戶程序的主函數,也需要用DdeInitialize()函數初始化,并設置過濾類型。其中使用了類型調用函數DdeCall()。DdeClient()函數的代碼如下:
void CMyDde::DdeClient(CString strRequest)
{
??? m_strRequest=strRequest;
??? idInst=0;
??? DdeInitialize(&idInst,NULL,APPCLASS_STANDARD|
??????? CBF_FAIL_ADVISES|
??????? CBF_FAIL_POKES|
??????? CBF_SKIP_REGISTRATIONS|
??????? CBF_SKIP_UNREGISTRATIONS,0L);
??? DdeCall(XTYP_EXECUTE,TEXT("MyDDEService"),TEXT("data"),TEXT("1"));
DdeCall(XTYP_REQUEST,TEXT("MyDDEService"),TEXT("query"),TEXT("1"));
}
在類型調用的DdeCall()函數中,首先獲得Service名、Topics名和Items名的字符串句柄,然后用DdeConnect()函數與服務程序連接。如果連接成功,就可以用DdeClientTransaction()
函數和用XTYP_REQUEST和XTYP_EXECUTE類型向服務程序發送數據。其中,對于XTYP_REQUEST,可以用DdeGetData ()函數從服務程序獲得數據。最后用DdeDisconnect()函數斷開與服務程序的連接,并且用DdeFreeStringHandle()函數釋 放Service名、Topics名和Items名的字符串句柄。DdeCall()函數的源代碼如下:
void CMyDde::DdeCall(UINT iType,LPCSTR szSvr,LPCSTR szTopic,LPCSTR szItem)
{
??? HSZ hszServName = DdeCreateStringHandle(idInst,szSvr,CP_WINANSI);
??? HSZ hszTopic = DdeCreateStringHandle(idInst,szTopic,CP_WINANSI);
??? HSZ hszItem =? DdeCreateStringHandle(idInst,szItem,CP_WINANSI);
??? HCONV hConv=?? DdeConnect(idInst,hszServName,hszTopic,NULL);
???
??? HDDEDATA hData;
DWORD dwResult;
??? char szBuffer[100];
??? DWORD dwLength;
???
??? switch(iType)
??? {
??? case XTYP_REQUEST:
???????
??????? // 向服務器發送請求
??????? hData = DdeClientTransaction(NULL,0,hConv,
??????????? hszItem, CF_TEXT, iType, 5000, &dwResult);
???????
??????? // 從服務器取得返回值
??????? dwLength = DdeGetData(hData, (LPBYTE)szBuffer,sizeof(szBuffer), 0);
??????? if (dwLength > 0)
??????????? m_strReply=szBuffer;
??????? break;
??? case XTYP_EXECUTE:
??????? strcpy(szBuffer,m_strRequest);
???????
??????? // 向服務器發送執行命令
??????? hData = DdeClientTransaction((LPBYTE)szBuffer,
??????????? sizeof(szBuffer), hConv,
??????????? hszItem, CF_TEXT, iType, 5000, &dwResult);
??????? break;
??? }
???
??? DdeDisconnect(hConv);
??? DdeFreeStringHandle(idInst,hszServName);
??? DdeFreeStringHandle(idInst,hszTopic);
??? DdeFreeStringHandle(idInst,hszItem);
}
總結
以上是生活随笔為你收集整理的windows 进程通信(使用DDE)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: UNIX 环境高级编程读书笔记(1)
- 下一篇: The Devil is in the