COM 连接点事件 转

 转 http://blog.csdn.net/wovow/archive/2004/12/12/214017.aspx

开发环境: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 <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;
}
 
现在编译客户端程序,并运行。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值