天天看点

Win32编程点滴5 - 响应ActiveX控件的事件

在最近的 一篇文章

中说到了,如何创建ActiveX,这次我们来响应事件。这次,我们将创建一个类:CGeneralEventSink,它能够响应任何Dispatch事件(事件的接口继承与IDispatch)。

首先,我们来回顾一下ConnectionPoint的概念。任何支持事件的对象(比如,ActiveX控件),都支持

IConnectionPointContainer 接口,顾名思义就是一个 IConnectionPoint 的容器,包含了这个对象支持的全部事件。IConnectionPoint代表了一组事件,调用IConnectionPoint::Advise并传入我们想要接收事件的对象指针。而IConnectionPoint::GetConnectionInterface返回的IID,是此接收事件的对象必须实现的接口,否则Advise会失败。来看一下AxAdviseAll的代码:

//枚举IConnectionPointContainer中的每个IConnectionPoint,     //对于每个IConnectionPoint新建一个支持iid接口的CGeneralEventSink对象,并Advise。     //pUnk是控件的指针     HRESULT AxAdviseAll(IUnknown * pUnk)     {     	HRESULT hr;     	IConnectionPointContainer * pContainer = NULL;     	IConnectionPoint * pConnectionPoint=NULL;     	IEnumConnectionPoints * pEnum = NULL;     	hr = pUnk->QueryInterface(IID_IConnectionPointContainer,(void**)&pContainer);     	if (FAILED(hr)) goto error1;     	hr = pContainer->EnumConnectionPoints(&pEnum);     	if (FAILED(hr)) goto error1;     	ULONG uFetched;     	while(S_OK == (pEnum->Next(1,&pConnectionPoint,&uFetched)) && uFetched>=1)     	{     		DWORD dwCookie;     		IID iid;     		hr = pConnectionPoint->GetConnectionInterface(&iid);     		if (FAILED(hr)) iid = IID_NULL;     		//这里传入pUnk是为了通过pUnk得到iid的ITypeInfo,进而判断iid是否是Dispatch接口     		IUnknown * pSink = new CGeneralEventSink(iid,pUnk);     		hr = pConnectionPoint->Advise(pSink,&dwCookie);     		pSink->Release();     		pConnectionPoint->Release();     		pConnectionPoint = NULL;     		pSink = NULL;     	}     	hr = S_OK;     error1:     	if (pEnum)pEnum->Release();     	if (pContainer) pContainer->Release();     	if (pConnectionPoint) pConnectionPoint->Release();     	return hr;     }      

然后,来说一下Dispath事件。如果IConnectionPoint::GetConnectionInterface返回的IID代表的接口是继承于IDispatch的话,这个事件就是一个Dispath事件。当事件发生时,产生事件的对象不会直接调用pObj->OnEvent(),而会调用pObj->Invoke(…)。例如,Flash控件的事件对象:(通过vc的#import指令生成的)

struct __declspec(uuid("d27cdb6d-ae6d-11cf-96b8-444553540000"))     _IShockwaveFlashEvents : IDispatch     {     //     // Wrapper methods for error-handling     //     // Methods:     HRESULT OnReadyStateChange (     long newState );     HRESULT OnProgress (     long percentDone );     HRESULT FSCommand (     _bstr_t command,     _bstr_t args );     HRESULT FlashCall (     _bstr_t request );     };      

当Flash控件产生事件时,它会调用IDispath::Invoke而不是,OnReadyStateChange等方法。只有在vc,atl等框架里面,这些框架会自动生成Invoke函数,在Invoke函数中根据参数的不同(第一个参数memid代表代表哪个方法被调用),再调用OnReadyStateChange等方法。

所以,虽然IConnectionPoint要求实现的接口是_IShockwaveFlashEvents,但是我们的虚表中,只要存在IDispath的方法即可,虚表中之后_IShockwaveFlashEvents的方法不会被直接调用(如果我们自己实现Invoke,也不会去调用的)。我们告诉Flash控件,这是一个_IShockwaveFlashEvents接口,虽然它只实现了IDispath。就像CGeneralEventSink中的代码:

STDMETHOD(QueryInterface(REFIID riid,void **ppvObject))     {     *ppvObject = NULL;     if ( IID_IUnknown == riid)     {     *ppvObject = (IUnknown*)this;     }     else if (IID_IDispatch == riid || m_iid == riid)     {     *ppvObject = (IDispatch*)this;     }     else     {     return E_NOINTERFACE;     }     AddRef();     return S_OK;     }      

其中m_iid是IConnectionPoint要求实现的接口,通过CGeneralEventSink构造函数参数传入的。当然,m_iid一定要是一个dispatch接口(继承于IDispatch)。如果IConnectionPoint要求的接口不是继承于IDispatch的,则m_iid会被设置为IID_NULL,然后IConnectionPoint便得不到所要求的接口,Advise就会失败,否则的话控件就会调用一个虚表中不存在的方法(不是IDispatch事件的话,控件只能直接调用接口的方法,而不是Invoke),产生错误。

那么,如何判断m_iid是Dispatch接口呢?首先,得到代表此接口的ITypeInfo指针,这个请参考代码中的:

HRESULT CGeneralEventSink::GetIIDTypeInfo(IID iid,ITypeInfo ** ppInfo,IUnknown * pRelateObj);      

简单的说,就是控件的接口pRelateObj会实现IProvideClassInfo接口,来提供它本身的ITypeInfo。再通过ITypeInfo::GetRefTypeInfo可以得到相关事件的ITypeInfo。

接着,此ITypeInfo的TYPEATTR结构中的typekind表明了,此ITypeInfo是否的Dispatch接口,代码如下:

TYPEATTR *attr;     if (SUCCEEDED(pInfo->GetTypeAttr(&attr)))     {     if (attr->typekind == TKIND_DISPATCH) isDispatch = true;     pInfo->ReleaseTypeAttr(attr);     }      

最后,CGeneralEventSink的IDispatch方法全部返回E_NOTIMPLE就可以了,毕竟控件只是通知我们事件发生了,而不关心我们有什么反应。当然,为了让提供的示例更有趣,代码里面的Invoke做了详细的log(在得到接口的ITypeInfo的同时,也枚举了MEMID/DISPID对应的名字。还从注册表中把iid变成了接口的名字,所有这一切参考CGeneralEventSink的构造函数)。

代码下载

继续阅读