BHO开发中的IE事件响应原理

转自:http://blog.csdn.net/lyclowlevel/article/details/5955065


PS:本文适合于对虚表、模板等语法特性熟悉的朋友。

ATL的IDispEventImpl简化了事件响应的编码流程。一般需要3个步骤:

1、  继承IDispEventImpl:

public IDispEventImpl<1, CSayHello, &DIID_DWebBrowserEvents2>  

2、  添加SINK_ENTRY_EX:

SINK_ENTRY_EX(1, DIID_DWebBrowserEvents2,DISPID_DOCUMENTCOMPLETE, OnDocumentComplete)

3、  在SetSite中建立连接和取消连接:

HRESULT STDMETHODCALLTYPE CSayHello::SetSite(IUnknown * pUnkSite)

{

    if ( NULL!= pUnkSite )

    {

       pUnkSite->QueryInterface(IID_IWebBrowser2,(void**)&m_spWebBrowser2);

 

       if ( m_spWebBrowser2!= NULL )

       {

           HRESULT hr = DispEventAdvise(m_spWebBrowser2); 

 

           if ( SUCCEEDED(hr) )

           {

              m_bAdvised = true;

           }

       }

    }

    else

    {

       if ( m_bAdvised)

       {

           DispEventUnadvise(m_spWebBrowser2);

           m_bAdvised = false;

       }

       m_spWebBrowser2.Release();

    }

 

    return IObjectWithSiteImpl<CSayHello>::SetSite(pUnkSite);

}

 

 

这些步骤都完成之后,每当一个页面加载完成后都会调用我们自定义的OnDocumentComplete。

但是,具体的调用流程又是什么? IE事件的通知流程如下:

1、取出已经注册的BHO的IUnknown接口指针;

2、  在每个IUnknown接口指针上调用QueryInterface(IID_IDispatch,(void**)&pDisp)获取IDispath接口指针;

3、  在所有的IDispatch接口指针上调用Invoke。BHO对象会在内部通过类型库辅助类将Invoke的调用转发至OnDocumentComplete。

 

可是,我们的BHO并没有实现IDispatch接口啊?QueryInterface怎么会成功呢?请继续阅读。

IE必须要保存各个BHO的IUnknown指针,否则就无法通知BHO。那么IE是通过什么方式获取这些IUnknown指针呢?IE当然无法知道谁要挂接它,因此必须是BHO自己将IUnknown指针传给IE,IE在内部保存它的备份。我们的程序通过调用DispEventAdvise将自己的IUnknown指针传给IE。由于我们的BHO没有定义这个成员函数,因此最终调用的是IDispEventSimpleImpl:: DispEventAdvise。该函数源码如下:

HRESULT DispEventAdvise(IUnknown* pUnk, const IID* piid)

    {

       ATLASSERT(m_dwEventCookie == 0xFEFEFEFE);

       if (m_dwEventCookie!= 0xFEFEFEFE)

           return E_UNEXPECTED;

       return AtlAdvise(pUnk, (IUnknown*)this, *piid, &m_dwEventCookie);

    }

 

AtlAdvise又是ATL提供的函数,源码如下:

ATLINLINE ATLAPI AtlAdvise(IUnknown*pUnkCP, IUnknown*pUnk, const IID& iid, LPDWORD pdw)

{

    if(pUnkCP== NULL)

       return E_INVALIDARG;

       

    CComPtr<IConnectionPointContainer> pCPC;

    CComPtr<IConnectionPoint> pCP;

    HRESULT hRes= pUnkCP->QueryInterface(__uuidof(IConnectionPointContainer),(void**)&pCPC);

    if (SUCCEEDED(hRes))

       hRes = pCPC->FindConnectionPoint(iid, &pCP);

    if (SUCCEEDED(hRes))

       hRes = pCP->Advise(pUnk, pdw);

    return hRes;

}

 

实际的事件订阅工作由:hRes = pCP->Advise(pUnk, pdw);这句代码完成。IE保存的IUnknown指针就是pUnk,而pUnk又等于(IUnknown*)this,this指针又指向BHO对象的IDispEventSimpleImpl子对象。因此,IE保存的IUnknown指针指向BHO对象的IDispEventSimpleImpl。对于IE而言,它才不管你具体指向哪个对象,,只是简单地调用QueryInterface。由于QueryInterface是虚拟函数,因此,

pUnk->QueryInterface(IID_IDispatch, (void**)&pDisp);

被编译器转换成:

(*(pUnk)) (IID_IDispatch, (void**)&pDisp);

 

即取出pUnk指向的内存空间的值,并强制转换成与QueryInterface原型一致的函数指针。而pUnk实际上指向IDispEventSimpleImpl子对象,以上调用转换成IDispEventSimpleImpl子对象的第一个虚函数的调用。IDispEventSimpleImpl的前几个虚函数分别为:

_LocDEQueryInterface

AddRef

Release

GetTypeInfoCount

GetTypeInfo

GetIDsOfNames

Invoke

所以,最终调用的是_LocDEQueryInterface。这就是QueryInterface能成功的原因。

_LocDEQueryInterface的源代码如下:

STDMETHOD(_LocDEQueryInterface)(REFIID riid, void ** ppvObject)

{

    if (ppvObject== NULL)

       return E_POINTER;

    *ppvObject = NULL;

 

    if (InlineIsEqualGUID(riid, IID_NULL))

       return E_NOINTERFACE;

 

    if (InlineIsEqualGUID(riid, *pdiid) ||

       InlineIsEqualUnknown(riid) ||

       InlineIsEqualGUID(riid, __uuidof(IDispatch)) ||

       InlineIsEqualGUID(riid, m_iid))

    {

       *ppvObject = this;

       AddRef();

       return S_OK;

    }

    else

       return E_NOINTERFACE;

}

 

其实就是直接返回this指针,也就是IE保存的IUnknown指针。

接下来,IE会调用pDisp->Invoke(…);

由于Invoke也是虚函数,所以该调用会被转换成对IDispEventSimpleImpl第7个虚函数的调用(因为Invoke就是IDispatch接口的第7个虚函数),也就是调用IDispEventSimpleImpl::Invoke。Invoke内部根据内建的映射表调用用户自定义的DocumentComplete函数。


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值