当前位置:编程学习 > 汇编语言 >>

Win32编程系列之做好响应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;
}

然后,来说一下Dispah事件。如果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的构造函数)。

代码下载:files.cnblogs.com/Greatest/TestActiveX2.zip

www.zzzyk.com电脑知识网,平板电脑知识也很精彩

CopyRight © 2012 站长网 编程知识问答 www.zzzyk.com All Rights Reserved
部份技术文章来自网络,