天天看點

Win32程式設計點滴5 - 響應ActiveX控件的事件

在最近的 一篇文章

中說到了,如何建立ActiveX,這次我們來響應事件。這次,我們将建立一個類:CGeneralEventSink,它能夠響應任何Dispatch事件(事件的接口繼承與IDispatch)。

首先,我們來回顧一下ConnectionPoint的概念。任何支援事件的對象(比如,ActiveX控件),都支援

IConnectionPointContainer 接口,顧名思義就是一個 IConnectionPoint 的容器,包含了這個對象支援的全部事件。IConnectionPoint代表了一組事件,調用IConnectionPoint::Advise并傳入我們想要接收事件的對象指針。而IConnectionPoint::GetConnectionInterface傳回的IID,是此接收事件的對象必須實作的接口,否則Advise會失敗。來看一下AxAdviseAll的代碼:
Win32程式設計點滴5 - 響應ActiveX控件的事件

//枚舉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;
}      
Win32程式設計點滴5 - 響應ActiveX控件的事件

然後,來說一下Dispath事件。如果IConnectionPoint::GetConnectionInterface傳回的IID代表的接口是繼承于IDispatch的話,這個事件就是一個Dispath事件。當事件發生時,産生事件的對象不會直接調用pObj->OnEvent(),而會調用pObj->Invoke(…)。例如,Flash控件的事件對象:(通過vc的#import指令生成的)

Win32程式設計點滴5 - 響應ActiveX控件的事件
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 );
};      
Win32程式設計點滴5 - 響應ActiveX控件的事件

當Flash控件産生事件時,它會調用IDispath::Invoke而不是,OnReadyStateChange等方法。隻有在vc,atl等架構裡面,這些架構會自動生成Invoke函數,在Invoke函數中根據參數的不同(第一個參數memid代表代表哪個方法被調用),再調用OnReadyStateChange等方法。

是以,雖然IConnectionPoint要求實作的接口是_IShockwaveFlashEvents,但是我們的虛表中,隻要存在IDispath的方法即可,虛表中之後_IShockwaveFlashEvents的方法不會被直接調用(如果我們自己實作Invoke,也不會去調用的)。我們告訴Flash控件,這是一個_IShockwaveFlashEvents接口,雖然它隻實作了IDispath。就像CGeneralEventSink中的代碼:

Win32程式設計點滴5 - 響應ActiveX控件的事件
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;
}      
Win32程式設計點滴5 - 響應ActiveX控件的事件

其中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接口,代碼如下:

Win32程式設計點滴5 - 響應ActiveX控件的事件
TYPEATTR *attr;
if (SUCCEEDED(pInfo->GetTypeAttr(&attr)))
{
    if (attr->typekind == TKIND_DISPATCH) isDispatch = true;
    pInfo->ReleaseTypeAttr(attr);
}      
Win32程式設計點滴5 - 響應ActiveX控件的事件

最後,CGeneralEventSink的IDispatch方法全部傳回E_NOTIMPLE就可以了,畢竟控件隻是通知我們事件發生了,而不關心我們有什麼反應。當然,為了讓提供的示例更有趣,代碼裡面的Invoke做了詳細的log(在得到接口的ITypeInfo的同時,也枚舉了MEMID/DISPID對應的名字。還從系統資料庫中把iid變成了接口的名字,所有這一切參考CGeneralEventSink的構造函數)。

代碼下載下傳

繼續閱讀