COM连接点 - Part I - 基本原理

From: http://blog.csdn.net/zj510/article/details/39079221

 

这次我们来看一下连接点的基本工作原理。画了一个简单的图:

从上面的图,大概也可以看到基本结构了。如果一个COM对象要支持连接点的话,那么这个对象类一定要从IConnectionPointImpl继承下来。通常,会有一个proxy类,上图中的CProxy_IMyCarEvents。这个proxy类一般会有这样的代码:

  1. #pragma once 
  2.  
  3. template<class T> 
  4. class CProxy_IMyCarEvents : 
  5.     public ATL::IConnectionPointImpl<T, &__uuidof(_IMyCarEvents)> 
  6. public
  7.     HRESULT Fire_OnStop(FLOAT Distance) 
  8.     { 
  9.         HRESULT hr = S_OK; 
  10.         T * pThis = static_cast<T *>(this); 
  11.         int cConnections = m_vec.GetSize(); 
  12.  
  13.         for (int iConnection = 0; iConnection < cConnections; iConnection++) 
  14.         { 
  15.             pThis->Lock(); 
  16.             CComPtr<IUnknown> punkConnection = m_vec.GetAt(iConnection); 
  17.             pThis->Unlock(); 
  18.  
  19.             IDispatch * pConnection = static_cast<IDispatch *>(punkConnection.p); 
  20.  
  21.             if (pConnection) 
  22.             { 
  23.                 CComVariant avarParams[1]; 
  24.                 avarParams[0] = Distance; 
  25.                 avarParams[0].vt = VT_R4; 
  26.                 CComVariant varResult; 
  27.  
  28.                 DISPPARAMS params = { avarParams, NULL, 1, 0 }; 
  29.                 hr = pConnection->Invoke(1, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD, ¶ms, &varResult, NULL, NULL); 
  30.             } 
  31.         } 
  32.         return hr; 
  33.     } 
  34. }; 
#pragma once

template<class T>
class CProxy_IMyCarEvents :
	public ATL::IConnectionPointImpl<T, &__uuidof(_IMyCarEvents)>
{
public:
	HRESULT Fire_OnStop(FLOAT Distance)
	{
		HRESULT hr = S_OK;
		T * pThis = static_cast<T *>(this);
		int cConnections = m_vec.GetSize();

		for (int iConnection = 0; iConnection < cConnections; iConnection++)
		{
			pThis->Lock();
			CComPtr<IUnknown> punkConnection = m_vec.GetAt(iConnection);
			pThis->Unlock();

			IDispatch * pConnection = static_cast<IDispatch *>(punkConnection.p);

			if (pConnection)
			{
				CComVariant avarParams[1];
				avarParams[0] = Distance;
				avarParams[0].vt = VT_R4;
				CComVariant varResult;

				DISPPARAMS params = { avarParams, NULL, 1, 0 };
				hr = pConnection->Invoke(1, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD, ¶ms, &varResult, NULL, NULL);
			}
		}
		return hr;
	}
};

其实这个类很简单,比如那里面有一个函数Fire_OnStop。这个函数内部就是遍历一下m_vec数组,这个数组存放的就是sink对象。然后遍历整个数组,把每个sink对象的Invoke函数调用一下。简单说,如果COM组件完成了一项任何,需要通知所有sink的话,就是调用一下Fire函数。这样fire函数会调用所有sink的Invoke。比如:

  1. STDMETHODIMP CMyCar::Run() 
  2.     // TODO: Add your implementation code here 
  3.  
  4.     this->Fire_OnStop(1000); 
  5.  
  6.     return S_OK; 
STDMETHODIMP CMyCar::Run()
{
    // TODO: Add your implementation code here

    this->Fire_OnStop(1000);

    return S_OK;
}

上面这个例子代码,就是在Run()函数里面,调用fire,然后fire函数会调用这个对象里面的所有sink的Invoke。

_IMyCarEvents定义了所有支持的event,比如下面的例子就有一个OnStop事件。

  1. library MyComLib 
  2.     importlib("stdole2.tlb"); 
  3.     [ 
  4.         uuid(2CF347A8-63ED-4CE0-8A6D-F98D60C98B8C)       
  5.     ] 
  6.     dispinterface _IMyCarEvents 
  7.     { 
  8.         properties: 
  9.         methods: 
  10.              
  11.             [id(1)] HRESULT OnStop([in] FLOAT Distance); 
  12.     }; 
  13.     [ 
  14.         uuid(DA6770F3-CBB6-4F34-A137-2B02A27AB219)       
  15.     ] 
  16.     coclass MyCar 
  17.     { 
  18.         [default] interface IMyCar; 
  19.         [default, source] dispinterface _IMyCarEvents; 
  20.     }; 
  21. }; 
library MyComLib
{
	importlib("stdole2.tlb");
	[
		uuid(2CF347A8-63ED-4CE0-8A6D-F98D60C98B8C)		
	]
	dispinterface _IMyCarEvents
	{
		properties:
		methods:
            
            [id(1)] HRESULT OnStop([in] FLOAT Distance);
    };
	[
		uuid(DA6770F3-CBB6-4F34-A137-2B02A27AB219)		
	]
	coclass MyCar
	{
		[default] interface IMyCar;
		[default, source] dispinterface _IMyCarEvents;
	};
};

注意:CMyCar有一个基类叫做IConnectionPointContainerImpl。一个IConnectionPointContainerImpl内部可以有多个连接点,也就是说,我们现在已经有了一个连接点:_IMyCarEvents。那么可以再增加一个连接点,比如叫做_IMyCarEvents2,然后这两个连接点都可以放到Container中。(回头再写个例子)


CSink是一个接收对象,它继承于CComObjectRoot,表明这是一个COM类,同时继承_IMyCarEvents接口。通常这个类看上去像:

  1. class CSink : 
  2.     public CComObjectRoot, 
  3.     public _IMyCarEvents 
  4.     BEGIN_COM_MAP(CSink) 
  5.         COM_INTERFACE_ENTRY(IDispatch) 
  6.         COM_INTERFACE_ENTRY(_IMyCarEvents) 
  7.     END_COM_MAP() 
  8.  
  9. public
  10.     virtual ~CSink(){ 
  11.     } 
  12.     STDMETHODIMP GetTypeInfoCount(UINT *pctinfo) { return E_NOTIMPL; } 
  13.     STDMETHODIMP GetTypeInfo(UINT iTInfo, LCID lcid, ITypeInfo **ppTInfo)   { return E_NOTIMPL; } 
  14.     STDMETHODIMP GetIDsOfNames(REFIID riid, LPOLESTR *rgszNames, UINT cNames, LCID lcid, DISPID *rgDispId)  { return E_NOTIMPL; } 
  15.  
  16.     STDMETHODIMP Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS *pDispParams, VARIANT *pVarResult, EXCEPINFO *pExcepInfo, UINT *puArgErr) 
  17.     { 
  18.         printf("sink, id: %d, parm: %f", dispIdMember, pDispParams->rgvarg[0].fltVal); 
  19.  
  20.         return S_OK; 
  21.     } 
  22. }; 
class CSink :
    public CComObjectRoot,
    public _IMyCarEvents
{
    BEGIN_COM_MAP(CSink)
        COM_INTERFACE_ENTRY(IDispatch)
        COM_INTERFACE_ENTRY(_IMyCarEvents)
    END_COM_MAP()

public:
    virtual ~CSink(){
    }
    STDMETHODIMP GetTypeInfoCount(UINT *pctinfo) { return E_NOTIMPL; }
    STDMETHODIMP GetTypeInfo(UINT iTInfo, LCID lcid, ITypeInfo **ppTInfo)   { return E_NOTIMPL; }
    STDMETHODIMP GetIDsOfNames(REFIID riid, LPOLESTR *rgszNames, UINT cNames, LCID lcid, DISPID *rgDispId)  { return E_NOTIMPL; }

    STDMETHODIMP Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS *pDispParams, VARIANT *pVarResult, EXCEPINFO *pExcepInfo, UINT *puArgErr)
    {
        printf("sink, id: %d, parm: %f", dispIdMember, pDispParams->rgvarg[0].fltVal);

        return S_OK;
    }
};

唯一值得注意的就是Invoke函数了,这个函数会在Proxy类中被调用。也就是通知函数。

将Sink对象挂载到对应的COM对象,就靠函数AtlAdvise。

如:

  1. AtlAdvise(spCar, sinkptr, __uuidof(_IMyCarEvents), &cookies); 
AtlAdvise(spCar, sinkptr, __uuidof(_IMyCarEvents), &cookies);

再看一下函数定义:

  1. ATLINLINE ATLAPI AtlAdvise( 
  2.     _Inout_ IUnknown* pUnkCP, 
  3.     _Inout_opt_ IUnknown* pUnk, 
  4.     _In_ const IID& iid, 
  5.     _Out_ LPDWORD pdw) 
  6.     if(pUnkCP == NULL) 
  7.         return E_INVALIDARG; 
  8.  
  9.     CComPtr<IConnectionPointContainer> pCPC; 
  10.     CComPtr<IConnectionPoint> pCP; 
  11.     HRESULT hRes = pUnkCP->QueryInterface(__uuidof(IConnectionPointContainer), (void**)&pCPC); 
  12.     if (SUCCEEDED(hRes)) 
  13.         hRes = pCPC->FindConnectionPoint(iid, &pCP); 
  14.     if (SUCCEEDED(hRes)) 
  15.         hRes = pCP->Advise(pUnk, pdw); 
  16.     return hRes; 
ATLINLINE ATLAPI AtlAdvise(
	_Inout_ IUnknown* pUnkCP,
	_Inout_opt_ IUnknown* pUnk,
	_In_ const IID& iid,
	_Out_ 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;
}

从上面的代码可以看到,

1. 先从pUnkCP(也就是spCar对象)中找到IConnectionPointContainer,

2. 然后再从container中找到相应的连接点,由第三个参数指定,也就是__uuidof(_IMyCarEvents)。

3. 之后就调用连接点函数pCP->Advise(pUnk, pdw);

连接点Advise函数如下:

  1. template <class T, const IID* piid, class CDV> 
  2. STDMETHODIMP IConnectionPointImpl<T, piid, CDV>::Advise( 
  3.     _Inout_ IUnknown* pUnkSink, 
  4.     _Out_ DWORD* pdwCookie) 
  5.     T* pT = static_cast<T*>(this); 
  6.     IUnknown* p; 
  7.     HRESULT hRes = S_OK; 
  8.     if (pdwCookie != NULL) 
  9.         *pdwCookie = 0; 
  10.     if (pUnkSink == NULL || pdwCookie == NULL) 
  11.         return E_POINTER; 
  12.     IID iid; 
  13.     GetConnectionInterface(&iid); 
  14.     hRes = pUnkSink->QueryInterface(iid, (void**)&p); 
  15.     if (SUCCEEDED(hRes)) 
  16.     { 
  17.         pT->Lock(); 
  18.         *pdwCookie = m_vec.Add(p); 
  19.         hRes = (*pdwCookie != NULL) ? S_OK : CONNECT_E_ADVISELIMIT; 
  20.         pT->Unlock(); 
  21.         if (hRes != S_OK) 
  22.             p->Release(); 
  23.     } 
  24.     else if (hRes == E_NOINTERFACE) 
  25.         hRes = CONNECT_E_CANNOTCONNECT; 
  26.     if (FAILED(hRes)) 
  27.         *pdwCookie = 0; 
  28.     return hRes; 
template <class T, const IID* piid, class CDV>
STDMETHODIMP IConnectionPointImpl<T, piid, CDV>::Advise(
	_Inout_ IUnknown* pUnkSink,
	_Out_ DWORD* pdwCookie)
{
	T* pT = static_cast<T*>(this);
	IUnknown* p;
	HRESULT hRes = S_OK;
	if (pdwCookie != NULL)
		*pdwCookie = 0;
	if (pUnkSink == NULL || pdwCookie == NULL)
		return E_POINTER;
	IID iid;
	GetConnectionInterface(&iid);
	hRes = pUnkSink->QueryInterface(iid, (void**)&p);
	if (SUCCEEDED(hRes))
	{
		pT->Lock();
		*pdwCookie = m_vec.Add(p);
		hRes = (*pdwCookie != NULL) ? S_OK : CONNECT_E_ADVISELIMIT;
		pT->Unlock();
		if (hRes != S_OK)
			p->Release();
	}
	else if (hRes == E_NOINTERFACE)
		hRes = CONNECT_E_CANNOTCONNECT;
	if (FAILED(hRes))
		*pdwCookie = 0;
	return hRes;
}

明白了,基本上就是把sink对象放到spCar对象的m_vec里面而已。

到这里,基本流程就明白了。

再总结一下:

1. 如果COM对象需要支持连接点,那么这个对象类需要从连接点和连接点容器继承下来;(一个类可以有多个连接点)

2. 创建接收对象(sink),接收对象需要从CComObjectRoot(或者类似的其他类)和连接点接口继承下来;

3. 使用AtlAdvise来将一个sink对象挂载到相应的COM对象上。(当COM对象释放的时候,会相应释放所有拥有的sink对象)

4. 这样,当COM对象需要触发一个事件的时候,就可以遍历所有sink对象,一个一个来触发。

整个过程也还是蛮简单的。其实仔细看看这个结构,这活脱脱就是一个观察者模式的典型例子。sink是观察者,具体COM对象是被观察者。当具体COM对象有事件需要触发的时候,就通过m_vec来通知所有的观察者(sink)。IConnectionPoint::m_vec是一个数组,存放所有的观察者。

代码例子:http://download.csdn.net/detail/zj510/7867453

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值