一、前言 STDMETHODIMP CDispConnect::Add(long n1, long n2) { long nVal = n1 + n2; Fire_Result( nVal ); // 调用IDE帮我们生成的代理函数代码,发出事件 return S_OK; }15、修正 IDE 产生的代码中的错误。你不用死记硬背错误点,只要编译一下就会报出错误了。一般 VC6 帮我们生成的代码中,有2个地方可能会有BUG。一是打开头文件,找到连接点影射宏,修改如下: BEGIN_CONNECTION_POINT_MAP(CDispConnect) CONNECTION_POINT_ENTRY(DIID__IDispConnectEvents) // 修改 IID_XXXX 为 DIID_XXXX END_CONNECTION_POINT_MAP()这个错误简直可恨,既然我们使用的是双接口连接点,它生成的代码居然不会判断吗?另一个可能的错误可能发生在代理类中的 Fire_xxxx() 函数中。在示例程序中的 Fire_Result() 函数代码,大家自己去阅读,简单说就是循环地取得每个和自己连接对象(每个cookie表示的对象)的接口指针,(如果是自动化接口,则再取得 IDispatch 接口指针),然后调用事件函数。你不理解它现在没有太大的关系,不过在后面的示例二中,它给我们产生的代码是有错误的,我们需要进行修改。这是后话,待会儿再说。 四、实现调用者(一) 1、建立一个 MFC 工程(Project)。示例程序中的工程名称叫 Use。 2、按照咱们以前所学的知识,添加 #import、AfxOleInit()、......不多浪费口条了。如果你还不会,那么请重新从“第四回”再次阅读。 (注2) 3、这里只介绍一下重点部分。我们需要在调用者工程中,增加“接收器”对象。还记得上回书中的增加“回调接收器”对象的方法吗?上回中,我们的回调接口是从 IUnknown 继承下来的。本回中,由于我们的组件是双接口(Dual)的,连接点也是双接口的,因此这次我们的接收器要从 IDispatch 派生啦。 ![]() 4、完成 CSink 类的接口函数(虚函数) STDMETHODIMP CSink::QueryInterface(const struct _GUID &iid,void ** ppv) { *ppv=this; return S_OK; } ULONG __stdcall CSink::AddRef(void) { return 1; } // 做个假的就可以,因为反正这个对象在程序结束前是不会退出的 ULONG __stdcall CSink::Release(void) { return 0; } // 做个假的就可以,因为反正这个对象在程序结束前是不会退出的 STDMETHODIMP CSink::GetTypeInfoCount(unsigned int *) { return E_NOTIMPL; } // 不用实现,反正也不用 STDMETHODIMP CSink::GetTypeInfo(unsigned int,unsigned long,struct ITypeInfo ** ) { return E_NOTIMPL; } // 不用实现,反正也不用 STDMETHODIMP CSink::GetIDsOfNames(const struct _GUID &,unsigned short ** ,unsigned int,unsigned long,long *) { return E_NOTIMPL; } // 不用实现,反正也不用 STDMETHODIMP CSink::Invoke( long dispID, const struct _GUID &, unsigned long, unsigned short, struct tagDISPPARAMS * pParams, struct tagVARIANT *, struct tagEXCEPINFO *, unsigned int *) { // 只需要实现这个就足够啦 switch(dispID) // 根据不同的dispID,完成不同的回调函数 { case 1: ...... // 这里就能接收到 COM 发出的事件啦 break; case 2: ...... // 事件的代号 dispID 其实就是 IDL 文件中的连接点函数的id(n)的号码 break; default: break; } return S_OK; }五、示例(二) 示例程序中的第2个组件(MultConnect),我们再增加一个连接点( _IDispConnectEvents2 )。这个接口对象负责完成一个时钟,每间隔一定的毫秒就向调用者发出“时钟事件”。增加第二个连接点的方法是要手工修改 IDL 文件 ...... library MULTCONNECTLib { importlib("stdole32.tlb"); importlib("stdole2.tlb"); ...... // 第一个,ATL 框架默认给我们生成的连接点接口描述 [ // 需要手工增加第二个或更多个连接点 uuid(F81DB93F-4F63-4A55-8114-A32BC78466D3), // CLSID 可以用 GUIDGEN.EXE 来产生 helpstring("_IDispConnectEvents2 Interface") ] dispinterface _IDispConnectEvents2 { properties: methods: }; [ uuid(9461BE82-0D64-4E3B-B0DB-2306D1BFE3F0), // 这是示例程序的类型库ID,肯定和你生成的不一样的啦 helpstring("DispConnect Class") ] coclass DispConnect { [default] interface IDispConnect; [default, source] dispinterface _IDispConnectEvents; [source] dispinterface _IDispConnectEvents2; // 别忘了,这里还有一行呢 }; };好了,和前面的方式一样,增加接口函数、编译IDL文件、让IDE帮我们实现代理类代码、输入程序代码、修改框架代码中的BUG。在示例中,我们的事件函数叫 HRESULT Timer([in] VARIANT varData),varData 中传递一个时间类型(VT_DATA)的信息(注3)。下面我们来看一下代理类代码中的错误: HRESULT Fire_Timer(VARIANT varDate) { CComVariant varResult; T* pT = static_cast在编写调用者客户端代码方面,如果你需要接收时钟事件,那么可以仿照示例一再从 IDispatch 派生一个时钟接收器。大家下载事例程序代码,里面有丰富的注释信息。 六、小结 连接点,尤其是双接口的连接点,在远程(DCOM)环境上运行效率是比较低的。如果你只想完成简单的“通知”功能,那么前一回中的“回调接口”是一个明智的方案,并且可以运行在DCOM环境上。连接点方案当然也很重要,因为微软的许多应用程序(IE、Office......)都支持连接点,并且 ActiveX 只能通过连接点接口提供“事件”功能。所以,咱们还是都掌握为善吧。 注1:金庸老先生的武侠小说里,总是用“XX 紧”来表示“很 XX”。我也学一学,嘿嘿。 注2:如果看了好几遍,您老人家还不会的话,那只好......先别学了。5555 |
|