开发环境: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中连接点事件。
想象一下,客户端暴露一个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里。
好了,让我们用一个实际的例子来演示整个实现过程。
命名为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;
}
现在编译客户端程序,并运行。