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

COM 连接点

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.84/5 (71投票s)

2003年1月22日

5分钟阅读

viewsIcon

285265

downloadIcon

4433

本文旨在通过一个清晰的实际示例来解释连接点的概念,该示例将演示一个进程内 COM 服务器和一个使用该服务器的 MFC 客户端。

引言

本文旨在通过一个清晰的实际示例来解释连接点的概念,该示例将演示一个进程内 COM 服务器和一个使用该服务器的 MFC 客户端。

它到底是什么?

这是 COM 对象用来回调客户端的一种方法。换句话说,客户端会收到来自 COM 对象的事件通知。

也许您听说过回调函数。好吧,它是这样的。假设您有一个公开了 IArithematic 接口的 COM 对象,并且有一个资源密集型的方法,比如 Add(int a,int b) - (“为了简单起见,任何东西都可以,”隐士说,他赤身裸体地住在喜马拉雅山洞里。Sam Weller —《匹克威克 papers》)。试想一下,这个方法会花费大量时间,而您不想一直等到任务完成。您可以使用这段时间做些其他事情。所以,这就是连接点发挥作用的地方。您在客户端代码中分配一个名为 ExecutionOver(int Result) 的函数,COM 对象在完成执行 Add 方法后可以调用它。

因此,当 COM 对象完成任务后,它会调用客户端函数 ExecutionOver(传递加法的结果)。客户端很高兴地在消息框中弹出结果。这就是全部内容。现在我们将深入探讨细节。

COM 对象如何知道如何调用 ExecutionOver??

想象一下,客户端公开了一个名为 ISink 的接口,该接口有一个名为 ExecutionOver(int result) 的方法。现在,如果客户端可以将此接口传递给 COM 对象,COM 对象就可以愉快地调用 ExecutionOver。例如,在 COM 代码片段中可能如下所示:

//===================================================
ISink *pClientSink;
//(Client somehow passes the ISink interface pointer
//we shall see how later -- so pClientSink is loaded now
HRESULT Add(int a , int b)
{
  pClientSink->ExecutionOver(a+b);
}
//=====================================================

这才是真正发生的事情。其余部分是为了使这一切足够通用。微软通过定义可连接对象来实现这一点。让我们从检查连接所涉及的 COM 接口开始——IConnectionPointIConnectionPointContainer。对象(而不是客户端)实现了这两个接口。

这两个接口如下所示。

interface IConnectionPointContainer : IUnknown {
  HRESULT EnumConnectionPoints(
    IEnumConnectionPoints **ppEnum) = 0;
  HRESULT FindConnectionPoint(REFIID riid,
    IConnectionPoint **ppCP) = 0;
};

interface IConnectionPoint : IUnknown {
  HRESULT GetConnectionInterface(IID *pIID) = 0;
  HRESULT GetConnectionPointContainer(
    IConnectionPointContainer **ppCPC) = 0;
  HRESULT Advise(IUnknown *pUnk, DWORD *pdwCookie) = 0;
  HRESULT Unadvise(DWORD dwCookie) = 0;
  HRESULT EnumConnections(IEnumConnections **ppEnum) = 0;
};

现在,我们一步一步地看看这一切是如何工作的。

COM 客户端调用 CoCreateInstance 来创建 COM 对象。一旦客户端获得了初始接口,它就可以通过调用 QueryInterface 来查询对象是否支持任何出站接口(使用 IConnectionPointContainer)。如果对象回答“是”,并返回一个有效的指针,客户端就知道它可以尝试建立连接。

一旦客户端知道对象支持出站接口(换句话说,能够回调客户端),客户端就可以通过调用 IConnectionPointContainer::FindConnectionPoint 来请求一个特定的出站接口,使用代表所需接口的 GUID。如果对象实现了该出站接口,对象就会返回一个指向该连接点的指针。此时,客户端使用该 IConnectionPoint 接口指针并调用 IConnectionPoint::Advise( [in] IUnknown *pUnk, [out] DWORD *pdwCookie) 来传递其回调接口的实现,以便对象可以回调客户端。为了再次明确,传递给 advise 方法的 IUnknown 的指针是一个在客户端 EXE 中定义和实现的接口的指针。

好的,现在让我们通过一个实际的例子来说明这一切。

  1. 创建一个新的 ATL-COM AppWizard 项目,并将其命名为 ConnectionCOM。
  2. 右键单击类视图并创建一个新的 ATL 对象。

    将其命名为 Add(接口 IAdd)。

    在单击“确定”按钮之前,请务必选中“支持连接点”复选框。

    点击“确定”。

注意类视图中生成的类。您会找到一个 IAdd 和一个 _IAddEvents。后者只是一个代理类,需要在客户端实现。它是因为我们勾选了 Connection_Points 复选框而生成的。

IAdd 接口添加一个方法 'Add(int a,int b) ',向 _IAddEventsInterface 添加一个方法 'ExecutionOver(int Result)'。类视图将如下所示:

但是,由于我们选择了一个双重接口,并且不需要那么多麻烦,所以让我们通过编辑 IDL 文件来移除对 IDispatch 的支持。下面是原始文件。

//===========================================================
// ConnectionCOM.idl : IDL source for ConnectionCOM.dll
//
  :
  :

library CONNECTIONCOMLib
{
importlib("stdole32.tlb");
importlib("stdole2.tlb");

[
  uuid(AFE854B0-246F-4B66-B26F-A1060225C71C),
  helpstring("_IAddEvents Interface")
]
// Old block - take this out
// dispinterface _IAddEvents
// {
// properties:
// methods:
// [id(1), helpstring("method ExecutionOver")]
// HRESULT ExecutionOver(intResult);
// };
//To this one -put this in
interface _IAddEvents : IUnknown
  {
  [id(1), helpstring("method ExecutionOver")] HRESULT
          ExecutionOver(intResult);
  };
  [
    uuid(630B3CD3-DDB1-43CE-AD2F-4F57DC54D5D0),
    helpstring("Add Class")
  ]
  coclass Add
  {
  [default] interface IAdd;
  //[default, source] dispinterface _IAddEvents; take this line
  //out and put the line below in
  [default, source] interface _IAddEvents ;
  };
};

//================================================================

呼!客户端方面几乎完成了。现在,进行一次构建,因为我们需要类型库来用 ATL 做一些巧妙的事情。现在,右键单击 CoClass 并单击“实现连接点”。

在出现的对话框中选中 _IAddEvents。

将生成一个 CProxy_IAddEvets 类,其中包含 Fire_ExecutionOver(int result) 方法。这将负责 COM 对象如何调用客户端接口(并处理多个客户端调用同一 COM DLL 以及其他此类问题)。现在,让我们实现我们旧的 IAdd 接口的 Add 方法。

//=====================================================

STDMETHODIMP CAdd::Add(int a, int b)
{
// TODO: Add your implementation code here

Sleep(2000);   // to simulate a long process

//OK, process over now; let's notify the client

Fire_ExecutionOver(a+b);

return S_OK;
}
//======================================================

进行一次构建,COM 就准备好了。确保 COM 已注册。

现在是客户端方面

创建一个新的 MFC AppWizard(exe)基于对话框的项目 - ConnectionClient。它看起来像这样:

现在到了主要部分。

我们创建一个 CSink 类,它派生自 _IAddEvents。您可以使用类向导来完成此操作。您必须提供 _IAddEvents 接口定义的头文件。为此,请将 ConnectionCOM.h 和 ConnectionCOM.tlb 文件复制到您的客户端 EXE 的项目文件夹中,并将以下行添加到 Sink.h 文件中:

#include "ConnectionCOM.h"
#import "ConnectionCOM.tlb" named_guids raw_interfaces_only

现在,我们还有一项额外任务,即实现 _IAddEvents 接口中定义的每个方法。(永远不要忘记,COM 接口只是一个纯抽象基类,派生类必须实现其所有方法。)

所以,让我们实现第一个 ExecutionOver

STDMETHODIMP ExecutionOver(int Result)
  {
  CString strTemp;
  strTemp.Format("The result is %d", Result);
  AfxMessageBox(strTemp);
  return S_OK;;

  };

现在是 QueryInterface、AddRef 和 Release。

HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, void
                                         **ppvObject)
  {
    if (iid == IID__IAddEvents)
    {
      m_dwRefCount++;
      *ppvObject = (void *)this;
      return S_OK;
    }
    if (iid == IID_IUnknown)
    {
      m_dwRefCount++;
      *ppvObject = (void *)this;
      return S_OK;
    }
    return E_NOINTERFACE;
  }

ULONG STDMETHODCALLTYPE AddRef()
  {
    m_dwRefCount++;
    return m_dwRefCount;
  }

ULONG STDMETHODCALLTYPE Release()
  {
    ULONG l;
    l  = m_dwRefCount--;
    if ( 0 == m_dwRefCount)
       delete this;

    return l;
  }

我们现在几乎完成了。

现在,在对话框类的 SendToServer 按钮点击事件处理程序中,我们将完成最后一点代码。

#include "Sink.h"          // for our CSink class
#include <atlbase.h>       // for ATL smart pointers

void CConnectionClientDlg::OnSendToServer()
     //SendToServer button click event
{
UpdateData(1);
HRESULT hr;

//call CoInitialize for COM initialisation

hr =CoInitialize(NULL);
if(hr != S_OK)
  return -1;

// create an instance of the COM object

CComPtr<IAdd> pAdd;
hr =pAdd.CoCreateInstance(CLSID_Add);
if(hr != S_OK)
  return -1;

IConnectionPointContainer  * pCPC;
  //IConnectionPoint       * pCP;
  //these are declared as a dialog's member
  //DWORD                  dwAdvise;
  //variables,shown here for completeness

  //check if this interface supports connectable objects

  hr = pAdd->QueryInterface(IID_IConnectionPointContainer,
                           (void **)&pCPC);

  if ( !SUCCEEDED(hr) )
  {
    return hr;
  }

  //
  //OK, it does; now get the correct connection point interface
  //in our case IID_IAddEvents

  hr = pCPC->FindConnectionPoint(IID__IAddEvents,&pCP);

if ( !SUCCEEDED(hr) )
  {
    return hr;
  }

//we are done with the connection point container interface

pCPC->Release();

IUnknown *pSinkUnk;

// create a notification object from our CSink class
//

CSink *pSink;
  pSink = new CSink;

  if ( NULL == pSink )
  {
    return E_FAIL;
  }

  //Get the pointer to CSink's IUnknown pointer (note we have
  //implemented all this QueryInterface stuff earlier in our
  //CSinkclass

hr = pSink->QueryInterface (IID_IUnknown,(void **)&pSinkUnk);

//Pass it to the COM through the COM's _IAddEvents
//interface (pCP) Advise method; Note that (pCP) was retrieved
//through the earlier FindConnectoinPoint call
//This is how the com gets our interface, so that it just needs
//to call the interface method when it has to notify us

hr = pCP->Advise(pSinkUnk,&dwAdvise);

//dwAdvise is the number returned, through which
//IConnectionPoint:UnAdvise is called to break the connection

//now call the COM's add method, passing in 2 numbers
  pAdd->Add(m_number1 ,m_number2);
//do whatever u want here; once addition is here a message box
//will pop up showing the result

//pCP->Unadvise(dwAdvise); call this when you need to
//disconnect from server
pCP->Release();

  return hr;
}

现在,构建对话框 EXE。然后运行它。连接点的内容就到此为止。

© . All rights reserved.