* 本文选自:Visual C++/MFC开发指南
我们的梦想用COM重造我们的软件
-- 向佳
关键词:
COM OLE ACTIVE X CONTAINER AUTOMATION CONTROLE Iunknown IDispatch SELF_CONTAINED
CLASS FACTORY DCOM COM+ MTS MSMQ
概论
一个应用程序通常是由一个二进制文件组成。当编译器生成此文件后,在对下一版本重新编
译并发行新生成的版本值之前,应用程序一般不会发生任何变化。操作系统、硬件及客户需
求的改变都必须等到整个应用程序被重新编译后才能够得以认可。整个软件业就是这样随着
已发行软件的日益“老化”而奔向未来的。目前,这种状况已经发生了变化。开发人员找到
的一种方案是将单个的应用程序分割成多个独立的部分。即组件。也就是将软部件看成对象。
这种做法的好处是可以随技术的不断发展而用新的组件取代已有的组件。此时的应用程序将
不再是以前那样是一个在发行前就已注定过时的实体,而是可以随着新组件不断取代就组件
而趋于完善。
这一点对我们来说并没有太大的诱惑力。谁都知道我们不可能天天给用户发行新的版本。
但有一点你一定喜欢,当你根据需要更改了DLL中的一些实现函数,生成了DLL后,不需要重新编
译整个程序,就能使用新的性能。是不是有些不可思议。
使用COM模型给软件带来的影响将是巨大的。是软件工业的革命。对于一个软件的编程者,谁
不想在编程中能够使用像MFC那样方便的使用类,而不是面对枯燥而又嵌得很深的函数,使人
眼花缭乱全局变量。在改动程序前花费大量的时间去理解源程序。COM提供了一种二进制标准
的、面向对象、可扩充、可重用的通讯协议。如果你继续读下去,就会发现构建基于COM的测
量软件给你带来的好处将是巨大的。花点时间、金钱将得到事半功倍的回报。
每个程序员恐怕都有这样的经历:当修改一个大型程序时,由于程序本身结构的复杂性,使
某些功能的实现难度非常大。怎样才能构造出一个逻辑清晰、结构完整、功能相对独立、便
于扩充的逻辑模型?MICROSOFT提供了COM,APPLE有它的OPENDOC,IBM有SOM。现在的软件业
正在走着二十年前硬件所走过的道路。二十年前,人们制造了集成电路块,用一块块能完成
特定工作的小芯片,构造新的更大的芯片,去完成新的功能。这就是软件业所面临的问题。
当今计算机界最热门的两项新技术是COM和DCOM。COM似乎成为了解决所有软件开发弊端的良
方。当然这不意味着使用COM后你将一劳永逸。软件业是没有”银弹”神话的。只不过COM对
我们今天面临的新问题有了更好的解决方案。例如分布式程序的开发,事务处理的安全,跨
平台的可重用性------
使用组件的优点:
使用组件的优点是应用程序可随时间的流逝而发展进化。除此之外,使用组件可是对已有应
用程序的升级更方便和灵活。组件构架从本质上讲是可定制的,用户可以用更能满足他们需
要的组件来将某个组件替换掉。象类一样,COM是一个软件编程的构造模型,而不是一个数学
模型。
一组件技术的演变
观察组件技术的演变是理解COM和DCOM的开始,虽然面向对象技术已经经历了一个完整的自身
演变的过程,但组件技术作为面向对象技术的一个派生物,却有着自己的演变过程。组件技
术的演变的主要根基是WINDOWS OS。当然,其他的一些技术也起了很大的作用。
1.在WINDOWS 3.0中出现的定制控制(CUSTOM CONTROL)。一个定制控制就是一个可以输出
定义好的函数集合的动态链接库。但与一般的DLL 不同的是,一个定制控制能进行特性操作
,并能处理由用户或程序输入所引起的触发事件。
2.1991年MICROSOFT推出的VBX,使小型的可重用组件的重要作用体现得淋漓尽致。但遗憾的
是,VBX软件包内约束太多,16位的结构,且没有一个开放的接口,限制了VBX的应用。
3.也就是同一年MICROSOF开发了OLE1.0规范。(OBJECT LINKING AND EMBEDDING )。OLE1.0
努力朝着文件中心的方向发展,而不是传统的以应用为中心的发展方向。“复合文档”是
这个思想的产物,是在单个文件中以文本、图形、视频、图形和声音等多种格式存储数据的
方法。office套件中的word我们是再熟悉不过了。但ole1.0由于自身的复杂性,及实现的技
术方面的不成熟。在市场上遭到了冷遇。
4.Microsoft 并没被ole1.0的困难所吓倒,而是继续致力于ole的改进与提高。1993年发布
了ole2.0规范,推出了一个完全基于对象的服务结构,易于扩展、定制和增强。通过这种结
构可得到的服务是:
Com
剪贴板(clipboard)
拖放(drag and drop)
嵌入(embedding)
定点激活(in-place activation)
链接(linking)
标记(永久命名)(moniker(persistent naming))
ole自动化(ole automation)
ole控制(ole control)
ole文档(ole document)
结构化存储(structrued storage)
同一数据传输(uniform data transfer,UDT)
5.ACTIVE X
1996年3月INTERNET专业开发人员研讨会(Internet PDC)上提出ACTIVE X一词,当初仅做为一
种号召而非具体的应用程序开发技术。称为:Active the Internet随着INTERNET的飞速发展
,Active X成为定义从Web页面到OLE控件的所有内容的核心。一方面,它代表了将用户联到
MICROSOFT,INTERNET和业界的新技术的小型快速得重用组件。另一方面:ACTIVE X代表着
INTERNET与应用程序集成的策略.
有一种说法:“ACTIVE X是OLE 与INTERNET碰撞的产物,INTERNET使它变成了今天的样子".
今天的ACTIVE X包括:
ACTIVE X 文档
ACTIVE X 控件
COM
INTERNET标记
Active X超级链接
Active x会议
Active X服务器扩展
Active X脚本
代码签名
HTML扩展
活动电影
二初探COM
组件对象模型(简称COM)是windows对象的二进制标准。为WINDOWS提供了统一的,面向对象的
,可扩充的通讯协议。这意味着描述一个对象的可执行代码(.dll 或.exe 文件的代码)可以
被其它对象执行。即使两个对象使用不同语言来编写的,他们可以用COM标准来进行通信。
个人认为:COM是MICROSOFT发展的产物,日前MICROSOFT所提出的大部分概念都是基于COM的。
COM随着MICROSOFT的壮大而不断完善,MICROSOFT也为COM引以为豪。如今MICROSOFT 产品已发
展成为一个软件大家族。OFFICE系列、VISUAL BASIC、VISUAL C++、VISUAL J++都已成为
WINDOWS程序设计的标准,为了在不同应用程序之间进行数据交换,达到资源共享的目的,迫切
需要一种接口规则与协议。建立一个软件模块同另一个软件模块的连接,然后将其描述出来
,当这种连接建立起来后,两个模块之间就可以通过成为接口的机制来进行通讯。COM立足于
二进制这一层,是语言无关的接口规范。
曾经使用的数据交换技术存在的问题:
*WINDOWS API有太大的编程“接口面”(超过350个单独函数)
*VBA 不能在WIN32环境中使用
*DDE 是一个关于应用程序,主题和项目的复杂系统。
*DLL 是完全面向应用程序的
COM的特征在于:
*为WIN32客户EXE载入和调用WIN32 DLL提供了一种标准的与语言无关的方法。
*为一个EXE控件同一机器的另一个EXE提供了通用的方法。(可代替DDE)
*用ACTIVE X代替了VBX。
*为应用程序与操作系统的交互操作提供了一种新的方法。
*为适应新的协议提供了可扩充性,如MICROSOFT的OLE DB数据接口。
*分步式COM(DCOM)允许一个EXE与不同及其上的另一个EXE进行通讯。
*COM+则综合了COM、DCOM、MTS和MSMQ。
1.COM的基本设计思路:
通常我们描述一个对象是将其划分若干的方法和属性。通过继承这一机制来获取对方法使用
和属性访问的权利,以达到数据处理的目的。而COM模块不再允许客户直接调用对象的方法(
包括属性及其构造函数,析构函数),COM只提供一个标准的全局函数来访问对象。从而最大
限度的将客户与服务分离。在客户与服务器之间通过接口来连结,使用虚拟函数表(vtbl)
的内存结构,实现了一组指向虚拟函数实现的指针。
2.COM如何进行数据交换?
通过接口。
这个接口定义了函数调用方法,标准的基于结构的数据传输技术,以及一些标准的函数调用。
接口是虚函数的集合,实质上是函数名的集合。它是没有数据的C++类。是纯的虚函数,对
象从这个类继承,并提供函数代码,其他函数通过调用这些函数来获得代码,所有组件对象必须
有一个称为IUnknow 的接口。
IUnknown接口的目的:找到其他的接口。它有一个QueryInterface()函数,该函数使用一个接
口ID,返回一个指向该对象接口的指针。所有其它接口都从IUnknown中继承,因此,所用接口中
都有Query Interface()。
interface IUnknown{
public:
BEGIN_INTERFACE
virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid,
void_RPC_FAR *_RPC_FAR *ppvObject)=0;
virtual ULONG STDMETHODCALLTYPE AddRef(void)=0;
virtual ULONG STDMETHODCALLTYPE Release(void)=0;
END_INTERFACE};
其中AddRef() 和Release()用来跟中那些正在使用接口的应用程序.
所有三个函数被所有接口继承.
接口的实现:
可以通过多重继承。但多重继承遇到的问题是:当多个接口方法符号名冲突的问题。即多个
接口都含有同名的方法,而不同接口的方法实现各不相同。编译器仅允许程序员提供一个方
法的实现。显然通过多重继承来实现接口是有问题的。
而在MFC中,通过类嵌套来实现。MFC库有一组宏可以自动实现这个过程。
INTERFACE_PART宏产生嵌套类,并为特定的接口函数产生原型,并加上QueryInterface,AddRef
和Release的原型。
DECLARE_INTERFACE_MAP宏产生一张表的声明,该表包含了所有类接口的ID。CCmdTarget::
ExternalQueryInterface()函数使用该表来获取接口指针。
3.另一个有用的接口--类厂(CLASS FACTORY)
CLASS FACTORY是一种类,是被用来支持名为IClassFactory的特殊的COM接口。IClassFactory
是从IUnknown派生的,它的主要成员函数是:CreateInstance(),它产生的原因是在COM
中我们无法直接调用构造函数,于是只好让组件来决定如何构造对象,为此组件提供了类厂
,从而将具体的创建过程封装起来。利用类厂来创建多个对象。MFC库采取动态创建技术来解
决这一问题,一个名为COleObjectFactory的类可以在运行时创建任何类。采用DYNCREATE宏
设置标准的动态创建机制。OLECREATE宏声明和定义了一个COleObjectFactory类的全局对象
,使用指定的唯一CLSID。在DLL组件中,倒出函数DllGetClassObject根据OLECREATE宏设置
的全局变量,找到特定的类厂对象,并返回指向该对象的指针。当DLL被初始化时,RegesterAll
被调用,在EXE组件中,初始化代码调用静态的COleObjectFactory::RegisterAll函数
,该函数查找所有的厂对象,并调用CoRegisterClassObject注册每一个对象。
接口集、接口、接口函数、属性之间的关系。
个人认为:接口集、接口、接口函数(方法)、属性在逻辑上存在着一定的对应的关系。接
口集包含多个接口。一个接口包含着一类接口函数,每一接口函数对应着一类属性。接口函
数才是用户所关心的,用户只有通过接口使用接口函数才能改变属性。属性与接口函数都是
对组件对象的抽象。
4.AUTOMATION & ACTIVE X
AUTOMATION & ACTIVE X是ACTIVE X组件的重要部分。ACTIVE X控件也是小型的AUTOMATION服
务器。AUTOMATION让应用程序告诉它做什么。它揭示属性和方法。如果我们揭示了自己应用
程序的所有方法和属性,则使用AUTOMATION应用程序的任何编程语言都能成为自己应用程序
的书写语言。知道有关与AUTOMATION交互的最主要的事情总有一个程序在控制着。控制着的
应用程序称AUTOMATION控制器,揭示方法和属性的应用程序称为AUTOMATION服务器。
AUTOMATION通讯实际上已经明确定义了主从关系。主是自动化客户程序,从是自动化组件(
控件)。通过构造一个组件对象,或者与已经运行的程序中的现存对象建立连接,客户程序
可以对相互作用过程进行初始化,然后客户程序可以调用组件程序中的接口函数,并且将那
些已完成的接口释放掉。
这意味着WORD ,EXCEL,ACCESS及VB这些支持AUTOMATION的应用程序,可以被连接到与AUTO
MATION兼容的组件中。我们用VC++就可以调用WINDOWS下的任何控件(即使它包含在WORD 或
其他程序中),也可以用VC++自己编写组件,而用VBA宏来对它进行调用。
(1)IDispatch是自动化的核心。它将所有的内部模块通讯都汇集在IDispatch::Invoke()
上,像IUnknown和IClassFactory一样,它完全被COM调度(marshaling)所支持。从组件一
方面来看,我们需要有一个具有IDispatch接口(包含必要的类厂)的COM类;从用户方面来
看,我们使用标准的COM技术来获取指针。
interface IDispatch:public IUnknown
{
public:
virtual HRESULT STDMETHODCALLTYPE GetTypeInfo(
UINT iTInfo,
LCID lcid,
ITypeInfo _RPC_FAR *_RPC_FAR *ppTInfo)=0;
virtual HRESULT STDMETHODCALLTYPE GetIDsofNames(
REFIID riid,
LPOLESTR _RPC_FAR *rgszNames,
LCID lcid,
DISPID _RPC_FAR *rgDispId)=0;
virtual HRESULT STDMETHODCALLTYPE Invoke(
DISPID dispIdMember,
REFIID riid,
LCID lcid,
WORD wFlag,
DISPPARAMS _RPC_FAR *pDispParams,
VARIANT _RPC_FAR *pVarResult,
EXCEPINFO _RPC_FAR *pExcepInfo,
UINT _RPC_FAR *puaArgErr)=0;
};
说明:Invoke()函数是其中最主要的函数,用于调用AUTOMATION服务器的函数和属性。
(2)IDispatch 的实现:
组件程序可以有几种方法来实现IDispatch接口。最通常的做法是把大部分工作交给WINDOWS
的COM DLL,这可以通过调用COM函数CreateStdDispatch来实现,或把Invoke调用委托给
ITypeInfo接口,由它来处理组件的类型库(typelibrary)。一个类型库是一张表,该表允许
客户查找组件中的对象方法和属性的符号化名字。MFC支持类型库,但在IDispatch的实现中
并没有用到类型库。IDispatch的实现是靠分发映射(DISPATCH MAP)驱动的。MFC程序根本
不调用CreateStdDispatch,它也不用类型库来实现IDispatch::GetIDsOfName。
(3)MFC 的分发映射是如何与IDispatch和Invoke成员函数相关连?
分发映射宏在内部产生了一个数据表,MFC中的Invoke函数可以读到该表。控制程序可以得
到类的IDispatch指针。并且用一个指针数组作为参数调用Invoke。MFC的Invoke函数的执行
体会利用类分发映射对所提供的指针进行解码,可以调用某个成员函数。
(4)关于VARIANT类型:
自动化客户和组件都用到了VARIANT类型。它是一个通用数据类型,是64位数据。IDspatch:
:Invoke利用它来传递参数和返回值。
BEGIN_DISPATCH_MAP(CWwddCtrl, COleControl)
//{{AFX_DISPATCH_MAP(CWwddCtrl)
DISP_PROPERTY_NOTIFY(CWwddCtrl, "Wdlowline", m_wdlowline, OnWdlowlineChanged, VT_R4)
DISP_PROPERTY_NOTIFY(CWwddCtrl, "Wdcaption", m_wdcaption, OnWdcaptionChanged, VT_BSTR)
DISP_PROPERTY_NOTIFY(CWwddCtrl, "Wdxmean", m_wdxmean, OnWdxmeanChanged, VT_BSTR)
DISP_PROPERTY_NOTIFY(CWwddCtrl, "Wdymean", m_wdymean, OnWdymeanChanged, VT_BSTR)
DISP_FUNCTION(CWwddCtrl, "setwdobject", setwdobject, VT_EMPTY, VTS_I4)
DISP_FUNCTION(CWwddCtrl, "setwdproper", setwdproper, VT_EMPTY, VTS_I4)
DISP_FUNCTION(CWwddCtrl, "deletelist", deletelist, VT_EMPTY, VTS_NONE)
DISP_FUNCTION(CWwddCtrl, "addwdvalue", addwdvalue, VT_EMPTY, VTS_R4)
DISP_FUNCTION(CWwddCtrl, "setambie", setambie, VT_EMPTY, VTS_R4 VTS_R4 VTS_R4)
END_DISPATCH_MAP()
2.Active X控件
一个典型的空间包括设计时和运行时的用户界面,唯一的IDispatch接口定义控件的方法和属
性,唯一的IConnectionPoint接口用于控件可引发的事件。除此之外,一个控件还可以包含对
其整个生命周期的一执性支持,以及对剪贴,拖放等用户界面特性的支持。从结构上看,一
个控件有大量必须支持的COM接口,以利用这些特性。Active X控件永远是放在其所放置的容
器内运行的,空间的扩展名为.OCX,但从运行模块的角度看,它不过是一个标准的WINDOWS
DLL文件。
属性和事件是容器内应用程序与控件通讯的方式,容器和控件之间的通信使用事件。对于
ActiveX控件,事件是在容器端时显现的IDispatch接口。事件的底层机制称连接点。一个连接
点就是与容器通讯所需接口的类型描述,连接点不只限于IDispatch接口,它也可用任何COM
实现,控件只不过第一个利用他们。
ACTIVEX 是一个独立的对象,可以激起和相应事件,处理消息,具有唯一的属性,并有多线程能
力,并支持其余容器之间的双向通信和消息传递.ACTIVE X控件最奇妙的地方在于它的可编程
性和克重复使用性.它对外部环境是开放的,能被各种编程合肥编程环境使用.控件对外有三个
属性集.(属性,对象,方法)
* 本文选自:Visual C++/MFC开发指南