From: http://blog.csdn.net/zj510/article/details/39079221
这次我们来看一下连接点的基本工作原理。画了一个简单的图:
从上面的图,大概也可以看到基本结构了。如果一个COM对象要支持连接点的话,那么这个对象类一定要从IConnectionPointImpl继承下来。通常,会有一个proxy类,上图中的CProxy_IMyCarEvents。这个proxy类一般会有这样的代码:
- #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;
- }
- };
#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。比如:
- STDMETHODIMP CMyCar::Run()
- {
- // TODO: Add your implementation code here
- this->Fire_OnStop(1000);
- 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事件。
- 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;
- };
- };
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接口。通常这个类看上去像:
- 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;
- }
- };
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。
如:
- AtlAdvise(spCar, sinkptr, __uuidof(_IMyCarEvents), &cookies);
AtlAdvise(spCar, sinkptr, __uuidof(_IMyCarEvents), &cookies);
再看一下函数定义:
- 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;
- }
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函数如下:
- 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;
- }
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