我的博客里写的关于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);