BHO開發中的IE事件響應原理

PS:本文適合於對虛表、模板等語法特性熟悉的朋友。

ATLIDispEventImpl簡化了事件響應的編碼流程。一般需要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、取出已經註冊的BHOIUnknown接口指針;

2、  在每個IUnknown接口指針上調用QueryInterface(IID_IDispatch,(void**)&pDisp)獲取IDispath接口指針;

3、  在所有的IDispatch接口指針上調用InvokeBHO對象會在內部通過類型庫輔助類將Invoke的調用轉發至OnDocumentComplete

 

可是,我們的BHO並沒有實現IDispatch接口啊?QueryInterface怎麼會成功呢?請繼續閱讀。

IE必須要保存各個BHOIUnknown指針,否則就無法通知BHO。那麼IE是通過什麼方式獲取這些IUnknown指針呢?IE當然無法知道誰要掛接它,因此必須是BHO自己將IUnknown指針傳給IEIE在內部保存它的備份。我們的程序通過調用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*)thisthis指針又指向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也是虛函數,所以該調用會被轉換成對IDispEventSimpleImpl7個虛函數的調用(因爲Invoke就是IDispatch接口的第7個虛函數),也就是調用IDispEventSimpleImpl::InvokeInvoke內部根據內建的映射表調用用戶自定義的DocumentComplete函數。

發佈了46 篇原創文章 · 獲贊 6 · 訪問量 10萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章