March 16, 2014 分类: ASM/C/C++     作者: hoverlees     留言: 发表

我的博客里写的关于C语言访问COM的一些文章帮助了一些朋友,感到非常高兴。最近有几个朋友发邮件问过我C梆定ActiveX事件的方式,解答后感觉好像也有段时间没有写文章了,所以就详细地来写一篇关于C梆定和监听ActiveX事件的文章。

对C访问COM不是很了解的朋友,可以看我博客里的这些文章:

C语言使用ActiveX控件

C语言访问Windows 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);
标签:
我来留个言

您的电子邮箱我一定会保密的哦!

昵称

邮箱

评论内容