天天看点

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

在最近的 一篇文章

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

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

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

//枚举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;
}      
Win32编程点滴5 - 响应ActiveX控件的事件

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

Win32编程点滴5 - 响应ActiveX控件的事件
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 );
};      
Win32编程点滴5 - 响应ActiveX控件的事件

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

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

Win32编程点滴5 - 响应ActiveX控件的事件
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;
}      
Win32编程点滴5 - 响应ActiveX控件的事件

其中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接口,代码如下:

Win32编程点滴5 - 响应ActiveX控件的事件
TYPEATTR *attr;
if (SUCCEEDED(pInfo->GetTypeAttr(&attr)))
{
    if (attr->typekind == TKIND_DISPATCH) isDispatch = true;
    pInfo->ReleaseTypeAttr(attr);
}      
Win32编程点滴5 - 响应ActiveX控件的事件

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

代码下载

继续阅读