COM连接点 - Part II - 最简单的例子 (1)

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

 

连接点,connection point,在COM里面也是挺重要的。简单讲,COM里面的连接点就好像是C语言的回调函数,只不过它是基于面向对象实现的。连接点的作用也就是COM对象将一些事件通知客户(调用者)。


先来做一个简单的连接点例子吧,之后再慢慢探讨。

用ATL创建一个工程(DLL COM),取名MyCom,

然后创建一个COM接口,IMyCar. 在options那里选上connection points。

然后在class view那里找到_IMyCarEvents(在MyComLib下面),右键点击Add Method.如图,增加一个方法OnStop。

增加完之后,可以看到IDL文件里面多了个方法,如下图:

  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上点击右键,选择Add Connection Point。

选择_IMyCarEvents,点击>按钮。然后Finish.

这样,我们就增加了一个连接点,在CProxy_IMyCarEvents里面可以看到多了个函数。如下

  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;
	}
};


现在来看看怎么使用这个连接点吧。

先个IMyCar增加一个方法,

  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;
}


然后写个客户程序,如下:

  1. // TestCom.cpp : Defines the entry point for the console application. 
  2. // 
  3.  
  4. #include "stdafx.h" 
  5. #include <atlbase.h> 
  6. #include <atlcom.h> 
  7. #include "../MyCOM/MyCOM_i.h" 
  8. #include "../MyCOM/MyCOM_i.c" 
  9.  
  10. #include <iostream> 
  11. #include <thread> 
  12.  
  13.  
  14. using namespace std; 
  15.  
  16. class CSink : 
  17.     public CComObjectRoot, 
  18.     public _IMyCarEvents 
  19.     BEGIN_COM_MAP(CSink) 
  20.         COM_INTERFACE_ENTRY(IDispatch) 
  21.         COM_INTERFACE_ENTRY(_IMyCarEvents) 
  22.     END_COM_MAP() 
  23.  
  24. public
  25.     virtual ~CSink(){} 
  26.     STDMETHODIMP GetTypeInfoCount(UINT *pctinfo) { return E_NOTIMPL; } 
  27.     STDMETHODIMP GetTypeInfo(UINT iTInfo, LCID lcid, ITypeInfo **ppTInfo)   { return E_NOTIMPL; } 
  28.     STDMETHODIMP GetIDsOfNames(REFIID riid, LPOLESTR *rgszNames, UINT cNames, LCID lcid, DISPID *rgDispId)  { return E_NOTIMPL; } 
  29.  
  30.     STDMETHODIMP Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS *pDispParams, VARIANT *pVarResult, EXCEPINFO *pExcepInfo, UINT *puArgErr) 
  31.     { 
  32.         printf("sink, id: %d, parm: %f", dispIdMember, pDispParams->rgvarg[0].fltVal); 
  33.  
  34.         return S_OK; 
  35.     } 
  36. }; 
  37.  
  38.  
  39. CComModule m_commodule; 
  40.  
  41. int _tmain(int argc, _TCHAR* argv[]) 
  42.     CoInitializeEx(0, COINIT_APARTMENTTHREADED); 
  43.  
  44.     { 
  45.         CComPtr<IMyCar> spCar; 
  46.         spCar.CoCreateInstance(CLSID_MyCar, NULL, CLSCTX_INPROC_SERVER); 
  47.  
  48.         CComObject<CSink>* sinkptr = nullptr; 
  49.         CComObject<CSink>::CreateInstance(&sinkptr); 
  50.  
  51.         DWORD cookies = 0; 
  52.  
  53.         AtlAdvise(spCar, sinkptr, __uuidof(_IMyCarEvents), &cookies); 
  54.  
  55.         spCar->Run(); 
  56.     } 
  57.      
  58.  
  59.     CoUninitialize(); 
  60.  
  61.  
  62.     return 0; 
// TestCom.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include <atlbase.h>
#include <atlcom.h>
#include "../MyCOM/MyCOM_i.h"
#include "../MyCOM/MyCOM_i.c"

#include <iostream>
#include <thread>


using namespace std;

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;
    }
};


CComModule m_commodule;

int _tmain(int argc, _TCHAR* argv[])
{
	CoInitializeEx(0, COINIT_APARTMENTTHREADED);

    {
        CComPtr<IMyCar> spCar;
        spCar.CoCreateInstance(CLSID_MyCar, NULL, CLSCTX_INPROC_SERVER);

        CComObject<CSink>* sinkptr = nullptr;
        CComObject<CSink>::CreateInstance(&sinkptr);

        DWORD cookies = 0;

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

        spCar->Run();
    }
    

	CoUninitialize();


	return 0;
}

跑一下,发现CSink里面的invoke被调用到了。

这样,我们就有了一个简单的连接点例子。其中pDispParams->rgvarg[0].fltVal就是COM对象触发这个连接点的时候传进来的参数1000.


Sink类

其实,这个东西比较固定,反正新手那么写就行了。其他的都可以忽略,主要就是Invoke函数,当COM对象fire一个事件的时候,Invoke函数会被调用。然后在这个函数里面处理可以获取事件的id:dispIdMember以及参数。这样调用者,也就是事件的接收者就可以处理这些事件了,并且返回结果。

如何创建和挂载sink

首先,我们需要创建一个sink实例。就通过下面2行。先声明一个null指针,然后调用CComObject<CSink>::CreateInstance来创建一个sink对象。等CreateInstance返回后,成功的话,sinkptr就指向一个CComSink<CSink>对象。

  1. CComObject<CSink>* sinkptr = nullptr; 
  2. CComObject<CSink>::CreateInstance(&sinkptr); 
        CComObject<CSink>* sinkptr = nullptr;
        CComObject<CSink>::CreateInstance(&sinkptr);

   

然后将这个sink对象挂载到对应的COM对象上。下面的2行代码就是将sinkptr挂载到spCar对象上面。cookies的作用查看MSDN.简单讲cookies是一个传出参数,代表这次挂载,这个值是唯一的。如果想取消这个挂载,就可以用这个cookies和AtlUnadvise来取消连接。

  1. DWORD cookies = 0; 
  2. AtlAdvise(spCar, sinkptr, __uuidof(_IMyCarEvents), &cookies); 
        DWORD cookies = 0;
        AtlAdvise(spCar, sinkptr, __uuidof(_IMyCarEvents), &cookies);

细心一点的同学可能会问,这个地方是不是有个内存泄漏?
sinkptr指向了一个CComObject<CSink>对象,然后sinkptr本身死亡前又没有释放,那岂不是内存泄漏?

这是个很好的问题,我们先看一下AtlAdvise的实现:

  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;
}

我们可以看到pUnk被传到了pCP->Advise(pUnk, pdw);里面。再看看相关代码:

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

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;
}

我们可以看到pUnkSink被放到了m_vec里面(当然,如果中间过程出错的话,直接被release了)。然后在析构函数里面,我们可以看到m_vec里面的所有元素都会调用release来释放。

而spCar的实现类CMyCar有个基类叫做CProxy_IMyCarEvents,CProxy_IMyCarEvents有个基类是IConnectionPointImpl。

那么当spCar在析构的时候,自然就会调用IConnectionPointImpl的析构函数。也就是说会把所有sink对象全部释放掉。

换句话说,一旦将sink对象成功挂载到了COM对象,那么sink对象的生命周期就由对应的COM对象来管理。

我们可以做个简单测试,在上面的例子代码里面,当spCar析构的时候,也就是代码要跑出大括号(spCar->Run();下面)的时候,sink对象也应该被释放。跟了一把,确实是这样。


ok,我们现在不用再担心内存泄漏问题了。一旦挂载成功,sink对象就托管给对应的COM对象了,如果对应的COM对象析构了,那么所有它管理的sink对象也就释放了。

上面的例子,其实很傻,它的主要过程是:

1. 创建sink对象

2. 挂载sink对象到一个COM对象上

3. COM对象调用Run函数

4. Run函数内部触发了一个连接点

5. 调用者的对应函数会被调用,这里是Invoke。

细细看上面的例子,其实Invoke是在Run函数中被调用的。这个好像没有什么意义。真正的回调应该是调用者调用完之后做其他事情了,然后等有回调来的时候,对应的函数会被触发。

anyway,本文中的例子是个非常简单的例子,只是为了展示一个连接点。连接点的威力根本没有发挥,至于真正的连接点应该怎样?待续。。。

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值