分享
 
 
 

COM中的可连接对象与连接点机制及其MFC程序实现

王朝vc·作者佚名  2006-01-08
窄屏简体版  字體: |||超大  

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

为了在组件对象和客户之间提供更大的交互能力,组件对象也需要主动与客户进行通信。组件对象通过出接口(Outgoing Interface)与客户进行通信。如果一个组件对象定义了一个或者多个出接口则此组件对象叫做可连接点对象。

所谓出接口也是COM接口。每个出接口包含一组成员函数,每个成员函数代表了一个事件、一个通知或者一个请求。但是这些接口是在客户的事件接收器(sink)中实现的,所以叫出接口。事件接收器也是COM对象。

可连接对象必须实现一个IConnectionPointContainer接口用于管理所有的出接口。每个出接口对应一个连接点对象,连接点对象实现了IConnectionPoint接口。客户正是通过IConnectionPoint接口与可连接对象建立连接。每一个连接用CONNECTDATA结构描述。

CONNECTDATA包含两个成员:IUnknown* pUnk和DWORD dwCookie。pUnk对应于客户中事件接收器的IUnknown接口指针;dwCookie是由连接点对象生成的用于唯一标识此连接的32位整数。

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

通过另一个有可连接对象实现的枚举器接口IEnumConnections,无论客户还是可连接对象都可以访问一个连接点上的所有连接。通过IConnectionPoint::EnumConnections(IEnumConnections**)函数可以获得IEnumConnections接口指针。

综上所述,一个可连接对象必须实现四个接口:IConnectionPointContainer、IConnectionPoint、IEnumConnectionPoints、IEnumConnections。这四个接口的定义请阅读MSDN文档。

现在结合后面的示例简单描述一下可连接对象和客户通信的过程。在后面的示例中,可连接对象ConnObject定义了出接口IEventSink,对应此出接口,实现了一个连接点对象SampleConnPoint(此对象实现了对应于出接口的连接点接口IConnectionPoint,接口ID为IID_IEventSink)。

客户在获取了可连接对象的IUnknown接口指针m_pIUnknown后,调用

m_pIUnknown->QueryInterface(IID_IConnectionPointContainer,(void**)&pConnPtCont);如果调用成功,pConnPtCont中将存放可连接对象的IConnectionPointContainer接口指针。如果调用不成功,则表明对象不是可连接对象。

调用pConnPtCont->FindConnectionPoint(IID_IEventSink,&pConnPt)。如果调用成

功,pConnPt将存放对应于出接口IEventSink的连接点对象SampleConnPoint所实现的连接点接口IConnectionPoint指针;如果调用不成功,说明可连接对象不支持出接口IEventSink。

调用pConnPt->Advise(pIEventSink,&m_dwCookie)以建立事件接收器(EventSink)

与连接点的连接。其中pIEventSink是客户事件接收器IUnknown接口的指针,此指针通过此函数传递给了可连接对象以便可连接对象发起对客户的通信;m_dwCookie是连接标识,此值由可连接对象设置由客户保存,客户还要使用此值以断开连接。

可连接对象可以通过连接点调用客户事件接收器中的方法。在客户与连接点成功

建立连接后,连接点中已经保存了客户事件接收器接口的指针并可以调用pConnPt->GetConnections()来获取。

客户调用pConnPt->Unadvise(m_dwCookie)来取消连接,同时调用pConnPt->Release()释放连接点对象。

编程实例

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

需要说明的是,MFC通过CCmdTarget类实现了IConnectionPointContainer和IEnumConnectionPoints接口,此外,通过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, 0x30 } };

// {EE888B02-EA9C-11d3-97B5-5254AB191930}

static const IID IID_IEventServer = //一般的COM接口,客户使用此接口的方法

//DoSomething()

{ 0xee888b02, 0xea9c, 0x11d3, { 0x97, 0xb5, 0x52, 0x54, 0xab, 0x19, 0x19, 0x30 } };

//// {EE888B03-EA9C-11d3-97B5-5254AB191930}

static const IID IID_IEventSink = //连接点对象所实现的连接点接口ID

{ 0xee888b03, 0xea9c, 0x11d3, { 0x97, 0xb5, 0x52, 0x54, 0xab, 0x19, 0x19, 0x30 } };

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中加上#include “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宏声明了实现连接点的嵌套类SampleConnPoint,并且是基于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_IConnectionPointContainer,ConnPtContainer)以实现IConnectionPointContainer接口.注意,CCmdTarget类内嵌有才ConnPtContainer类以实现IConnectionPointContainer接口,并用m_xConnPtContainer加以记录.

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**)&pIEventServer);

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文档中有一个示例.

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有