COM 连接点事件讲解

开发环境:VC++6.0
测试环境:windows 2000

这篇文章是Alex C. Punnen.所写,在http://www.codeguru.com/上发表的,在此本人将其翻译成中文,和大家共享。 (注:本人翻译不当之处,还请各位多指正和谅解)

运行环境:Windows 2000 Server, Microsoft Visual C++ 6

此篇文章将用一个清晰的例子来描述COM中连接点事件的思想,组件将是一个进程内服务,并且用一个MFC客户端来连接此服务。

它究竟是什么?

它是一个COM组件提供被客户端回掉的方法。换句话说,就是客户端从COM组件得到一个回调通知。

也许你已经熟悉回调方法。很好,连接点事件就是如此。假设你有一个COM对象,暴露了一个Iarithematic接口,并且有一个接口方法为:Add(int a,int b)。想象这个方法将花费很长时间并且你不想一直等待它执行完为止。你可以用这段时间做别的一些事情。因此连接点事件将帮助你解决此问题。你可以在客户端赋一个ExecutionOver(int Result)方法,COM组件在执行完Add方法后触发ExecutionOver(int Result)方法。

因此,当客户端组件完成此任务,它将调用客户端的ExecutionOver方法。客户端也许用一个对话框抛出这个结果。那是一个完整体系。下面我们将详细介绍如何用ATL实现COM中连接点事件。

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接口被包含在IConnectionPoint and IconnectionPointContainer。这个对象将实现这两个接口。

这两个接口定义如下:

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对象。一旦COM客户端有了一个最初接口,这个客户端就可以询问COM对象是否支持可连接事件,通过QueryInterface方法请求IconnectionPointContainer接口。如果请求成功,表明为可连接对象并返回IconnectionPointContainer接口指针,否则说明对象为不可连接对象。

   一旦,客户端知道COM对象支持可连接事件,客户得到IconnectionPointContainer接口指针后,调用其成员函数获取相应出接口连接点对象。如果COM对象实现了此出接口,那么COM对象将返回一个连接点接口指针。接着,客户端利用IconnectionPoint接口并调用IConnectionPoint::Advise( [in] IUnknown *pUnk, [out] DWORD *pdwCookie)方法去移交回调函数的实现。为了变得更清晰,通过Advise方法得到指向Iunknown接口的指针被定义并实现在客户端的EXE里。

好了,让我们用一个实际的例子来演示整个实现过程。

  1. 利用ATL-COM AppWizard 创建一个进程内COM组件,此项目命名为“ConnectionCom
  2. 右键点击类视图,创建一个ATL对象,如图

命名为Add,接口(IAdd)

在点击Ok之前,请确认选中“ Support Connection point checkbox”,在“Attributes”页上。

   点击OK。

注释:创建的类显示在类视图中,你将发现一个Iadd和_AddEvents接口。后者恰恰是一个代理类,它将在客户端中被实现。它的出现是因为我们选中的Connection_Points check box。

添加一个方法'Add(int a,int b) '到Iadd接口中,添加一个方法'ExecutionOver(int Result)'到_IaddEvents接口中。类视图如下:

下面让我们来看看生成IDL文件。

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

// 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处理。现在右键点击的Cadd类,并点击Implement Connection Point。

在确认对话框选中 _IAddEvents 。

CProxy_IaddEvets类被生成并带有一个Fire_ExecutionOver(int result) 方法.这个类将关心COM对象如何调用客户端接口。现在,让我们来实现原始的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;
}
//======================================================

编译项目,并用Tools—》Register Control菜单注册COM组件。

现在,编写客户端调用

Create a new MFC AppWIzard(exe) Dialog based project—ConnectionClient. It looks like this:

创建一个MFC Dialog项目—ConnectionClient,如下:

 

创建一个Csink类,从_IaddEvents继承。你可以用类向导来完成此任务,选择通用类。由于我们从_IaddEvents继承,所以头文件必须包含它的定义,所以拷贝ConnectionCOM.h 和 ConnectionCOM.tlb文件到客户端项目目录中,并且增加这些连接到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;;
 

  };
 

Csink类中定义一个记数变量

private:
DWORD       m_dwRefCount;
 

并在Csink()构造函数中,初始化m_dwRefCount为0。

CSink::CSink()
{
 m_dwRefCount =0;
}
 

下面实现 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”按钮的Click事件中,添加代码:

#include "Sink.h"          // for our CSink class
#include        // 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 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;
}
 

现在编译客户端程序,并运行。

你可能感兴趣的:(M,ATL)