摘要 作为研究的热点,近年来提出构件模型层出不穷.出于相互之间互操作性的考虑,必须具备一个公共的构件
底层框架.分析各种实际的因素,COM是一个可行的选择.本文讨论了COM规范对组件复用的支持及其发展前景,提
出了基于COM的复用思想:以接口的标准化推动服务的标准化,为复用软件的开发和使用建立规范.
关键词 软件复用,COM,分布对象,组件,面向对象
构件复用作为一种现实有效的软件开发方法正受到越来越多的重视和研究。近年来,随着DNA,中间件等三层体
系结构和分布式对象的兴起,为基于可复用构件的软件体系结构赋予了更大的内涵。从而使我们在评价构件复
用的意义时,不应只局限于开发周期的缩短,软件质量的可靠提高,更要认识到构件组装式的开发在系统维护(如
版本更新,功能增删等),分布式计算等方面所带来的极大便利。由于维护阶段占软件开发周期总成本的67%左右
,因此构件重用对整个软件产业的重要意义就不言而喻了.从维护和分布计算的角度考虑,构件复用就是要实现
"即插即用".
传统的面向对象编程语言中,基于继承机制的类的复用,只是源代码级的重用,在源代码不可得的情况下(构件产
业化的发展趋势下,这种情况很普遍),就变得毫无意义了.更为重要的是,联编以后,类构件就只是一个逻辑上的
虚幻的概念了,不会给将来可能的处理带来任何方便.
MicroSoft自90年代初就进行了COM的开发,历经OLE1,VBX组件,OLE2,ActiveX,COM+的不断完善,现已成为一个相
当成熟的组件模型,对构件复用提供了有力的底层支持.
一 COM对象的封装COM是一个二进制的标准,它详细规定了一个COM组件所应具有的内存结构.COM对象间的交互完全基于对此内存
结构的操作.因此可以在很大程度上忽略不同编程语言,应用环境之间的差别,解决了重新编译重新发行的问题
.二进制代码级的兼容性要求操作系统的支持,但是COM描述对象连接的方法与传统的API式共享系统服务不同.
连接建立后,COM底层库不再被需要,停止耗用系统资源,与API相比,操作系统必须一直管理组件之间的连接.
COM用接口的概念对组件的功能属性进行完全的封装.与组件的通信必须通过接口进行.接口不仅是一个逻辑上
的概念,而且也存在着与之相对应的物理内存结构(VTABLE).一个对象可以对应多个接口,一个接口也可以由多
个对象所实现,表现出灵活的多态性.同时也为版本管理提供了方便.当使用新版本的组件替换老版本时,只要该
组件实现了旧版本的接口(通过包容,聚合等手段),就保证了其与原用软件系统的兼容.同时新增功能(新的接口
)又可被自然的使用.
接口完全封装了内部功能,属性的具体实现,使得COM对象对外表现为"黑盒"结构,完全吻合面向对象系统所要求
的"强内聚性".但由于对接口的过多强调,COM组件一般不具备广泛提倡的"弱耦合性"的特点.不过,微软一向重
视接口的不变性,试图用接口的标准化推动服务的标准化,以接口为基础为软件复用建立实用的框架.其实
ActiveX技术规范中的相当大一部分都是通过定义标准的接口及其相互之间的逻辑关系来确定的.
二 自动化在考虑调用接口内成员函数的具体实现时,就会发现由于组件的特殊性,这种实现需要与通常完全不同的规范.
需要解决的问题有:源程序中如何标识一个组件(物理上,就是一段已经存在了的,具有一定功能的二进制代码)
,对于组件内特定函数的调用,编译器将如何做出处理,如何进行参数的检验及返回值的收集.
在旧的编程模式中,以上问题的解决均需要一个对组件进行充分定义的说明性文件.而且,该说明性文件的格式
必须完全符合所用的编程语言的语法.这就产生了以下一些矛盾.首先,为每一个发行组件均配置各种不同版本
的说明文件在实践中并不可行;第二,即使这样的头文件通过类型库自动转换得到,为各种编程语言提供这种转
换工具同样是不可行的;第三,组件中所用到的数据类型并不一定总能与目标编称语言一一对应;第四,这种笨拙
的实现方法,与构件的"即插即用"概念相去甚远,程序员难以接受.
针对以上问题,COM规范提出了自动化技术,较好的实现了以符号为导航的动态绑定.Idispatch是实现这一点的
关键接口.
Class Idispatch : public Iunknown
{
public:
virtual HRESULT GetTypeInfoCount(UINT * pctinfo) = 0;
virtual HRESULT GetTypeInfo(UINT iTinfo,LCID
lcid,ItypeInfo ** ppTInfo) = 0;
virtual HRESULT GetIDsOfNames(REFIID riid,LPOLESTR *
rgszNames,UINT cNames,LCID lcid,DISPID *rgDispId) = 0;
virtual HRESULT Invoke(DISPID dispIdMember,REFIID
riid,LCID lcid,WORD wFlags,DISPPARAMS *pDispParams,
VARIANT *pVarResult,EXCEINFO *pExcepInfo,
UINT *puArgErr) = 0;
};
在自动化对象中,每一个成员函数均对应一个分发ID(DISPID),服务导航器InVoke就是通过此分发ID建立与目标
函数的连接.可以用GetIDsofName实现符号(函数名)到ID的映射.因此,应用程序就有了一个统一的界面来使用
组件提供的各项服务.
对于数据类型的兼容性问题,微软提供了一个生硬的解决方法.定义一个尽可能"包罗万象"的大的数据结构
VARIANT
typedef struct tagVARIANT
{
VARTYPE vt; //类型标示
union
{ // 传值类型
short iVal;
long lVal;
byte bVal;
float fltVal;
……
// 引用类型
short *piVal;
……
IUnknown **ppunkVal;
IDispatch **ppdispVal;
VARIANT *pvarVal;
void *byref;
};
};
自动化对象与其调用者之间参数与返回值的传递,必须在VARIANT的范围之内进行.各种不同种类的VARIANT型参
数之间的转换,可以在InVoke中实现.为程序员提供更加智能化的编程界面(如VB中实现的那样).
COM为所有的组件对象规定了一个必须实现的基接口IUnknown,这不仅定义了最基本的公共服务,而且还使一个
指向IUnknown接口的指针客观上成为组件实例的对象标示.除对象生存期的管理外,这种对象标示还使COM组件
具有了自解释的特性:通过对Iunknown接口的成员函数QueryInterface的调用可以查询到对象实现的所有接口
.
不过上述自动化的各种优点是以其数据类型的限制和性能的部分损失为代价的.虽然编译器可以通过类型库进
行前绑定(参数类型的检查,符号到分发ID的转换),但是最耗时的InVoke却难以优化.为此COM规定了双接口机制
,为程序员提供了在动态绑定与静态联编之间选择的自由.同时,自动化技术的存在,也为脚本语言(VBScript,
JavaScript)提供了活力.
三 包容和聚合作为面向对象系统的重要特性,COM支持两种重用模型:包容(containment)和聚合(aggregation)它们的思路基
本一致,只是在实现方法上有所不同.
考虑包容的情况,假定已经有一个实现了接口IReuseInterface的COM对象A,由于新的需求,我们又要实现一个新
的COM对象B,它既要实现接口IReuseInterface,又要实现接口INewInterface,而且IreuseInterface所提供的服
务与对象A的接口基本一致,只要再实现新添加的功能就可完成对象B的开发.最直观的想法就是在对象B的内部
"包容"一个对象A的实例.所有对B的接口IreuseInterface成员函数的调用均在其内部简单地转发给对象A,对于
B的客户来说,更本无需知道A的存在.这种包容关系可用下图表示:
包容模型在实现时较为灵活,可以再将调用转发给内部对象的前后,进行一些本地化的处理,从而弥补A和B各自
IreuseInterface接口在功能上的微小差异.
依然考虑上面的例子,如果B的IReuseInterface接口在功能上与A实现的完全一致,不需要做任何改动.现在采用
聚合模型来实现B.对象B本身并不是实现接口IreuseInterface,它只实现接口InewInterface.当B客户请求
IreuseInterface 接口时,对象B把对象A的IreuseInterface接口指针传递给客户程序,因此客户直接与A进行了
交互(虽然它并不知道对象A的存在).聚合模型的实现如下图所示:
当然,聚合模型在对Iunknown指针的处理上要比包容模型复杂一些.需要进行对象双方的合作,并不是每对象都
能支持聚合的特性.但聚合体现了组件软件真正意义上的复用,而包容的重用性只是体现在客户服务器模型相对
性的基础上.实际也就是客户程序和组件程序的嵌套关系.这是包容和聚合的本质不同.
四 统一数据传输和消息传递在基于软件复用开发的应用系统中,各分立的组件对象要相互协作来共同完成某一特定任务逻辑.为支持各种可
能复杂的协作关系,仅有参数信息的传递是远远不够的,必须提供应用间数据交换的标准方案和事件,消息的通
知机制.
在这两方面,COM均提供了较为成熟的规范.即统一数据传输(UDT,Uniform Data Transfer)和MSMQ.
众所周知的OLE程序利用剪贴板传输连接对象信息,就是UDT的一种典型应用.统一数据传输建立在结构化存储的
基础上,包括两方面的内容:首先是数据格式的统一,其次是传输协议的建立.统一数据传输机制使用"数据对象
"作为信息实体,通过IDataObject接口暴露其内部信息.由于数据对象本身是一个COM对象,因此它不仅可以表达
一般的结构化信息,也可以表达一些非结构化信息.
传输协议任务就是将一个组件提供的数据对象即IdataObject接口指针传递给另一个组件对象.接收方得到
IdataObject接口指针后就可以利用其成员函数获取所需的数据.传输协议的具体实现与数据交换是完全分开的
,因此可以在最大限度与原系统保持兼容的情况下,方便的扩充协议.较常见的传输协议包括剪贴板,"拖-放"技
术等.另外,为适应一些实时性比较强的应用,COM还提供了"数据变化通知"机制,以扩充数据对象与客户程序之
间的双向通信.MicroSoft Message Queue(MSMQ)版本1是Windows NT操作系统的一个新特征.MSMQ提供可在不可
靠的网络上进行可靠的关键任务信息通信.与大多数现代应用程序用紧密耦合且同步通信相反,MSMQ基于一个消
息排队模型来提供一个异步,松散耦合,可靠的网络通信.出于功能复用及标准化的考虑,MSMQ 使用了面向消息
的中间件(MOM Message_Oriented Middleware).MOM提供了一个队列和消息的通信基础结构.应用程序向队列写
入消息,消息中间件将消息传递给被请求的队列,另一个应用程序(或同一应用程序的另一个组件)从目标队列读
取消息并进行处理.消息的内容完全由读写队列的应用程序控制.MSMQ支持大范围的队列类型.公共应用程序队
列对MSMQ客户是可见的,因为它们发布在一个命名服务器上,即MQIS(Message Queue Information Store,消息
队列信息存储).MSMQ客户能够查询MQIS来确定是否存在某一公共应用程序队列以及它已哪台计算机为主机.另
一方面,私有的应用程序队列不在MQIS中发布.因此,想向私有队列发送消息,就必须详细知道此队列的主机.
五 总结企业级应用系统领域中的构件复用,由于有了三层体系结构的支持而变得清晰,明确.在Windows DNA框架中应用
系统被分为了表现层,业务层和数据层.其中的业务层又可根据需要分为多个子层.表现层和数据层的重用已经
相当标准化了,如各种支持GUI开发的类库,ActiveX控件,OLEDB,ODBC等.构件复用的重点在业务处理层.笔者认
为通过对商业世界中各项服务的明确划分,对东服务接口的定义标准化,可以有效地实现体系结构和组件的复用
.
随着Windows 2000的发布,即将浮出水面的COM+作为COM,DCOM,MTS的某种集成,与操作系统的结合更加紧密.
COM+提供了对负载平衡,安全性,消息事件模型,MTS服务的更好支持.同时,COM+的部分特性将在下一代编译器
(Visual Studio 7.0)中有所体现,使得组件化程序设计更加自然,友好.随着COM+的发表,从现在到将来,COM将
成为一条基于Windows的面向组件对象的软件开发之路.
参考文献① Stephen R.Schach,Software Engineering with JAVA,McGraw-HIll
② Component Object Model specification,MicroSoft Corp.,1993
③ 潘爱民,COM原理与应用 ,清华大却出版社
④ 袁小玲,组件技术--企业管理信息系统开发的新曙光,计算机工程与应用
1999,9
⑤ 徐正权,骆婷,异质可复用软构件属性模型,计算机应用研究,2000,3
⑥ 来欣,邓铁清,分布对象与WWW技术的集成研究与实践,计算机工程与应用,
1999,7
⑦ Rogerson Dale,Inside COM,Microsoft Press,1997.ISBN 1572313498.
⑧ Don Benage Azam Mirza,Building Enterprise Solutions with Visual Studio
6.0,Macmillan Computer Publishing U.S.A
本文地址:http://com.8s8s.com/it/it2972.htm