19.MFC中实现可连接对象和接收器

COM教程 专栏收录该内容
23 篇文章 36 订阅

和之前很多实现一样,MFC对可连接对象的支持主要还是依赖基类CCmdTarget。这里贴上上一节我们讲到的实现一个可连接对象需要做的如下:

1.源对象实现IConnnectionPointContainer和IConnectionPoint接口

2.客户端需要实现一个接收器接口,使用时将该接收器连接(注册)到源对象上

3.客户端的接口需要和源对象协商,理论上只要二者商量好即可,但实际中常用的是自动化接口——IDispatch,具体原因请参看《COM原理与应用》


1.实现连接点容器

类似对自动化的支持,首先在我们继承CCmdTarget的类构造函数中开启对可连接对象的支持,如下:

EnableConnections();
这样, m_xConnPtContainer包含了当前的连接点集合。


注意此时要把连接点容器的接口暴露出来,客户端先找到连接点容器才再找到连接点。这里我们实现的功能是ICat接口调用DoSleep使猫猫睡指定的时间,然后通过ICatEvent连接点通知客户猫猫睡醒了。

对应接口如下

	//普通接口定义
	BEGIN_INTERFACE_PART(Cat, ICat)
		INIT_INTERFACE_PART(CAnimalObject, Cat)
		STDMETHOD_(VOID, DoSleep)(LONG nTime);
	END_INTERFACE_PART_STATIC(Cat)

	DECLARE_INTERFACE_MAP()
BEGIN_INTERFACE_MAP(CAnimalObject, CCmdTarget)
	INTERFACE_PART(CAnimalObject, IID_ICat, Cat)
	INTERFACE_PART(CAnimalObject, IID_IConnectionPointContainer, ConnPtContainer)
END_INTERFACE_MAP()

IID_IConnectionPointContainer为标准的连接点容器的IID。

2.实现连接点

前面说过了,实际中使用的源对象和客户端协商的接口是IDispatch,MFC给出了标准的连接点实现,只需要继承CConnectionPoint即可。使用CConnectionPoint,只需要GetIID传入指定的借口IID,其它的保持默认即可。

同样,这里使用一组宏定义来简化操作,如下:

	//连接点接口定义
	BEGIN_CONNECTION_PART(CAnimalObject, CatEvent)
		CONNECTION_IID(IID_ICatEvent)
	END_CONNECTION_PART(CatEvent)
	
	DECLARE_CONNECTION_MAP()
BEGIN_CONNECTION_MAP(CAnimalObject, CCmdTarget)
	CONNECTION_PART(CAnimalObject, IID_ICatEvent, CatEvent)
END_CONNECTION_MAP()
这样在客户端即可查询得到可连接接口,具体和接口映射表非常相似。


3.触发操作

当我们操作源对象触发指定事件发生时,这时候就会调用连接点,遍历当前连接点上的当前注册的客户接收器,告诉他们这个事件发生,具体实现如下:

//接口实现
STDMETHODIMP_(VOID) CAnimalObject::XCat::DoSleep(LONG nTime)
{
	METHOD_PROLOGUE_EX_(CAnimalObject, Cat)
	Sleep(nTime);

	pThis->FiredProcess(nTime);
}

void CAnimalObject::FiredProcess(LONG nTime)
{
	COleDispatchDriver driver;

	POSITION position = m_xCatEvent.GetStartPosition();
	LPDISPATCH pDispatch;
	while (position != NULL)
	{
		pDispatch = (LPDISPATCH)m_xCatEvent.GetNextConnection(position);
		ASSERT(pDispatch != NULL);
		driver.AttachDispatch(pDispatch, FALSE);

		TRY 
		{
			driver.InvokeHelper(DISP_ID_WAKE, DISPATCH_METHOD, VT_EMPTY, NULL, PBYTE(VTS_I4), nTime);
		}
		END_TRY

		driver.DetachDispatch();
	}
}
可以看到,这里具体事件触发使用FireProcess完成,这里我们遍历当前的ICatEvent连接点上的注册的接收器,使用Invoke调用通知, MFC具体的实现时注册的接收器保存在m_xCatEvent中

4.接收器的实现

接收器的实其实就是IDispatch接口的实现,这个在之前我们已经讲过,这里为了方清楚整个过程,还是采用通用的IDisptach实现方式。

接受器的实现并没有统一的标准,我们只需要简单的实现对应接口即可,

这里的生命周期管理实现如下,AddRef和Release直接将引用计数置为1和0.

/************************************************************************/
/* IUnknown生命周期管理,这里并没有统一的实现标准,
   所以简单的处理引用计数为1,释放后则计数为0 */
/************************************************************************/
ULONG STDMETHODCALLTYPE CTestComDlg::XCatEvent::AddRef( void)
{
	return 1;
}

ULONG STDMETHODCALLTYPE CTestComDlg::XCatEvent::Release()
{
	return 0;
}

HRESULT STDMETHODCALLTYPE CTestComDlg::XCatEvent::QueryInterface( 
	/* [in] */ REFIID iid,
	/* [iid_is][out] */ __RPC__deref_out void __RPC_FAR *__RPC_FAR *ppvObj)
{
	METHOD_PROLOGUE_EX(CTestComDlg, CatEvent)

	if (IsEqualIID(iid, IID_IUnknown) ||
		IsEqualIID(iid, IID_IDispatch) ||
		IsEqualIID(iid, IID_ICatEvent))
	{
		*ppvObj = this;
		AddRef();
		return S_OK;
	}
	else
	{
		return E_NOINTERFACE;
	}
}


IDispatch只需要实现Invoke如下:

HRESULT STDMETHODCALLTYPE CTestComDlg::XCatEvent::Invoke(  /* [in] */ DISPID dispIdMember, 
														   /* [in] */ REFIID riid, 
														   /* [in] */ LCID lcid, 
														   /* [in] */ WORD wFlags, 
														   /* [out][in] */ DISPPARAMS *pDispParams, 
														   /* [out] */ VARIANT *pVarResult, 
														   /* [out] */ EXCEPINFO *pExcepInfo, 
														   /* [out] */ UINT *puArgErr )
{
	if (DISP_ID_WAKE == dispIdMember)
	{
		VARIANT var;
		var.intVal = 0;
		if (pDispParams && pDispParams->cArgs==1)
		{
			var = (pDispParams->rgvarg)[0];
		}

		CString strInfo;
		strInfo.Format(L"喵 刚睡了%d毫秒 何事扰朕清修!", var.intVal);
		AfxMessageBox(strInfo);
	}
	else
	{
		AfxMessageBox(L"喵~ 干撒子!");
	}

	return S_OK;
}

5.连接到源对象和断开连接

连接到源对象过程是:先查找连接点容器,连接点容器查到连接点,连接到连接点,如下:

//连接
BOOL CTestComDlg::ConnectSource()
{
	BOOL bRet = FALSE;
	LPCONNECTIONPOINTCONTAINER pConnPtCont = NULL;
	LPCONNECTIONPOINT pConnPt = NULL;

	do 
	{
		if (m_dwCookie!=0 || NULL==m_pDispatch)
		{
			break;
		}
		
		//查询连接点容器对象
		if (FAILED(m_pDispatch->QueryInterface(IID_IConnectionPointContainer, (LPVOID *)&pConnPtCont)) || NULL==pConnPtCont)
		{
			break;
		}

		//查询连接点
		if (FAILED(pConnPtCont->FindConnectionPoint(IID_ICatEvent, &pConnPt)) || NULL==pConnPt)
		{
			break;
		}

		//连接
		DWORD dwCookie = 0;
		if (FAILED(pConnPt->Advise(&m_xCatEvent, &dwCookie)))
		{
			break;
		}

		m_dwCookie = dwCookie;
		bRet = TRUE;
	} while (FALSE);

	//可以释放,因为此时处于连接状态,计数不为0
	if (pConnPt)
	{
		pConnPt->Release();
	}
	if (pConnPtCont)
	{
		pConnPtCont->Release();
	}

	return bRet;
}

断开连接类似,如下:

//断开连接
BOOL CTestComDlg::DisConnectSource()
{
	BOOL bRet = FALSE;
	LPCONNECTIONPOINTCONTAINER pConnPtCont = NULL;
	LPCONNECTIONPOINT pConnPt = NULL;

	do 
	{
		if (m_dwCookie==0 || NULL==m_pDispatch)
		{
			break;
		}

		//查询连接点容器对象
		if (FAILED(m_pDispatch->QueryInterface(IID_IConnectionPointContainer, (LPVOID *)&pConnPtCont)) || NULL==pConnPtCont)
		{
			break;
		}

		//查询连接点
		if (FAILED(pConnPtCont->FindConnectionPoint(IID_ICatEvent, &pConnPt)) || NULL==pConnPt)
		{
			break;
		}

		//断开连接
		if (FAILED(pConnPt->Unadvise(m_dwCookie)))
		{
			break;
		}

		m_dwCookie = 0;
		bRet = TRUE;

	} while (FALSE);

	if (pConnPt)
	{
		pConnPt->Release();
	}
	if (pConnPtCont)
	{
		pConnPtCont->Release();
	}

	return bRet;
}


这里我们演示的时候,使用对话框操作,点击连接时连接,点击睡眠触发操作会弹框提示,点击断开连接断开连接。在InitDialog中查询得到源对象IDisptach接口,在析构函数中释放接口。

MFC 实现可连接对象和连接点方法下载链接

原创,转载请注明来自http://blog.csdn.net/wenzhou1219

  • 0
    点赞
  • 0
    评论
  • 0
    收藏
  • 打赏
    打赏
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 撸撸猫 设计师:马嘣嘣 返回首页

打赏作者

文大侠

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值