我的博客里写的关于C语言访问COM的一些文章帮助了一些朋友,感到非常高兴。最近有几个朋友发邮件问过我C梆定ActiveX事件的方式,解答后感觉好像也有段时间没有写文章了,所以就详细地来写一篇关于C梆定和监听ActiveX事件的文章。
对C访问COM不是很了解的朋友,可以看我博客里的这些文章:
本文链接: http://www.hoverlees.com/blog/?p=1793
上面的文章讲过COM,ActiveX,Ole自动化对象的一些东西,这里不再多说。关于ActiveX的事件,是由ActiveX内部产生的,如果ActiveX容器对这个事件感兴趣,就可以监听这个事件,当这个事件触发时,就会调用我们提供的实现IDispatch的监听器,从而实现ActiveX调用容器函数的效果,这样就可以扩展ActiveX的功能。
例如我们常用的IWebBrowser控件有很多的事件,如将要开始导航、页面加载完成等事件,当容器监听到这些事件,就可以做自己的处理;浏览器里javascript有个window.external对象,是提供给javascript脚本调用容器的方式,实现javascript调用C实现的容器所提供的功能。
同样,常用的Flash控制也有一些事件可供监听,如fscommand、ExternalInterface.call,都是脚本调用容器的方式,它们同样是靠触发ActiveX事件实现的。
进入正题,具有事件的ActiveX控件,都有实现IConnectionPointContainer接口,通过QueryInterface就可以获得这个接口,然后调用IConnectionPointContainer的FindConnectionPoint函数就可以得到具体事件的IConnectionPoint,然后使用IConnectionPoint的Advise函数即可注册该事件的一个监听器,UnAdvise取消监听。
关于QueryInterface和调用接口的函数,可以参考C访问COM组件的函数那篇文章里的代码,现在主要要解决的是C去实现一个IDispatch接口的对象。其实只要熟悉COM的结构,要实现一个IDispatch也是很简单的事,如下面的代码:
//注意需要#include "ccom.h", ccom.h在上面的文章中有提供 typedef struct _IDispatchVTable{ _QueryInterface QueryInterface; _AddRef AddRef; _Release Release; _GetTypeInfoCount GetTypeInfoCount; _GetTypeInfo GetTypeInfo; _GetIDsOfNames GetIDsOfNames; _Invoke Invoke; }IDispatchVTable; typedef struct _IDispatchClass{ IDispatchVTable* pv; IDispatchVTable vtable; int counter; //对象自己的引用计数,可以再加其他私有变量。 }IDispatchClass; //实现IDispatch的vtable static HRESULT __stdcall IDispatchClass_QueryInterface(LPVOID _this,REFIID iid,void ** ppvObject){ //不管Query什么接口,都返回该对象本身,如果要真自的自消毁,要注意在前三个函数中对引用计数进行正确的维护,我这儿为了简便就不做维护了,对象不会自消毁,需要手工free *ppvObject=_this; return S_OK; } static ULONG __stdcall IDispatchClass_AddRef(LPVOID _this){ return 1; } static ULONG __stdcall IDispatchClass_Release(LPVOID _this){ return 1; } static HRESULT __stdcall IDispatchClass_GetTypeInfoCount(LPVOID _this,unsigned int FAR* pctinfo){ *pctinfo=0; return S_OK; } static HRESULT __stdcall IDispatchClass_GetTypeInfo(LPVOID _this,unsigned int iTInfo,LCID lcid,ITypeInfo FAR* FAR* ppTInfo){ return S_OK; } //自已只需要传入GetIdsOfNames和Invoke的实现,即可创建一个IDispatch,GetIdsOfNames就是通过名称获取ID的函数,例如你向ActiveX提供Hover函数,则可以通过这个函数为字符串Hover这个名称返回一个指定的ID,并在Invoke中判断,如果是这个ID,就调用Hover对应的函数。 int CComCreateDispatchObject(_GetIDsOfNames getIDsOfNames,_Invoke invoke,LPVOID* ppdispatch){ IDispatchClass* pthis=(IDispatchClass*) malloc(sizeof(IDispatchClass)); if(pthis==NULL) return 0; pthis->pv=&pthis->vtable; pthis->vtable.QueryInterface=IDispatchClass_QueryInterface; pthis->vtable.AddRef=IDispatchClass_AddRef; pthis->vtable.Release=IDispatchClass_Release; pthis->vtable.GetTypeInfoCount=IDispatchClass_GetTypeInfoCount; pthis->vtable.GetTypeInfo=IDispatchClass_GetTypeInfo; pthis->vtable.GetIDsOfNames=getIDsOfNames; pthis->vtable.Invoke=invoke; pthis->vtable.counter=1; *ppdispatch=pthis; return 1; } //下面是向ActiveX注册事件监听器的函数 //调用COM组件的Release函数 void CComRelease(LPVOID ppv){ _Release* vtable=*(_Release**) ppv; vtable[2](ppv); } typedef HRESULT (__stdcall *_FindConnectionPoint)(LPVOID _this,REFIID riid,LPVOID* ppv); typedef HRESULT (__stdcall *_Advise)(LPVOID _this,IUnknown *pUnkSink,DWORD* cookie); //向psrc对象注册eventIID事件监听器listenerDispatch,当有事件触发时,会自动调用listenerDispatch的Invoke int CComBindEvent(LPVOID psrc,REFIID eventIID,LPVOID listenerDispatch,DWORD* pDWCookie){ _FindConnectionPoint* vtable; _Advise* vtable2; LPVOID pIConnectionPointContainer; LPVOID pIConnectionPoint; pIConnectionPointContainer=CComQueryInterface(psrc,L"{B196B284-BAB4-101A-B69C-00AA00341D07}",CCOM_IID_TYPE_WSTRINGID); if(pIConnectionPointContainer==NULL){ return 0; } vtable=*(_FindConnectionPoint**)pIConnectionPointContainer; //vtable[4]即是FindConnectionPoint函数。 if(S_OK!=vtable[4](pIConnectionPointContainer,eventIID,&pIConnectionPoint)){ CComRelease(pIConnectionPointContainer); return 0; } vtable2=*(_Advise**)pIConnectionPoint; //vtable2[5]即是Advise函数。 if(S_OK!=vtable2[5](pIConnectionPoint,(IUnknown*)listenerDispatch,pDWCookie)){ CComRelease(pIConnectionPointContainer); CComRelease(pIConnectionPoint); return 0; } CComRelease(pIConnectionPointContainer); CComRelease(pIConnectionPoint); return 1; }
下面是一个监听IShockwaveFlash控件事件的示例,实现ExternalInterface调用C容器
static LPVOID pFlashListener; static IID IID_IShockwaveFlashEvents = {0xD27CDB6D, 0xAE6D, 0x11CF, {0x96, 0xB8, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}}; HRESULT __stdcall Flash_GetIDsOfNames(LPVOID _this,REFIID riid,OLECHAR FAR* FAR* rgszNames,unsigned int cNames,LCID lcid,DISPID FAR* rgDispId){ return S_OK; } HRESULT __stdcall Flash_Invoke(LPVOID _this,DISPID dispIdMember,REFIID riid,LCID lcid,WORD wFlags,DISPPARAMS FAR* pDispParams,VARIANT FAR* pVarResult,EXCEPINFO FAR* pExcepInfo,unsigned int FAR* puArgErr){ char buffer[1024]; switch(dispIdMember){ case 197://ExternalInterface.call if(pDispParams->cArgs>0){ WideCharToMultiByte(0,0,pDispParams->rgvarg->bstrVal,-1,buffer,1024,0,0); MessageBox(0,buffer,0,0); } break; } return S_OK; } //创建监听器Dispatch对象 CComCreateDispatchObject(Flash_GetIDsOfNames,Flash_Invoke,&pFlashListener); //梆定Flash事件,梆定完成后,在Flash中调用ExternalInterface.call,会调用Flash_Invoke函数,并得到一串调用的具体信息,是以XML形式组织的数据。 CComBindEvent(pFlash,&IID_IShockwaveFlashEvents,pFlashListener,&dwCookie);