在最近的 一篇文章
中說到了,如何建立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;
}

然後,來說一下Dispath事件。如果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的構造函數)。
代碼下載下傳