分享
 
 
 

COM中事件驱动技术探讨

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

COM中事件驱动技术探讨

邹飞

版本v1.0

2004年7月

目 录

1. 问题的提出.... 3

2. 名次术语.... 3

3. 常用技术.... 3

3.1 紧密耦合事件(Tightly Coupled Events,TCE)... 3

3.1.1 连接点技术... 3

3.1.2 消息队列技术... 11

3.2 松散耦合事件(Loosely Coupled Events,LCE)... 11

3.2.1 COM+的事件驱动... 11

4. 总结.... 18

1. 问题的提出 类似于设计模式中Observer模式,在COM编程中,希望实现一种机制,使得对数据变化感兴趣的若干部分能够接受到数据的变化通知。一个典型的应用:计算机监控程序在计算机状态数据发生变化时通知系统管理员、系统日志程序、发送电子邮件等等。

2. 名次术语 订阅者Subscriber:对数据感兴趣的程序

发布者Publisher:发布数据变化通知的程序

激发事件Firing Event:发布者发起的通知过程

源接口(Source)/出(Outgoing)接口:发布者和订阅者之间达成的一致接口

接收器(Sink):订阅者提供给发布者的对象

3. 常用技术 3.1 紧密耦合事件(Tightly Coupled Events,TCE) 3.1.1 连接点技术 COM中提供了连接点的技术用于实现事件驱动。

连接点技术的工作方式为:

n 订阅者通过查询标准COM接口IConnectionPointContainer,询问发布者是否支持连接点机制

n 通过IConnectionPointContainer的FindConnectionPoint方法得到某种特定类型的连接点,通过接口IConnectionPoint返回

n 订阅者创建一个接收器(Sink)对象

n 订阅者通过IConnectionPoint的Advise方法把接收器对象加入到发布者的接收者名单中,返回一个cookie(DWORD的标识)

n 订阅者通过IConnectionPoint的Unadvise取消对该事件的关注

从上面的工作方式可以发现,要实现连接点技术,只要订阅者和发布者遵循一定的接口规范,并对这些接口进行实现即可。

发布者:实现IConnectionPointContainer的容器、支持IConnectionPoint的连接点

订阅者:实现Sink对象

在VC中,提供了多种机制对连接点的实现进行了简化,使得开发连接点程序变得很简单,在VC6和VC7中均对连接点有很好的支持,同时又有一定差别,本文分别介绍。

实例介绍

首先介绍一下我们要实现的实例的功能描述:

发布者:实现一个Add方法的接口,当Add方法被调用时,如果结果大于100,则调用OnAdd事件,将Add的参数传出去,实现为一个COM的DLL

订阅者:订阅OnAdd事件,当OnAdd事件被调用时,输出参数值,实现为Dialog Based Application。

Visual C++ 6.0

Visual C++从5.0后提供了ATL(Active Template Library,活动模板库),它一套可用于开发轻量级COM组件的开发库(在ATL里使用非常复杂难懂的Template,理解起来很麻烦~~~)

我们的第一种方法就是通过ATL来实现连接点:

发布者:

n 通过ATL COM Wizard新建一个COM组件

项目名称为XAdd

n 通过ATL Object Wizard新建一个COM对象(Simple Object)

Short Name设为Add(其他的名字Wizard会自动产生),同时,在Attributes中钩上Support Connection Points,以使得该COM对象支持连接点,这会自动产生一个_IAddEvents的接口。

n 给Add增加Add方法

HRESULT Add([in] LONG a, [in] LONG b, [out] LONG* pVal);

n 给_IAddEvents增加OnAdd方法

HRESULT OnAdd([in] LONG a, [in] LONG b);

n F7编译,自动注册COM组件

n 在CAdd的右键菜单中选择Implement Connection Point,选择_IAddEvents

Wizard会自动给CAdd增加上对IConnectionPointContainer接口的实现,并会增加CProxy_IAddEvents,它对IConnectionPoint接口进行了实现。

n 在CAdd的Add方法实现中增加对事件的触发:

STDMETHODIMP CAdd::Add(LONG a, LONG b, LONG* pVal)

{

*pVal = a + b;

Fire_OnAdd(a, b); // 触发OnAdd事件

return S_OK;

}

n 重新编译、注册即可。

订阅者:

n 新建一个MFC AppWizard(exe),名为XAddClient,类型为Dialog based,其余采用默认即可。

n 添加对ATL的支持(一种简单的方法是选择New ATL Class,由于不能在MFC Application中加入ATL Class,但这是ATL的支持已经被加入)

n 在项目中加入XAdd.h文件(发布者的接口定义头文件)

n 在项目中导入COM的类型库

在stdafx.h中加入:

#import "../XAdd.tlb" no_namespace, named_guids

n 新建一个类CEventSink,public继承自IDispEventImpl<1, CEventSink, &DIID__IAddEvents, &LIBID_XADDLib>

namespace {

_ATL_FUNC_INFO OnAddInfo =

{

CC_STDCALL,

VT_EMPTY,

2 ,

{VT_I4, VT_I4}

};

}

class CEventSink : public IDispEventImpl<1, CEventSink, &DIID__IAddEvents, &LIBID_XADDLib>

{

public:

CEventSink();

virtual ~CEventSink();

void __stdcall OnAdd(int a, int b);

BEGIN_SINK_MAP(CEventSink)

SINK_ENTRY_INFO(1, DIID__IAddEvents, 1, OnAdd, &OnAddInfo)

END_SINK_MAP()

};

n 实现CEventSink的OnAdd方法

void __stdcall CEventSink::OnAdd(int a, int b)

{

CString str;

str.Format("%d %d", a, b);

AfxMessageBox(str);

}

n 这样,接收器对象就已经实现完成,下面只要把接收器Advise到XAdd对象上即可。

n 在Dialog中加入Sink对象的Advise、并调用Add()方法,这个步骤比较简单,可以参考例程

n 定义private变量:

IAddPtr pAdd;

DWORD dwCookie;

CEventSink* pSink;

n 在OnInitDialog()中创建COM对象

CoInitialize(NULL);

pAdd.CreateInstance(__uuidof(Add));

n 在DestroyWindow中销毁COM对象

pAdd = NULL;

CoUninitialize();

n 增加3个按钮方法:OnCallAdd、OnAdvise、OnUnadvise

void CXAddClientDlg::OnCallAdd()

{

LONG ret;

pAdd->Add(1, 2, &ret);

}

void CXAddClientDlg::OnAdvise()

{

if (pSink == NULL)

{

pSink = new CEventSink();

AtlAdvise(pAdd, (IUnknown*)pSink, DIID__IAddEvents, &dwCookie);

}

}

void CXAddClientDlg::OnUnadvise()

{

if (pSink != NULL)

{

AtlUnadvise(pAdd, DIID__IAddEvents, dwCookie);

delete pSink;

}

}

我们可以发现,ATL为我们定义了一组Wizard、Macro、Template等,使得实现COM对象变得很简单,我们不需要去关心AddRef()、Release()等很多通用方法的实现,ATL已经帮我们做得很好。

除了使用ATL作为开发COM的开发库,MFC也提供了很好的COM支持,因此也可以通过MFC实现连接点技术,本文不再介绍。

Visual C++ 7.0

在Microsoft推出Visual Studio.NET后,同时也对VC进行了升级,在Visual C++ 7.0中,提供了很多对C++的扩充,包括更好的对COM的支持。

在VC 7.0种可以通过__hook、__unhook关键字更方便地实现事件驱动。

这里给出一个具体的实现例子(开发环境为:Microsoft Visual C++.NET 中文版):

发布者:

n 在VC 7.0中新建一个ATL项目,名为XAdd

n 添加ATL 简单对象,名为Add,在选项中增加对连接点的支持:

n 这里有个小小的问题,通过Wizard自动产生的接口定义是和接口的实现类定义放在一起的(Add.h),但接口定义应该是客户可见的,而接口的实现类则不应该客户可见,因此我们做个改动,把Add.h中的接口定义copy到另一个文件IAdd.h中,并在Add.h中#include “IAdd.h”

n 在IAdd中添加方法Add

HRESULT Add([in] LONG a, [in] LONG b, [out,retval] LONG* pVal);

n 在_IAddEvents中添加事件方法OnAdd

HRESULT OnAdd([in] LONG a, [in] LONG b);

n 实现CAdd中的Add方法

STDMETHODIMP CAdd::Add(LONG a, LONG b, LONG* pVal)

{

*pVal = a + b;

__raise OnAdd(a, b); // 触发事件

return S_OK;

}

n 编译、注册

订阅者:

n 新建Win32 控制台项目,名为AddClient,应用程序设置中添加ATL支持

n 添加ATL支持

在stdafx.h中增加:

#define _ATL_ATTRIBUTES

#include #include #include n 添加类AddProxy,用于实现Sink对象,同时作为客户端事件驱动代理 头文件AddProxy.h #pragma once #include #include "../IAdd.h" [ module(name = "Receiver") ]; [ event_receiver(com) ] class CAddProxy { public: CAddProxy(void); virtual ~CAddProxy(void); void OnAdd(LONG a, LONG b); private: void Hook(IAdd* pS); void UnHook(IAdd* pS); LONG Add(LONG a, LONG b); // 对COM对象的包装 _COM_SMARTPTR_TYPEDEF(IAdd, __uuidof(IAdd)); IAddPtr m_pAdd; }; 实现文件AddProxy.cpp #include "StdAfx.h" #include ".\addproxy.h" #include using namespace std; CAddProxy::CAddProxy(void) { m_pAdd.CreateInstance("XAdd.Add"); // 创建COM对象 Hook(m_pAdd); // 挂接事件 } CAddProxy::~CAddProxy(void) { UnHook(m_pAdd); // 取消挂接 m_pAdd = NULL; // 销毁COM对象 } void CAddProxy::OnAdd(LONG a, LONG b) { cout << a << b << endl; // 事件代码 } void CAddProxy::Hook(IAdd* pS) { __hook(&_IAddEvents::OnAdd, pS, &CAddProxy::OnAdd); // 挂接 } void CAddProxy::UnHook(IAdd* pS) { __unhook(pS); // 取消挂接 } LONG CAddProxy::Add(LONG a, LONG b) { LONG ret; m_pAdd->Add(a, b, &ret); return ret; } n 在main()函数中创建Proxy对象,执行Add()方法。 int _tmain(int argc, _TCHAR* argv[]) { CAddProxy add; add.Add(1, 2); return 0; } n 编译,执行。 如果我们看一下Wizard帮我们生成的代码,我们可以发现,在VC 7.0里对COM的支持多出了很多关键字,如__event、__interface、__raise、__hook、__unhook等,通过这些关键字要实现COM事件驱动是很简单的(即使完全不采用Wizard,而手工编码,也不复杂) 此外,需要说明的是:虽然VC 7.0是在VS.net中提供的,但采用这种方法写出来的COM组件和接收器都可以在Windows 2000等没有.Net Framework的机器上运行,即不需要.NET Framework的支持。 3.1.2 消息队列技术 通过Microsoft提供的MSMQ(Microsoft Message Queue Server,微软消息队列服务器)也可以实现紧密耦合的事件驱动,这不是本文的重点,这里不叙述。

说明:MSMQ只在Windows 2000以后的操作系统提供,且不作为系统的必选安装,需要在安装系统后再增加。

3.2 松散耦合事件(Loosely Coupled Events,LCE) 3.2.1 COM+的事件驱动 虽然通过连接点技术可以实现COM中的事件驱动,但它存在着一些缺点:

n 发布者和订阅者生命周期紧密相关,对于企业应用不很合适

n 连接点在建立和断开连接时需要多次交互,效率较低,对于分布式应用环境存在问题

n TCE没有事件过滤的机制

针对这些问题,COM+中引入了一种发布和订阅LCE事件的机制,称为COM+事件(COM+ Event)。它有着很多好的特性,本文无法对COM+作更详细的介绍,这里只是结合一个实例说明COM+ Event的实现,而对于事件过滤、安全设定等高级选项请参考COM+相关书籍。

实例说明:设计一个股票价格发布和订阅系统,当股票价格发生变动时,发布者自动通知订阅者(调用订阅者的方法)

实现步骤:

1、编写事件组件

n 通过ATL COM Wizard创建COM组件工程XEvent

n 通过ATL Object Wizard创建组件StockEvent,无须设置Attributes中的Support Connection Points

n 给IStockEvent增加方法NewQuote

HRESULT NewQuote([in] BSTR bsSymbol, [in] double dValue);

无须为该方法实现,只须返回S_OK即可。

n 编译,注册

2、安装事件组件(以Windows 2000 Professional为例)

n 打开“控制面板”==〉“管理工具”下的组件服务

n 在COM+应用程序上点右键,“新建”==〉“应用程序”,为事件组件新建一个Application,名为StockApp

n 在新建出的StockApp应用程序下的组件菜单中,点右键,“新建”==〉“组件”

n 选择“安装新的事件类”

n 将第一步编写的事件组件导入,向导会自动发现组件以及组件中的接口。

n 安装完成

3、编写事件订阅服务

n 通过ATL COM AppWizard新建一个COM组件项目,名为StockSubscriber

n 导入事件组件接口

在stdafx.h中加入

#import "../Test.tlb" raw_interfaces_only no_namespace, named_guids

n 在项目中加入文件XEvent.h

n 通过ATL Object Wizard新建一个Simple Object,名为StockEventSubscriber,这一步工作只是为了让ATL自动帮我们产生idl文件以及coclass的C++类包装,所以接口IStockEventSubscriber对我们是没有什么用途的,可以把它删掉(下面会介绍怎么删除),当然也可以不通过ATL Object Wizard而是自己写idl和CStockEventSubscriber。

n 在StockEventSubscriber.idl中修改coclass,使它的默认接口为IStockEvent

修改library部分:

library STOCKSUBSCRIBERLib

{

importlib("stdole32.tlb");

importlib("stdole2.tlb");

importlib("../XEvent/XEvent.tlb");

[

uuid(E04C02F3-F8B4-489C-B91F-A04D3DB5AEFD),

helpstring("StockEventSubscriber Class")

]

coclass StockEventSubscriber

{

[default] interface IStockEvent;

interface IStockEventSubscriber;

};

};

n 给CStockEventSubscriber增加IStockEvent的实现(即当触发事件会执行的代码)

头文件StockEventSubscriber.h

class ATL_NO_VTABLE CStockEventSubscriber :

public CComObjectRootEx<CComSingleThreadModel>,

public CComCoClass<CStockEventSubscriber, &CLSID_StockEventSubscriber>,

public IDispatchImpl<IStockEventSubscriber, &IID_IStockEventSubscriber, &LIBID_STOCKSUBSCRIBERLib>,

public IDispatchImpl<IStockEvent, &IID_IStockEvent, &LIBID_XEVENTLib>

BEGIN_COM_MAP(CStockEventSubscriber)

COM_INTERFACE_ENTRY(IStockEventSubscriber)

COM_INTERFACE_ENTRY(IStockEvent)

COM_INTERFACE_ENTRY2(IDispatch, IStockEvent)

END_COM_MAP()

// IStockEvent

public:

STDMETHOD(NewQuote) (BSTR bsSymbol, double dValue);

实现文件StockEventSubscriber.cpp

STDMETHODIMP CStockEventSubscriber::NewQuote(BSTR bsSymbol, double dValue)

{

TCHAR buf[100];

_stprintf(buf, _T("%s %lf"), bsSymbol, dValue);

::MessageBox(NULL, buf, _T("Stock Price"), MB_OK);

return S_OK;

}

n 编译,注册

n 附:上面的步骤其实产生了一个没有任何用途的“空”接口IStockEventSubscriber,下面给出一个步骤去除它

n 在StockEventSubscriber.idl文件中,删除接口定义

[

object,

uuid(1FBF1C53-35B5-4E59-B821-AE68D16E4536),

dual,

helpstring("IStockEventSubscriber Interface"),

pointer_default(unique)

]

interface IStockEventSubscriber : IDispatch

{

};

删除coclass中的接口说明

interface IStockEventSubscriber;

n 在StockEventSubscriber.h头文件中,删除对IstockEventSubscriber接口的实现

public IDispatchImpl<IStockEventSubscriber, &IID_IStockEventSubscriber, &LIBID_STOCKSUBSCRIBERLib>,

COM_INTERFACE_ENTRY(IStockEventSubscriber)

修改COM_INTERFACE_ENTRY2(IDispatch, IStockEvent)为COM_INTERFACE_ENTRY(IDispatch)

n 编译,注册

4、安装事件订阅服务

n 在“组件服务”==〉“StockApp”的组件下新建组件,选择“安装新组件”,安装StockSubscriber.dll

5、订阅服务

n 在“组件服务”==〉“StockApp”的组件==〉“StockSubscriber.StockEventSubscriber.1”==〉“订阅”点右键,“新建”==〉“订阅”

n 选择订阅方法,直接选中IstockEvent,点“下一步”

n 选择事件类,选中某个事件类,点“下一步”

n 订阅选项,为订阅起一个名称StockSubscriber,选中“立即启用该订阅”,点“下一步”

n 完成订阅

5、测试事件驱动

我们用VB写一个简单的COM组件调用(用VC写也是完全一样的),在里面对Xevent的NewStock()方法进行调用,会发现StockSubscriber中的NewStock方法也被调用了(弹出对话框),证明事件被正确的订阅了。

代码如下:

Private Sub Command1_Click()

Set StockPriceEvent = CreateObject("XEvent.StockEvent")

StockPriceEvent.NewQuote "Test", 100

End Sub

6、制作COM+安装文件

COM+组件服务自动提供了对组件打包分发的功能,可以在“组件服务”==〉“StockApp”点右键,“导出”

然后可以自动生成安装文件,以后可以直接在其他机器上安装,COM+组件就可以正确安装。

其他说明:

n 采用COM+实现还可以获得COM+的其他很多特性,比如JIT、对象池、安全特性等。

n COM+至少要在Windows 2000以上的机器才能够使用。

4. 总结 实现COM的事件驱动包括TCE和LCE两种模式,TCE可以通过Connection Point或消息队列实现,LCE通过COM+订阅者模型实现。

实现连接点:VC7实现起来更简单,对ATL的支持更全面、更稳定,而VC6.0的ATL则经常容易出现一些小问题。

COM+订阅者模型:COM+可以提供更好的运行特性,以及更灵活的配置管理,但只能在Windows 2000以上的机器运行。

在实现事件驱动时根据具体情况选择一种实现。

下载PDF版本

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
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- 王朝網路 版權所有