zoukankan      html  css  js  c++  java
  • 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 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 1061
    Golang 编程思维和工程实战
    Apache Tomcat jar Catalina
    MySQL Client/Server Protocol mysql协议
    蚂蚁集团万级规模 k8s 集群 etcd 高可用建设之路
    实习生系列之找实习的途径
    Yahoo!网站性能最佳体验的34条黄金守则
    onselectstart="return false"无法复制文字
    VS2008开发环境中容易遇到的3个问题之解决办法
    实践与交流:“三保险”为世界顶级安全防范软件ESET Nod32 4.0的正常使用“保驾护航”
  • 原文地址:https://www.cnblogs.com/shatongtian/p/2638030.html
Copyright © 2011-2022 走看看