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函數。