荔园在线

荔园之美,在春之萌芽,在夏之绽放,在秋之收获,在冬之沉淀

[回到开始] [上一篇][下一篇]


发信人: Second (石开), 信区: Program
标  题: [转载] COM中的可连接对象与连接点机制及其MFC程序实
发信站: 荔园晨风BBS站 (Thu Jul 19 08:30:58 2001), 转信

可连接对象和连接点机制的基本原理

为了在组件对象和客户之间提供更大的交互能力,组件对象也需要主动与客户进行通信
。组件对象通过出接口(outgoing interface)与客户进行通信。如果一个组件对象定
义了一个或者多个出接口则此组件对象叫做可连接点对象。
所谓出接口也是com接口。每个出接口包含一组成员函数,每个成员函数代表了一个事件
、一个通知或者一个请求。但是这些接口是在客户的事件接收器(sink)中实现的,所
以叫出接口。事件接收器也是com对象。

可连接对象必须实现一个iconnectionpointcontainer接口用于管理所有的出接口。每个
出接口对应一个连接点对象,连接点对象实现了iconnectionpoint接口。客户正是通过
iconnectionpoint接口与可连接对象建立连接。每一个连接用connectdata结构描述。
connectdata包含两个成员:iunknown* punk和dword dwcookie。punk对应于客户中事件
接收器的iunknown接口指针;dwcookie是由连接点对象生成的用于唯一标识此连接的32
位整数。

通过一个由可连接对象实现的枚举器接口ienumconnectionpoints,客户可以访问可连接
对象的所有连接点。但是要获得ienumconnectionpoints接口指针,要通过iconnection
pointcontainer::enumconnectionpoints(ienumconnectionpoints**)函数,此函数
返回枚举器接口指针。

通过另一个有可连接对象实现的枚举器接口ienumconnections,无论客户还是可连接对
象都可以访问一个连接点上的所有连接。通过iconnectionpoint::enumconnections(
ienumconnections**)函数可以获得ienumconnections接口指针。
综上所述,一个可连接对象必须实现四个接口:iconnectionpointcontainer、iconnec
tionpoint、ienumconnectionpoints、ienumconnections。这四个接口的定义请阅读ms
dn文档。

现在结合后面的示例简单描述一下可连接对象和客户通信的过程。在后面的示例中,可
连接对象connobject定义了出接口ieventsink,对应此出接口,实现了一个连接点对象s
ampleconnpoint(此对象实现了对应于出接口的连接点接口iconnectionpoint,接口id为
iid_ieventsink)。

客户在获取了可连接对象的iunknown接口指针m_piunknown后,调用
m_piunknown->queryinterface(iid_iconnectionpointcontainer,(void**)&pconnptco
nt);如果调用成功,pconnptcont中将存放可连接对象的iconnectionpointcontainer接
口指针。如果调用不成功,则表明对象不是可连接对象。
调用pconnptcont->findconnectionpoint(iid_ieventsink,&pconnpt)。如果调用成
功,pconnpt将存放对应于出接口ieventsink的连接点对象sampleconnpoint所实现的连
接点接口iconnectionpoint指针;如果调用不成功,说明可连接对象不支持出接口ieve
ntsink。

调用pconnpt->advise(pieventsink,&m_dwcookie)以建立事件接收器(eventsink)
与连接点的连接。其中pieventsink是客户事件接收器iunknown接口的指针,此指针通过
此函数传递给了可连接对象以便可连接对象发起对客户的通信;m_dwcookie是连接标识
,此值由可连接对象设置由客户保存,客户还要使用此值以断开连接。
可连接对象可以通过连接点调用客户事件接收器中的方法。在客户与连接点成功
建立连接后,连接点中已经保存了客户事件接收器接口的指针并可以调用pconnpt->get
connections()来获取。

客户调用pconnpt->unadvise(m_dwcookie)来取消连接,同时调用pconnpt->release()释
放连接点对象。
编程实例

现在用mfc实现一个可连接对象,然后写一个极为简单的客户和时间接收器。

需要说明的是,mfc通过ccmdtarget类实现了iconnectionpointcontainer和ienumconne
ctionpoints接口,此外,通过cconnectionpoint类实现了iconnectionpoint接口
可连接对象connobject

在这个对象中,实现一个一般的com接口ieventserver,客户可以使用此接口的
方法dosomething()作一些事情,但主要的是对象将在此处触发事件。sampleconnpoint

实现连接点对象。
在guids.h中写入:
// {ee888b01-ea9c-11d3-97b5-5254ab191930}
static const iid clsid_connobject = //组件id
{ 0xee888b01, 0xea9c, 0x11d3, { 0x97, 0xb5, 0x52, 0x54, 0xab, 0x19, 0x19, 0x
30 } };
// {ee888b02-ea9c-11d3-97b5-5254ab191930}
static const iid iid_ieventserver = //一般的com接口,客户使用此接口的方法
//dosomething()
{ 0xee888b02, 0xea9c, 0x11d3, { 0x97, 0xb5, 0x52, 0x54, 0xab, 0x19, 0x19, 0x
30 } };
//// {ee888b03-ea9c-11d3-97b5-5254ab191930}
static const iid iid_ieventsink = //连接点对象所实现的连接点接口id
{ 0xee888b03, 0xea9c, 0x11d3, { 0x97, 0xb5, 0x52, 0x54, 0xab, 0x19, 0x19, 0x
30 } };

2. 在iconnobject.h中写入
#include "guids.h"
//声明ieventserver接口
declare_interface_(ieventserver,iunknown)
{
stdmethod(dosomething)()pure;
};
//声明出接口,此出接口将由客户的事件接收器实现
declare_interface_(ieventsink,iunknown)
{
stdmethod(eventhandle)()pure;
};

3.添加基类为ccmdtarget的类cconnobject.在类声明文件cconnobject1.h中加上#inclu
de “iconnobject.h”,在类声明中写入:
protected:
……

//声明实现ieventserver接口的嵌套类
begin_interface_part(eventserver,ieventserver)
stdmethod(dosomething)();
end_interface_part(eventserver)
declare_interface_map()

//声明实现连接点的嵌套类
begin_connection_part(cconnobject,sampleconnpoint)
connection_iid(iid_ieventsink)
end_connection_part(sampleconnpoint)
declare_connection_map()
declare_olecreate(cconnobject)
说明:begin_connection_part和end_connection_part宏声明了实现连接点的嵌套类sam
pleconnpoint,并且是基于cconnectionpoint类的,如果需要重载cconnectionpoint类的
成员函数或者添加自己的成员函数,可以在这两个宏中声明.这里,connection_iid宏重载
了cconnectionpoint::getiid()函数.使用declare_connection-map()宏声明连接点映射
表.

4.在类cconnobject的实现文件中写入
implement_olecreate(cconnobject,"connobject",
0xee888b01, 0xea9c, 0x11d3, 0x97, 0xb5, 0x52, 0x54, 0xab, 0x19, 0x19, 0x30);

begin_interface_map(cconnobject,ccmdtarget)
interface_part(cconnobject,iid_ieventserver,eventserver)
interface_part(cconnobject,iid_iconnectionpointcontainer,connptcontainer)
end_interface_map()
begin_connection_map(cconnobject,ccmdtarget)
connection_part(cconnobject,iid_ieventsink,sampleconnpoint)
end_connection_map()

说明:a.必须在接口映射中写入interface_part(cconnobject,iid_iconnectionpointco
ntainer,connptcontainer)以实现iconnectionpointcontainer接口.注意,ccmdtarget类
内嵌有才connptcontainer类以实现iconnectionpointcontainer接口,并用m_xconnptco
ntainer加以记录.
b.用begin_connection_map和end_connection_map宏实现连接点映射.connection_part
定义了实现连接点的类.

5.在cconnobject::cconnobject()中写入:
enableconnections();

6.实现ieventserver接口
ieventserver接口是基于iunknown接口的,实现iunknown接口的方法这里不在赘述.在实
现文件中写入:
stdmethodimp
cconnobject::xeventserver::dosomething()
{
//dosomething
method_prologue(cconnobject,eventserver)
pthis->fireevent();
return s_ok;
}
dosomething()方法可以为客户提供需要的服务.这里着重的是可连接对象在此处触发客
户事件接收器的事件,fireevent()函数是connobject类实现的专门触发事件的的函数,代
码如下:
void cconnobject::fireevent()
{
//获取连接点上的连接指针队列
const cptrarray* pconnections = m_xsampleconnpoint.getconnections();
assert(pconnections!=null);
int cconnections = pconnections->getsize();
ieventsink* pieventsink;
//对每一个连接触发事件
for(int i = 0; i < cconnections; i++)
{
//获取客户事件接收器接口指针
pieventsink = (ieventsink*)(pconnections->getat(i));
assert(pieventsink!=null);
//调用客户事件接受器事件处理函数
//此函数是出接口定义,由客户事件接收器实现的
pieventsink->eventhandle();
}
}
客户事件接收器(sink)
事件接收器也是com对象,也可以用嵌套类来实现,但是它只是客户的一个内部对
象,所以可以没有clsid和类厂.下面示例是一个对话框程序,对话框有三个按钮:”连接”
(idc_connect),”断开”(idc_disconnect),”事件”(idc_event).
创建一个基于对话框的工程:connclient.
在cconnclientdlg中首先加入#include “iconnobject.h”,然后在对话框类声明中声明

事件接收器嵌套类:
begin_interface_part(eventsink,ieventsink)
stdmethod(eventhandle)();
end_interface_part(eventsink)

同时声明几个私有变量:
private:
lpconnectionpointcontainer pconnptcont;//记录组件对象
//iconnectionpointcontainer接口指针
lpconnectionpoint pconnpt;//记录连接点接口指针
dword m_dwcookie;//记录连接标识
iunknown* m_piunknown;//用以记录组件对象iunknown接口指针

实现事件接收器:
stdmethodimp_(ulong)
cconnclientdlg::xeventsink::addref()
{
return 1;
}
stdmethodimp_(ulong)
cconnclientdlg::xeventsink::release()
{
return 0;
}
stdmethodimp
cconnclientdlg::xeventsink::queryinterface(refiid riid,void** ppvobj)
{
method_prologue(cconnclientdlg,eventsink)
if(isequaliid(riid,iid_iunknown)||
isequaliid(riid,iid_ieventsink))
{
*ppvobj = this;
addref();
return s_ok;
}
else
{
return e_nointerface;
}
}
stdmethodimp
cconnclientdlg::xeventsink::eventhandle() //此函数将被可连接对象调用
{
::afxmessagebox("源对象向事件接收器发出了的通知!");
return s_ok;
}

初始化com库并创建组件对象实例
在cconnclientdlg::oninitdialog()中写入:
hresult hresult;
hresult = ::coinitialize(null);
if(failed(hresult))
{
::afxmessagebox("不能初始化com库!");
return false;
}
m_piunknown = null;
hresult = ::cocreateinstance(clsid_connobject,null,
clsctx_inproc_server,iid_iunknown,(void**)&m_piunknown);
if(failed(hresult))
{
m_piunknown = null;
::afxmessagebox("不能创建connobject对象!");
return false;
}
m_dwcookie = 0;//预置连接标识为0
在按钮”连接”(idc_connect)的click事件处理函数void cconnclientdlg::onconnect
()中写入:
void cconnclientdlg::onconnect()
{
if(m_dwcookie!=0)
{
return;
}
if(m_piunknown!=null)
{
hresult hresult;
hresult = m_piunknown->queryinterface(iid_iconnectionpointcontainer,
(void**)&pconnptcont);
if(failed(hresult))
{
::afxmessagebox("不能获取对象的iconnectionpointcontainer接口!");
return;
}
assert(pconnptcont!=null);
hresult = pconnptcont->findconnectionpoint(iid_ieventsink,&pconnpt);
if(failed(hresult))
{
pconnptcont->release();
::afxmessagebox("不能获取对象的ieventsink连接点接口!");
return;
}
assert(pconnpt!=null);
//获取事件接收器指针
iunknown* pieventsink;
m_xeventsink.queryinterface(iid_iunknown,(void**)&pieventsink);
//通过连接点接口的advise方法将事件接收器指针传给可连接对象
if(succeeded(pconnpt->advise(pieventsink,&m_dwcookie)))
{
::afxmessagebox("与可连接对象connobject建立连接成功!");
}
else
{
::afxmessagebox("不能与connobject建立连接!");
}
pconnpt->release();
pconnptcont->release();
return;
}
}

上述代码与可连接对象的连接点建立连接.
编写按钮”断开”(idc_disconnect)的click处理函数如下:
void cconnclientdlg::ondisconnect()
{
if(m_dwcookie==0)
{
return;
}
pconnpt->unadvise(m_dwcookie);
pconnpt->release();
pconnptcont->release();
m_dwcookie = 0;
}

编写按钮”事件”(idc_event)的click处理函数:
void cconnclientdlg::onevent()
{
if(m_piunknown!=null)
{
ieventserver* pieventserver;
hresult hresult;
hresult = m_piunknown->queryinterface(iid_ieventserver,(void**)&pieventserve
r);
if(failed(hresult))
{
::afxmessagebox("不能获取ieventserver接口!");
return;
}
pieventserver->dosomething();
}
}

这里,客户调用组件提供的服务dosomething(),而正如前面所看到的,组件对象将在这个
函数中触发一个由客户事件接收器处理(cconnclientdlg::xeventsink::eventhandle()
)的事件.
在退出应用时:
void cconnclientdlg::oncancel()
{
m_piunknown->release();
::couninitialize();
cdialog::oncancel();
}

运行程序后,首先点击”连接”,然后点击”事件”按钮,这时将弹出messagebox,并提示
” 源对象向事件接收器发出了的通知!”.

小结
正是由于有了可连接对象这一机制,实现了客户与组件对象的双向通信,使组件对象具有
了事件机制.这种类似于”服务器推送(server push)”的技术在分布式应用系统中十分
重要.

本文所举示例是用基于iunknown接口实现的,其实,用自动化接口idispatch作为出接口更
为方便.需要说明的是,用atl来写可连接对象更为简洁,msdn文档中有一个示例.

--
                            既然热爱生命
                            那么,
                            一切都在意料之中。

※ 来源:·荔园晨风BBS站 bbs.szu.edu.cn·[FROM: 192.168.28.190]


[回到开始] [上一篇][下一篇]

荔园在线首页 友情链接:深圳大学 深大招生 荔园晨风BBS S-Term软件 网络书店