在 C++ 对象中接收连接点






1.80/5 (2投票s)
有时,能够使用一个简单的 C++ 对象作为连接点通知的接收器会非常方便。本文将介绍如何实现这一点。
以下源代码是使用 Visual Studio 6.0 SP5 和 Visual Studio .Net 构建的。您需要安装 Microsoft NetMeeting SDK。可以从 这里 或作为最新版本 Microsoft Platform SDK 的一部分获得。
C++ 对象中的连接点接收。
许多 COM 对象通过连接点向其客户端提供事件。连接点是一种通用的方法,允许客户端注册一个 COM 接口,以便对象可以通知它们它们可能感兴趣的事情。问题是,有时您正在开发的代码仅使用 COM 对象而不实现任何 COM 对象本身,但您仍然需要能够注册一个 COM 对象以通过连接点接收通知。本文介绍了如何使用连接点并从中接收事件,而无需使用 ATL 或 MFC 或公开实际的 COM 对象。
注册通知
假设我们想使用一个通过连接点提供通知的 COM 对象,例如 NetMeeting Conference Manager 对象。我们的应用程序将创建 Conference Manager 对象的实例并通过其接口进行操作,我们将通过注册一个连接点来监视它生成的事件,以接收由 INmManagerNotify
接口生成的事件。下面显示了 INmManagerNotify
接口。
interface INmManagerNotify : IUnknown
{
typedef [unique] INmManagerNotify *LPNMMANAGERNOTIFY;
HRESULT NmUI(
[in] CONFN uNotify);
HRESULT ConferenceCreated(
[in] INmConference *pConference);
HRESULT CallCreated(
[in] INmCall *pCall);
}
我们不想使用 MFC 或 ATL,并且生成的程序将是一个使用纯 C++ 和 COM 客户端 API 的控制台应用程序。问题是我们的应用程序仅使用 COM,它本身不公开任何 COM 功能,我们也不想添加能够提供实际 COM 对象的功能,仅仅是为了能够从连接点接收事件。我们想做的是简单地实现通知接口的方法,并让 Conference Manager 对象在发生通知时调用我们,而不必担心必要的底层实现。
一个可重用的、自包含的通知类
连接连接点所涉及的工作相当直接,如果我们使用 MFC 和 ATL,它们都可以提供一些帮助,但由于我们没有使用它们,所以我们必须自己完成这项工作。给定一个可连接对象的 IUnknown,我们需要获取其 IConnectionPointContainer
接口的指针,然后从中获取所需连接点接口的指针。一旦我们有了这个,我们只需调用 Advise()
成员函数,传递我们的接收器接口,并保留返回的 cookie,因为我们需要它来断开连接。如果我们将这项工作封装在一个类中,我们可以确保在析构函数中断开连接。该类可能看起来像这样
class CNotify { public : CNotify(); ~CNotify(); void Connect( IUnknown *pConnectTo, REFIID riid, IUnknown *pIUnknown); void Disconnect(); bool Connected() const; private : DWORD m_dwCookie; IUnknown *m_pIUnknown; IConnectionPoint *m_pIConnectionPoint; IConnectionPointContainer *m_pIConnectionPointContainer; };
连接可以像调用 Connect()
并传递可连接对象、我们要连接的连接点的 IID
以及我们的接收器接口一样简单。断开连接同样容易。
上面的类很有用,但它并没有解决我们的问题。我们仍然需要一个 COM 对象作为我们的接收器接口,而我们没有,也不想构建一个。然而,相对容易地创建一个派生自我们 CNotify
类的模板类,并填充缺失的功能。模板看起来像这样
template <class NotifyInterface, const GUID *InterfaceIID> class TNotify : private CNotify, public NotifyInterface { public : void Connect(IUnknown *pConnectTo) { CNotify::Connect(pConnectTo, *InterfaceIID, this); } using CNotify::Disconnect; using CNotify::Connected; // IUnknown methods ULONG STDMETHODCALLTYPE AddRef() { return 2; } ULONG STDMETHODCALLTYPE Release() { return 1; } HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, PVOID *ppvObj) { HRESULT hr = S_OK; if (riid == IID_IUnknown) { *ppvObj = (IUnknown *)this; } else if (riid == *InterfaceIID) { *ppvObj = (NotifyInterface *)this; } else { hr = E_NOINTERFACE; *ppvObj = NULL; } return hr; } };
TNotify 用作您的通知接收器对象的基类,它派生自您要实现的接收器接口,并为您提供 IUnknown
方法。您只需实现接收器接口的方法,然后调用 Connect()
并传递您要连接的可连接对象。您的接收器对象可能看起来像这样
class MyConferenceManager : private TNotify{ public : HRESULT STDMETHODCALLTYPE NmUI(CONFN confn) { return S_OK; } HRESULT STDMETHODCALLTYPE ConferenceCreated(INmConference *pConference) { return S_OK; } HRESULT STDMETHODCALLTYPE CallCreated(INmCall *pCall) { return S_OK; } };
在使用接收器对象时,您需要注意几点。首先,TNotify
基类为您构造的 COM 对象期望驻留在堆栈上,或者至少 COM 不会管理其生命周期(注意 AddRef()
和 Release()
的实现)。其次,接收器对象维护对可连接对象接口的引用,以便在调用 Disconnect()
时断开连接,或者在不调用时,在对象被销毁时断开连接。最后,由于接收器对象在销毁时可能会进行 COM 调用,因此您需要确保此时 COM 仍然已初始化 - 因此,在销毁接收器对象之前,请勿调用 CoUninitialize()
。
示例代码
示例代码比上面显示的要复杂一些。由于从纯 C++ 调用 COM 方法通常需要进行大量的类型映射,因此我们倾向于将 COM 对象包装在精简的 C++ 包装器中。我们通过获取 INmManager 对象的接口来做到这一点,并使用相同的包装器来接收事件。这样我们就剩下一个 C++ 对象,它既公开了将参数类型转换为 C++ 友好类型的 INmManager 接口并对方法失败抛出异常,也公开了 INmManagerNotify 接口来通知我们对象上发生的事件。如您所见,创建和使用 NetMeeting Conference Manager COM 对象并将其连接到接收通知事件非常简单,就像这样
class MyConferenceManager : public CNmConferenceManager { public : // implement some or all of the INmManagerNotify methods to do // stuff when the notifications happen... }; ... if (MyConferenceManager::IsNmInstalled()) { MyConferenceManager confManager; ULONG options = NM_INIT_NORMAL; ULONG capabilities = NMCH_ALL; confManager.Initialize(options, capabilities); ...
由于 CNmConferenceManager
对象实现了所有通知接口,并且只是什么都不做,即,它对所有方法都返回 S_OK
,我们的派生类可以选择只覆盖它想要处理的通知。创建我们的对象会初始化 COM,直到我们的对象不再存在,COM 都会保持初始化状态,创建 NetMeeting Conference Manager COM 对象并连接通知接收器。然后,我们就可以简单地在该对象上调用方法并接收回通知。
修订历史
- 2002 年 5 月 30 日 - 初始修订。