转自: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函数。