荔园在线

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

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


发信人: Deny (中秋快乐!!!!!!!!!!!!!!!), 信区: Program
标  题: 组件、COM和ATL(2)
发信站: 荔园晨风BBS站 (Wed Sep 26 22:07:05 2001), 转信

COM 基本概念


     回顾与前瞻

     在第一部分,医生探讨了为什么 C++ 这样的语言不能解决用二进制组件构造
软件的问题 。这个问题的关键是,C++ 原本不打算解决这个问题;相反,它本来
的用途是在单个可执 行程序中,较为容易地重用源代码。C++ 很好地做到了这一
点。但是我们希望能够混合和 匹配不同供应商的组件,而在组件每次改变时不必
重新连编系统的一部分(或全部)。我 们已经讨论了很多理由,说明为什么
C++ 模型对此无能为力。

     那又怎样?您必须抛弃 C++ 吗?不,但是您必须以与以前所熟悉的方法稍有
不同的方法 使用它。这就是我们下一步将要讨论的,即如何从 C++ 中使用 COM。


     这是否意味着,如果您不是一位 C++ 程序员,就不要阅读下去了?不,因为
不管您使用 的是哪种 COM 兼容的语言(Visual Basic、Visual J++、Delphi 等
等),这些语言都会 做我们讨论的事情(或许不只是机房中的事情?)。所以,
如果您继续读下去,您会对这 些 ActiveX 控件和 COM 组件的工作机制获得有益
的了解。

     好吧,那么什么是 COM 呢? Components Object Model (COM) 是软件组件
互相通讯的一种方式。它是一种二进制和网 络标准,允许任意两个组件互相通讯
,而不管它们是在什么计算机上运行(只要计算机是 相连的),不管各计算机运
行的是什么操作系统(只要该操作系统支持 COM),也不管该 组件是用什么语言
编写的。COM 还提供了位置透明性:当您编写组件时,其他组件是进程 内 DLL、
本地 EXE 还是位于其他计算机上的组件,对您而言都无所谓。(当然会有性能 区
别,但是,即使改变了其他组件的位置,您也不必重新编写什么,这是关键所在。


     对象

     COM 是基于对象的——但是这种对象概念与您熟悉的 C++ 或 Visual
Basic 中的对象不 太一样。(顺便说一下,“对象”和“组件”几乎是同一个东
西。GUI 医生在谈论应用程 序的结构时愿意说成“组件”,而在谈论实现时愿意
说成是“对象”)

     首先,COM 对象被很好地封装起来。您无法访问对象的内部实现细节;您无
法知道对象使 用了什么数据结构。实际上,对象的封装是如此的严密,以致于
COM 对象通常被描绘为 盒子。图 1 描绘了一个完全封装的对象。请注意,实现细
节是如何向您隐藏的。

     图 1 一个完全封装的非 COM 对象。

     封装是不错,但是通讯又怎么样呢?在这种状况下,我们无法与这个盒子中
的组件通讯。 很明显,这个方法不行。

     接口:与对象的通讯 这时就需要接口了。访问 COM 对象的唯一途径是通过
接口。我们可以象图 2 所示,在对 象上描绘一个名为 IFoo 的接口。

     图 2 带有接口的对象—也不是 COM。

     在对象旁边支出来的象棒棒糖的东西就是接口——这里是 IFoo 接口。该接
口是与该对象 通讯的唯一途径。医生认为,将接口看成一个插件连接器,比看成
棒棒糖会更有用。正是 通过它您才能为对象添加功能,可将它看成是录相机或电
视机的天线输入。

     接口有两个含义。首先,它是一组可以调用的函数,由此您可以让该对象做
某些事情。在 C++ 中,接口是用抽象基类代表的。例如,IFoo 的定义可能是:
class IFoo { virtual void Func1(void) = 0; virtual void Func2(int
nCount) = 0; }; };

     我们现在暂时忽略返回值和继承性,但是要注意在接口中可以有多个函数,
而且这些函数 都是纯虚函数:它们没有在 Ifoo 类中实现。我们在这里并非要定
义行为,而只是要定义 在接口中有什么函数(当然,真正的对象必须要有实现部
分,有关的详细内容稍后讲解。 )

     其次,也是更重要的,接口是组件及其客户程序之间的协议。也就是说,接
口不但定义了 可用什么函数,也定义了当调用这些函数时对象要做什么。这种语
义定义不是以对象的特 定实现来表达的,所以无法用 C++ 代码来表达该定义(虽
然我们可以用 C++ 提供一种特 定实现)。相反,该定义是以对象的行为来定义的
,所以对该对象和(或)也实现该接口 (协议)的新对象进行修订是可能的。实
际上,对象可以按自己选择的任何方式实现该协 议(只要该对象遵守该协议)。
也就是说,该协议必须(医生恶狠狠地说)书写在源代码 之外的文档中!因为客
户程序无法(也不必)得到源代码,所以这一点尤其重要。

     这种特殊协议的观念对 COM 和组件软件都是很至关重要的。没有“坚不可摧
”的协议, 就不可能交换组件。

     接口协议象钻石一样永久 在 COM 中,一旦您通过发布一个组件来“公布”
了一个接口协议,该协议就不能变更了 ——不能以任何方式变更。您不能添加、
不能删除、不能修改。为什么?因为其他组件依 赖于该协议。如果更改了该协议
,您将会破坏那些软件。只要遵守该协议,您可以改进内 部的实现。 部的实现。


     如果您忘记了什么怎么办?如果需求发生了变化怎么办?难道整个世界都要
永远停滞不前 吗?

     答案很简单:编写一个新协议。标准的 OLE 接口列表有很多这样的协议:
IClassFactory 和 IClassFactory2,IViewObject 和 IViewObject2,等等。当然
,您也 可以提供一个 IFoo2。(我敢肯定,您已注意到接口名称按约定是以大写
字母 I 开头的 。)

     如果我编写了一个新协议,那么那些只知道旧协议的软件如何继续使用我的
组件呢?这是 否会把新旧组件搞得一团糟?

     COM 对象可以支持多接口—它们可以实现多个协议 答案也是不,原因很简单
:在 COM 中,一个对象可以支持多个接口。实际上,所有有用 的 COM 对象都至
少支持两个接口。(至少包含标准 IUnknown 接口(有关的详细内容稍 后讲解)
和一个实现所需功能的接口。)Visual ActiveX 控件都支持十几个接口,大多 是
标准接口。要使组件支持一个接口,必须实现该接口中的每个方法程序,所以要进
行大 量的工作。这就是 Active Template Library (ATL) 等工具流行的原因:它
们提供了所 有接口的实现。

     所以为了支持新的 IFoo2 功能,我们将 IFoo2 也添加到该对象。

     图 3 2.0 版,它支持 IFoo 和 IFoo2——但仍不是一个 COM 对象。

     如果您仍想到插件,可以将 IFoo 想象为电视机的天线输入,将 IFoo2 想象
为复合视频 输入。注意,您不能将天线电缆插入到复合视频输入的插孔,反过来
也不行。也就是说, 每个接口在逻辑上都是唯一的。

     另一方面,这些接口也有共同的地方。为了添加一个与旧接口几乎一样的新
接口,是否需 要重新编写全部的实现代码?不,因为 COM 支持接口的继承。只要
我们不更改 IFoo 中 已有的函数,我们可以如下定义 IFoo2: class IFoo2 :
public IFoo { // 继承了 Func1, Func2 virtual void Func2Ex(double nCount)
 = 0; }; 接口回顾 现在,让我们回顾一下我们阅读过的内容。首先,COM 是一个
有关软件对象交互的二进制 标准。由于是一个二进制标准,对象不会也不能知道
所使用对象的实现细节。所以,对象 就是黑盒子。

     我们只能通过对象提供的接口来操作这些黑盒子对象。最后,一个对象可以
提供任意多的 接口。

     很简单,是不是?

     其实,我们忽略了很多细节。如何创建这些对象?如何访问接口?如何调用
接口的方法程 序?不管怎么说,这些对象的实现代码在哪里?这个恼人的对象何
时最后被破坏?

     这些问题很好,但遗憾的是 GUI 医生的手术要迟到了,所以我们将推迟到第
三部分回答 。不过医生现在可以处理一个问题:如何调用接口的方法程序?

     调用接口的方法程序

     您可能认为会很复杂,但其实很简单:COM 方法程序调用就是 C++ 虚函数的
调用。我们 将以某种方法(有关的详细内容在第三部分讲解)获得实现接口的对
象的指针,然后我们 就调用该接口的方法程序。

     首先,假设我们有一个名为 CFoo 的 C++ 类,它实现了 IFoo 接口。注意,
我们从 IFoo 继承,以保证我们按正确的顺序实现了正确的接口。

     class CFoo : public IFoo { void Func1() { /* ... */ } void
Func2(int nCount) { /* ... */ } }; };

     我们使用的指针称为接口指针。假设我们可以得到一个接口指针,我们的代
码将如下所示 : #include Func1(); pFoo -> Func2(5); }; 就这么简单。

     但是,在这些代码的背后到底发生了什么呢?正如您以后要看到的,COM 二
进制标准也应 用于方法程序的调用——所以 COM 定义了调用函数时将发生什么。
具体地说,所发生的 事情与虚函数调用时的情形相同:

     由 pFoo 获得对象的虚函数表指针。

     由虚函数表指针和索引获得要调用函数的地址。

     调用函数。 有关这几个步骤的情况,请参阅图 4:

     图 4 通过接口指针进行的 C++ 虚函数调用

     记住,在 C++ 中,每当声明虚函数时,就会生成一个虚函数表,它指向这些
函数,并且 ,对这些函数的调用都是通过虚函数表和索引进行的。

     “哈哈!”您说,“我知道了。实际上,COM 是离不开 C++ 的!它根本算不
上一个二进 制标准!”

     GUI 医生回答说:“不对。”毕竟,您可以在任何支持函数指针数组的语言
中实现这种调 用。例如,在 C 语言中就很容易,通过指针 p 对 Func2 的调用可
能会象这样: (*((*p)+1))(p, 5); // 将 5 传递到数组中第二个函数 注意,我
们必须将 p 作为第一个参数来传递——这模拟了 C++ 的 this 指针。(*p) 是 第
一次寻址(步骤1),*((*p) + 1) 是用索引进入虚函数表的正确入口(步骤 2)
,然 后我们用 p 和 5 作为参数调用了函数(步骤 3)。很容易,但是很不雅观
——GUI 医生 步骤 3)。很容易,但是很不雅观——GUI 医生 做这个示范只是为
了表明 C 语言是可以做到的(并且使您欣赏 C++)。在 x86 汇编语言 中,调用
可能会象这样: MOV EAX, [pFoo] ; 步骤 1 MOV EAX, [EAX + 4] ; 步骤 2,用
索引获得第二个指针 CALL [EAX] ; 步骤 3 GUI 医生知道第二和第三个指令可以
合并为 CALL [EAX + 4],如果您不想在 EAX 中保留 函数的地址。

     为什么医生演示了所有这些细节?是的,如果能用汇编语言或 C 语言做到
,就能用任何 语言做到!其他语言(Visual Basic、Visual J++、Delphi)将对
这些调用的支持置入了 它们的运行时模块或虚拟机中——通常是使用与上面相似
的汇编语言代码或 C 语言代码 。

     要点是任何 COM 方法程序的调用都必须使用上面所示的数据结构,而不管其
原始语言是 什么,也不管 COM 对象位于何处。在将来的栏目中我们将讨论 COM
是如何做到位置透明 的。

     回顾与前瞻

     好了,我们已经讨论了接口、对象,以及如何调用接口的方法程序。

     对象是 COM 的基本单元——它是 COM 所创建的东西。对象要实现一些接口
。接口是一组

     方法程序和规定这些方法程序做什么的协议。调用接口的方法程序的方式与
调用 C++ 虚 函数的方式相同。

     在第三部分中,我们将讨论如何创建这些对象,如何获得接口指针,以及对
象是如何被破 坏的。




--

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


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

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