第一部分:您打算解释 COM 吗?以前不是已经有人解释过了吗?

本文探讨了组件对象模型(COM)在解决二进制代码重用问题中的作用,对比了多种编程语言如C++、Java在组件开发中的局限性,并介绍了COM如何克服这些挑战。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

第一部分:您打算解释 COM 吗?以前不是已经有人解释过了吗?
医生听到您说:“您要干什么?解释 COM?已经有人写了这方面的书!”

确实有书了,而且 GUI 医生还可以推荐很多好书。他很喜欢 Dale Rogerson 的 Inside COM (Microsoft Press, 1997) 和 Don Box 的 Essential COM (Addison-Wesley, 1998)。Adam Denning 的 ActiveX Controls Inside Out (Microsoft Press, 1996),Sing Li 和 Panos Economopoulos 的 Professional Visual C++ 5 ActiveX/COM Control Programming (WROX Press, 1997) 也是好书。当然,您也应该看 Kraig Brockschmidt 的 Inside OLE, 2nd Edition (Microsoft Press, 1995),这是 OLE 参考的最好印刷品。(当然,MSDN 也是最好的参考,不只是医生这样认为。)另外,如果您想要看客观地解释组件软件的书,请参考 Clemens Szyperski 的新书 Component Software: Beyond Object-Oriented Programming (Addison-Wesley, 1998)。

“那又怎么样,”您问到,“GUI 医生还能比这些书说得更好吗?”

“根本不会。”医生这样回答。这些作者(和其他作者)再加上文档确实已经全说到了。如果您有时间看这些书,就是这样。

“那么您要做的就是,”您叫道,“为我们轻描淡写地介绍一番 COM 和 ATL,而且每周只有一小段!”

GUI 医生回敬道:“您很聪明。这样就比我预料的要容易多了。”

组件?我是在编写一个立体声系统吗?
如果您很熟悉 Visual Basic,您就会很熟悉使用组件编写程序:您既使用 visual ActiveX 控件(例如微调按钮),也使用非可视的 ActiveX 组件(例如数据库访问对象)。很难找到一个没有频繁使用预制的可重用组件的重要 Visual Basic 程序。但是,虽然您重用了大量的组件,大多数人并没有为自己编写过完整的可重用组件。

如果您使用 C++ 进行编程,那么您对重用会有不同的感受。人们说 C++ 和面向对象编程可以很容易地实现对象重用,但是您的经验怎么样?您编写过可重用对象库吗?无疑少数人确实编写过,但是大部分人并非如此。并且,就算有了这样的一个库,我们能很好地利用它吗?并不只是由于缺乏规则从而使我们难以重用代码;事实是,代码很难重用(代码好象从不按我们需要的方式执行),而编写可重用代码则更难(很难既足够通用又足够实用)。

结果是,C++ 并没有使创建可重用二进制组件变得容易,而是使重用源代码变得相对容易。要知道,大多数主要的 C++ 库是以源代码形式发布的,而不是编译后的形式。为了正确地继承一个对象,很有必要查看这些源代码,并且依赖于原库的实现细节来进行重用是很容易的(也是有必要的)。更糟的是,修改源代码并自己连编原库是很诱人的(或是必要的)。(到底有多少自连编的 MFC?谁也不知道 . . . .)

那么让我们重用二进制对象,而不是源代码
那么又怎能重用二进制对象呢?Windows 程序员首先想到的答案很简单:使用动态链接库 (DLL)。使用 DLL 确实管用,毕竟 Windows 本身就主要是一组 DLL。但是,还有一些问题。

首先,DLL 不是独立于编程语言的。即使是使用 C 语言编写的 DLL,也很容易更改调用约定(按什么顺序将什么参数入栈),从而使该 DLL 只能用在 C 程序中。就算 Windows 使用的调用约定已作为 Windows 系统的标准规定得很完备,但是,医生仍遇到过由于调用约定不匹配而导致的 DLL 失败。

为 DLL 编写一个 C 语言的接口有一些重要限制。首先,这限制了您进行面向对象编程,因为,C++ 的面向对象特性需要对函数名进行修饰,名称修饰没有统一的标准。有些情况下,甚至同一编译器的不同版本对名称的修饰也会不同。第二,实现多态性将很困难。通过创建包装类,您可以解决这些问题,但是这样做是痛苦的。GUI 医生没有痛苦过。(起码没有太痛苦过。)

即使您解决了名称修饰问题,并进而成功地链接到了 DLL,当更新对象时也会出现其他问题。

首先,如果当您更新对象时要向其中添加任何虚函数,那么您就会像浑身插满软管的病人一样动弹不得。您可能会认为在对象的末尾添加新函数不会出问题,但是实际上并非如此:这样会将所有从您的对象派生的对象的虚函数表入口平移。并且,因为调用虚函数需要使用虚函数表中的固定偏移,以便调用正确的函数,所以您不能对虚函数表进行任何更改——至少不重新编译每个有关的程序(这些程序使用您的对象或任何从您的对象派生的对象),就不能进行更改。很明显,每次更新您的对象时都重新编译全世界,不是个好主意。

其次,如果您在客户程序中使用 new 来分配对象,那么您要更改该对象的大小(即添加任何数据),就必须重新编译全世界。

最后(也是最重要的),更新 DLL 简直就是一场恶梦,因为您处于两难境地,两种选择都很令人倒胃口:要么通过覆盖 DLL 来“就地”更新该 DLL,要么重新命名一个新的版本。就地更新 DLL 很糟糕:即使您保持接口的统一,DLL 的某些用户程序也会被破坏,这样的几率很高。GUI 医生就不必一一告诉您业界(包括 Microsoft 在内)因此问题而遇到的所有麻烦了。

另一种方法,即使用一个新的 DLL 名称,至少能让原来正常运转的系统继续正常运转。但是,代价是需要占用硬盘空间(也许当普通的硬盘有 3GB 左右大小时,这不是个大问题),而第二个代价是:增加了内存的使用。如果用户使用了两种版本的 DLL,那么在用户的工作集内就会存在两个代码极其相似的复本。例如,通常,当您检查用户内存的使用情况时,就会发现两三个版本的 Visual Basic 运行时模块或 Microsoft Foundation Class (MFC) DLL。既然几乎所有的 Windows 系统通常都使用比物理内存更多的虚拟内存,增加工作集的大小就意味着严重的性能问题,表现在增加了交换到硬盘上的虚拟内存的大小。(这就为 Brook 定律提供了反例:向一个慢的系统添加更多的内存反而会使它更快。)

在理想情况下,您希望能让用户(或应用程序)来选择使用哪个版本。这对于静态链接的 DLL 是极其困难的,但是对动态加载的 DLL 就很容易了。

公平地讲,应该指出,C++ 从来也没打算解决这类问题。C++ 的原本用途是在只有一个文件的程序中重用代码,这样所有的对象是同时编译的。C++ 并不打算提供一种建立可重用二进制组件的方法,以便可以混合使用不同版本和时间的组件。顺便说一句,Java 的缔造者注意到了这些问题,这些不足是开发 Oak 的主要原因,Oak 后来变成了 Java。

Java 的情况又如何?
Java 确实解决了这些问题中的一部分,但是它也引入了一些自己的问题。最大的问题是 Java 组件(通常是 JavaBeans)只能用于使用 Java 编写的程序。现在,Microsoft 虚拟机 (VM) 确实允许您将 JavaBeans 用作 COM 对象,从而可以在任何语言中使用它们。而且,Sun 确实有一个 Java/ActiveX bridge。但是,总的说来,除非在 Windows 中运行程序,否则 Java 仍是一个单语言的系统:Java 组件只能用于 Java 程序。并且,大多数情况下,为了使用 Java,您必须从头开始重新编写系统。(是的,您可以进行本地调用,但是,使用 Java Native Interface(JNI) 非常麻烦,而且程序将再也无法移植了。)GUI 医生认为这很不可取,所以他很高兴 Microsoft 虚拟机 (VM) 更为灵活,至少对 Windows 而言是这样。没有哪种语言,甚至包括 C++、Visual Basic 或 Java,能适合于每位程序员和解决每个问题。

当您用 Java 编写程序时,还必须确定要使用的组件是本地的(在您的计算机上)还是远程的(在另一台计算机上),而且使用本地和远程组件的方法很不相同。

Java 还有一些其他问题,使它还不能成为满足所有组件需要的理想工具。首先,它还没有真正可靠的方法解决版本问题。(Microsoft VM 中的打包管理程序对此会有很大帮助。)其次,Java 多少要比 C++ 慢一些。GUI 医生注意到,在一种 Java 联机杂志上出版的“象 C++ 一样快”的性能测试中,遗漏了 Java 会表现不好的测试。能想到的两个例子是字符串和数组操作(Java 必须在每次访问时进行越界检查),以及初始的方法程序调用(在第一次调用时,Java 必须在类中的一个表内按签名查找该方法程序。当然在后续的调用会很快,那个 Java 杂志所测试的正是后续的调用。)。最后,Java 的“一次一个类”的加载机制会比一次加载所有的代码慢得多(即使代码很少!),因为它需要更多的文件或 HTTP 事务,这些都需要极高的开销。

即使您是按能够获得良好性能的方法使用 Java,当您从另一种语言中使用 Java 组件时,性能也会很糟,因为需要存在翻译层,以连接不相似的语言和对象模型。

Java 的闪光之处在于存在这样一种可能,即您可以在不同的计算机上使用编译好的组件,而不必为每种计算机的处理器和操作系统重新编译。但是,这经常并不是那么回事,在要支持的每种平台上,都需要测试和调试您的组件。

那么,还有别的选择吗?
正如所证实的,可以使用 C++ 连编 DLL 和其他可重用的二进制组件。在 Dale Rogerson 的书 Inside COM 和 Don Box 的书 Essential COM 中,他们都以一个要重用的 C++ 类开始,然后使用一些聪明的技巧解决了我上面列出的每个问题(还有其他一些问题)。毫不奇怪地,他们最后都得出了同样的结果,即 COM。也就是说,二进制代码重用的每个问题的解决方法都是 COM 的一个重要特性。(如果您想要现在就查看这个过程,请查阅 Markus Horstmann 的文章“从 CPP 到 COM”。)

虽然 COM 的“母语”是 C++,从 C 程序中也可以很方便地使用 COM ——甚至头文件就支持这样做。而且,通过一些技巧,在任意一种语言中都可能实现双向 COM 支持,例如 Visual Basic、Java、Delphi 等等。(“双向 COM 支持”的意思是指,有可能既在一种语言中使用 COM 对象,又使用这种语言编写 COM 对象。)在语言的运行时模块中实现 COM 兼容性的工作并不简单,但是好处是巨大的:一旦这样做了,您就拥有了大量已经编写和调试好的 COM 对象,以供您使用。而且,COM 组件会有广阔的市场——Giga Information Group 估计当前的市场是每年 4 亿美元,在三年后预计为 30亿美元。(COM 组件市场比 Microsoft 的增长还要快!)要注意,这些市场预测是针对第三方 COM 对象的,不包括由 Microsoft 提供的 COM 组件。

COM 的另一个关键特性是支持三种类型的对象:进程内 (DLL)、本地(同一计算机上不同进程中的 EXE)以及远程(不同计算机中的 DLL 或 EXE,通过分布式 COM 或称 DCOM 来通讯)。您在编写使用 COM 组件的代码时,不必考虑(甚至知道)最后要使用哪种 COM 对象,因此可使用完全相同的代码来连接进程内、本地或远程对象。COM 是怎样连接到正确的对象上的呢?是这样,它在注册表中查找对象的 Class ID——注册表项告诉 COM 哪种对象可用。COM 做其余的工作,包括启动进程和通过网络通讯。(注意:不同种类的 COM 对象存在性能差异,对此您需要心中有数——但是,不管您最后使用哪种对象,至少用于连接和使用对象的代码是完全相同的。)

但是,COM 并不能解决世界上的所有问题。例如,当您更新一个组件时,仍有可能破坏使用该组件的程序。(可能由于 COM 强制地为组件赋予“黑盒子”视图,从而不可能了解组件的实现细节,也就使得这种破坏并不普遍,但是仍会发生。)所以,您仍需要选择是就地更新组件而承担破坏的风险,还是为新组件使用新的 Class ID。但是,有了 COM,确实可以较为容易地编写一些代码,以使用户(或应用程序)能够选择使用哪种版本的组件,而不必重新编译。

回忆一下,几乎可以使用任何语言来编写和使用 COM 组件,而且它们可以存放在任何计算机上。这很好。但是,跨平台支持又怎样呢?

跨平台的情况是喜忧参半的。忧的是,现在除了 Win32,还没有太多其他平台上的 COM。有一些移植到非 Windows 平台的 COM,但是不多。不过,这只是忧的一面。

好消息是,很快会进行很多的移植,包括对最常见的 UNIX 版本和对 MVS 的移植。而且,Microsoft 在亲自进行一些移植工作。COM 和 DCOM 在您最喜欢的主机和 UNIX 计算机中可用的日子不会太远了,按计划,用于 UNIX 的 COM 在二月份发布。想想看,在一些快速的主机上运行使用任意的语言编写的远程 COM 对象,而您可以通过您的计算机上的任何语言(Visual Basic、Java、Delphi、C++)访问该主机,这是多么酷啊!请查阅 Microsoft COM Web 站点 (http://www.microsoft.com/com/)上的最新信息。

所以,如果您正在为 Windows 编程,您一定会考虑编写 COM 对象,而不管您是使用 Visual Basic、Java、C++、Delphi 还是其他 COM 兼容语言进行开发的。您编写的对象可以用在本地计算机上或远程使用,而不用重新连编您的组件或组件的客户程序,这多亏了 COM 和 DCOM 的魔力。而且如果您想要让您的方案在非 Windows 平台上运行,COM 正越来越成为一种合适的手段,所以很值得认真地探究和考虑一下。

下一步:您应该知道的 COM 基本概念

下周我们将探讨 COM 的基本概念:对象、接口和模块。而且,如果有时间,我们将深入研究一个简单 COM 对象的 C++ 代码(如果没有时间,就下一周再说。)
 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值