在最近的一篇文章中說到了,如何創建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的構造函數)。
代碼下載:files.cnblogs.com/Greatest/TestActiveX2.zip