65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIcon
emptyStarIcon
starIcon
emptyStarIconemptyStarIconemptyStarIcon

1.80/5 (2投票s)

2002年6月6日

CPOL

5分钟阅读

viewsIcon

134812

downloadIcon

1234

有时,能够使用一个简单的 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 日 - 初始修订。
© . All rights reserved.