下载第 14章 COM,COM+和 ASP
组件对象模型 (Component Object Model,C O M )很受编程人员的欢迎,许多使用 C O M熟练的开发人员认为 C O M就像 A S P一样能够给编程人员灵感。可以说 C O M是微软所创造的最优秀的技术之一。就像 A S P一样,C O M是任何严谨的 Wi n d o w s开发者应该了解和掌握的技术。
因此,让我们研究一下 C O M到底是什么,为什么编程人员这么喜欢它,并且了解 C O M和 A S P
的内在联系,以及如何开发利用它。
令人惊奇的是大多数开发者和最终客户在一些方法、形式上已经不知不觉地运用了 C O M。
作为一个 A S P开发者,几乎一直都在使用着 C O M,所有 A S P内置的对象都是 C O M对象。当用到这些对象时,就调用了 C O M对象的方法。 I I S在很大程度上要用到 C O M。几乎每个微软产品都是基于 C O M或者使用 C O M。所以不必担心,C O M并不是什么新内容而且也不难理解。
在 Windows 2000中,已经引入了 C O M +,,+”标志着对 C O M做了一些重要的改动,即引入了略有区别的基本编程模型和许多企业级的服务,例如事务管理和有限资源共享。
本章将讨论以下内容:
COM的内容。
COM对开发者的意义。
COM的工作方式。
COM+有哪些改动。
用 V B编写的一个简单的 ASP COM组件。
本章用到 C O M这个词时,指的是对所有的 C O M版本都适用的部分,对 Wi n d o w s
9 5,Windows 98,Windows NT和 Windows 2000都适用。当用 C O M+时,指的是
Windows 2000中的特殊部分。
14.1 COM的内容
C O M就是指客户端与 C O M对象之间交流的一种二进制规范,换句话说,是指它们如何相互对话。
简单地说,C O M是基于对象的,可用合适的编程语言把一些代码变成一个组件,而且允许在编程语言中运用这个组件或功能,即使使用的语言不同,也不会出现问题,C O M考虑到了这一点。我们不必了解组件程序是如何编写的 (用了什么技术、算法等 ),用的是什么语言,
只须将它作为一个“黑盒子”,知道如何使用,如何通过一个或多个接口 (相关功能的集合 )来访问其功能即可。
例如,你知道微软用什么语言编写 A S P对象模型或 A D O吗?我们只需了解有哪些可用的
C O M对象、方法和属性,就能使用它们的功能。这些方法和属性称为对象的接口。
如果你认为 A S P的 C r e a t e O b j e c t函数与 C r e a e t e C O M O b j e c t一样,S e r v e r,C r e a t e
O b j e c t函数与 S e r v e r,C r e a t e C O M O b j e c t一样,则说明你知道你在使用 C O M了。
C O M能实现客户与其正在使用的 C O M对象间的透明定位,客户和组件可运行在不同的进程中,甚至在不同的机器上。另外 C O M充分考虑到了机器的远距离连接的需求,使用一个组件,
不管距离远近,都用同样的方式。 C O M提供了完整的基础结构能使所有的组件工作,你只需把注意力集中在重要的环节上,例如编程和运用组件。 C O M对客户和组件开发者的要求是:
组件开发者应使用一种能创建 C O M组件的编程语言,例如 V B,V C + +,所创建的 C O M
组件符合 C O M规范。
客户有一种编程语言或工具 (例如 Microsoft Wo r d或 A S P ),懂得如何实例化和使用 C O M
组件,而且要遵守 C O M规范。像 Microsoft Wo r d和 A S P这样的工具实际上使用其他组件来完成这种创建,Wo r d用的是 V B A,A S P用的是活动脚本 (Active Scripting)。
C O M+并未改变 C O M的基本模型,只是在环境 (将在下一章进行更详细的讨论 )
方面进行了一些扩展,并且引入了 C O M+服务。
上面已经简单地讲了什么是 C O M和 C O M+,其基本原理就是这么简单。
,C O M对象”和,C O M组件”这两个词经常混用,虽然这从技术上讲并不准确,
但一般也不会出错。严格地讲,它们是不同的,前者是后者的一个实例。
14.1.1 COM无处不在
C O M很复杂,要想了解 C O M各方面的细节很费时间,需要大量的阅读和编写代码,一般来说,要达到熟练并按对 C O M和 C O M+的理解编写正确的代码,粗略地算大概需要六个月。
然而,如果一开始就在应用程序中充分利用它,并且用 V B创建 C O M组件,正确地练习,则仅需几个星期。经过一段时间之后,就能掌握基本要点,并能善于使用它。因为理解 C O M确实很费时间,所以一开始就要确保经常创建一些可处理的范例。
本书不准备介绍 C O M的全部内容,也没必要。本章的目的是让读者对 C O M的关键部分有一个总体的了解,重点介绍一些对 A S P组件开发者们很有用的细节。你如果需要更详细地了解 C O M,请参阅以下两本好书。
,Beginning ASP Components》,Wrox 出版社出版 (ISBN 1-861002-88-2)
,VB COM》,Wrox 出版社出版 (ISBN 1-861002-13-0)。
下面将详细介绍 C O M,不必担心刚开始所遇到的任何令人头疼和迷惑的细节,只要认真学习一切都会解决。
14.1.2 COM+的三个方面
C O M可以分为三个基本部分
二进制规范。
运行期或库。
服务。
1,二进制规范
C O M规范规定了像 V B这样的开发工具在把类模块编译成 C O M时所需遵守的一些准则。
A S P这样的技术用它决定如何创建一个组件 ( C O M对象 )的实例并调用它的方法。对开发者们来说,这个规范也定义了一个跨语言的一致性编程接口。 C O M规范没限定的是 C O M自身如何
434计计 ASP 3 高级编程 下载实现。
在本书印刷的时候,COM 规范在已放在下面的网站上,h t t p,/ / W W W,m i c r o s o f t
,c o m / c o m / r e s o u r c e s / c o m d o c s,a s p
所有 C O M规范都是用一种语言中立方式制定的,这使编写客户端程序所使用的语言与编写 C O M对象所使用的语言可以不同,只要支持 C O M规范即可。请记住,C O M是一种二进制规范。
因为 C O M定义了这个通用的基于对象的通信中介,它真正简化了传统的一些繁重的过程。
过去编写客户端 /服务器应用程序,必须为不同的客户类型编写多种 A P I (例如,V C + +和 V B各一种 )。试想在 V B这样的程序语言中使用 C / C + +的 d e c l a r e语句的功能,V B程序员经常被不必要的内容搞糊涂,而且不得不弄懂 C / C + +的结构。然而也许有人会说 d e c l a r e语句和其他的语言组接机制并不太差,但是 C O M更简单,采用的是一种更自然和更一致的方式。
2,运行期或 /库
C O M运行期就是 C O M规范的一种实现,并且随各种版本的 Wi n d o w s提供。尽管运行期的核心 A P I是在 o l e 3 2,d l l中,但其本身存在于许多 D L L中。在开发 A S P时,我们时刻都使用着
C O M运行期和像 o l e 3 2,d l l这样的 D L L提供的所有功能。尽管有时我们并不知道,但为了完成任务确实调用了 C O M运行期。
C O M运行期有时被称为 C O M库。但作者偏爱前者,因为这暗示环境是“执行期间”;有些人喜欢用后者,因为它也是一个函数库。 C O M运行期是一个主要的内容,
希望大家能熟练地运用它。
(1) ASP页面中的 C O M客户在 A S P页面中,通过 A S P解释器间接地使用
C O M运行期,而 A S P解释器在技术上使用活动脚本,这个技术把 V B S c r i p t,J a v a S c r i p t和其他脚本语言转换成 C O M调用,其过程如图 1 4 - 1所示。
例如,当在 A S P页面中使用 C r e a t e O b j e c t或
S e r v e r,C r e a t e O b j c t函数时,可用下面的代码:
A S P页面的代码由 A S P解释器的活动脚本引擎转换为对 C O M运行期的调用。 C O M运行期实际负责创建 ADO Connection对象。
(2) VB中的 C O M客户在 V B中,使用 N E W语句时,就会调用 C O M运行期。当使用 R e f e r e n c e s对话框向一个项目增加 A D O支持时,所做的一切就是告诉 V B你所要使用的一些 C O M组件。所选择的引用决定了这些组件,并且允许像使用任何其他 V B固有类型一样使用它们:
第 1 4章 C O M,C O M +和 A S P计计 435下载图 14-1 ASP页在与 C O M运行期的关系活动脚本ASP解释器
COM运行期作为一位开发者,你发现运行期是 C O M中一个非常有用的部分。 C O M规范是 C O M的准则,从细节上规定了所有的方面 (我们应该了解它 ),但我们每天所用的是运行期。
在 V B中,如果创建的组件不在相同的 D L L中,n e w语句只能转入 C O M运行期,如果是在相同的 D L L中,则 V B不需要使用 C O M运行期,这时 V B可以直创建组件。
(3) 服务服务是一些编译过的代码 (目前只有微软能编写 )。它们能提供一些可以增强组件能力的且容易使用的重要功能。如果你用过 M T S,应该用过声明属性和声明安全,通过这两种方法管理员在运行期可以影响组件的行为和使用。
C O M+中的属性是一项描述组件的运行期特性的信息 (元数据 ),可以用组件服务浏览器来设置它们。通过用这种方法设置组件的一种或多种属性,就能控制它的运行期而不需要额外的编程和重新编译,这是重用代码的最优形式。可以充分利用微软编写的基本代码而不是由自己来开发。例如,一个 C O M+应用程序的
Tr a n s a c t i o n s选项卡,如图 1 4 - 2所示。
在此选项卡中选择 R e q u i r e d选项,就告诉了 C O M+我们所用的组件要用 C O M+提供的事务服务,即告诉 C O M+每个新的组件实例都应加入到一个事务中。
在第 1 9章将要讲到,事务处理能使一组组件做为一个整体单位共同工作。
(4) 没有 C O M,A S P将不再是 ASP
没有 C O M,A S P就不会这么容易扩充,使用也不会如此简单,也许它将不存在。
除非 A S P是建立在一些相同的规范/实现,否则就必须以不同的方式调用不同的组件,而且要根据组件是用什么语言编写的或者是哪个公司提供的。设想一下,为了能引用组件的一些方法,必须弄懂所有方式,这是 C + +开发者多年以来一直面对的一个问题,到今也没解决。
但是 C O M解决了,因为最近许多公司都逐渐适应了 C O M。没有 C O M,就要花很多时间弄懂组件编写者的意图及不同的 A P I。应该感谢 C O M提供了一个容易使用和基于对象编程的范型。
微软估计有 3 0 0万开发者利用 C O M开发 C O M应用程序,每天有 2亿人使用 C O M应用程序,这使 C O M成为世界范围内最成功的对象模型。
14.1.3 COM开发工具就编写组件的语言而言,微软所提供的主要是 V C + +和 V B,本书中两者都要讲到。
V C + +在性能和灵活性的统一上有些缺陷,它使开发者需要深入理解 C O M的内部工作情况。但是,一旦理解了,再使用微软活动模板库 (Active timeplate library,AT L ),编写组件就像用 V B一样又快又简单。但首先,必须了解相应的内容。
436计计 ASP 3 高级编程 下载图 14-2 Tr a n s a c t i o n s选项卡
V B是一个很好的折衷方案如果想不需经过长时间的 C O M学习就能快速地编写组件,并且对组件的性能要求不高,
那么使用 V B是一种很好的折衷方案。 V B隐含了大部分的 C O M运行期库,这意味着不需再花时间学习 C O M,就可以编写 C O M组件;如果用其他的语言,例如 V C + +,则必须熟悉 C O M之后才能创建 C O M组件。
在 V B中,对 C O M有一点基本的理解就能帮助我们优化组件 (通过理解为什么要那样做 ),
这就是我们要介绍更多关于 C O M的内容的原因。尽管 V B隐含了 C O M的大部分,理解 V B如何将其语言结构映射到 C O M,并且了解 V B中 C O M的限制是很重要的。
V B是 A S P的自然发展道路,尤其是对那些习惯于使用 V B S c r i p t工作的人。本书将用很大的篇幅讲述如何用 V B开发组件,同样也会讲述如何使用 C + +开发组件,这与 J a v a有一些相似之处。我们还要讲述如何用 J a v a S c r i p t开发组件。
本书用 VB 6.0作为开发工具,尽管 VB 6.0与 VB 5.0稍微有一点不同,但对于本书讲述的一些主要内容,用早期的版本一样可以做。
14.2 接口
C O M领域的许多人认为 C O M最重要的和最强大的方面是基于接口的编程。
如果编写代码去完成某项功能的话,其接口就是一系列定义某些东西如何使用的方法。
功能与实现的抽象观念已存在多年。只要有一些面向对象的知识的人都会很快对抽象数据类型 (Abstrct Data Ty p e,A D T )做一个正确的比较。
基于接口编程就好像控制电视机,遥控器使我们能够通过一个接口来遥控电视,并不需要知道遥控器的内部工作方式,但应知道如何通过遥控器上的各种按钮来控制选台、调节音量和开关电视。
遥控器按钮提供的功能可以由一个接口定义,称为 I R e m o t C o n t r o l。通过这个接口,各种控制就能实现,遥控器接口具有表 1 4 - 1所示的方法。
表 14-1 遥控器接口具有的方法及说明方 法 说 明
Tu r n O n O ff 如果电视机已经打开,则关闭;反之亦然
C h a n g e C h a n n e l 转换到指定的频道
I n c r e a s e Vo l u m e 增加音量
D e c r e a s e Vo l u m e 降低音量
G e t C h a n n e l 返回目前选定的频道如果有不同厂家生产的三台电视机,由于使用了接口定义,各个遥控器都能使用上述的方法实现相同的功能,每个遥控器也许内部工作情况不同,但知道如何使用接口 (这里的接口就是遥控器上的按钮 )就足够了,只要知道面前是哪台电视机,就能通过他的遥控器调换频道,
看自己喜欢的节目。
从内部过程看,遥控器上接口的实现是通过另外一个 C O M接口与电视机交流,这个接口就是 I Te l e v i s i o n,这个接口有与 I R e m o t e C o n t r o l相似的方法。如果每台电视机都实现相同第 1 4章 C O M,C O M +和 A S P计计 437下载
I Te l e v i s i o n接口,那么一个遥控器就能控制所有的电视机!不管电视机是哪个厂家生产的,只要提供的控制接口相同,遥控器就能控制它。
14.2.1 组件上面例子中的遥控器就是一个 C O M组件,在 V B中编译一个包含在一个 A c t i v e X项目中的类模块时,就创建了一个组件,如果这个项目含有多个类模块,那么就创建了多个组件。
组件就是通过实现一个或多个接口来提供功能的某种东西。
简单地说,组件就是一个具有唯一名称的功能体,并以某种形式的 D L L或 E X E封装或分布。在 V B中编译一个含有四个类模块的 A c t i v e x对象时,所创建的就是含有四个 C O M组件的
C O M服务器,每个组件对应一个类模块,生成了四个 C O M接口,每个接口对应一个组件,这些接口 (类模块 )通常提供了能够访问的方法和属性。类模块与 C O M组件的关系如图 1 4 - 3所示。
图 14-3 类模块与 C O M组件的关系棒形图 (Lollypop Diagram)
C O M中运用了一种简单的图解方法来表现组件支持的接口,即棒形图。这些棒形图中用一个方框表示组件,方框中的名称就是组件名称,方框左侧伸出的部分表示组件的接口,方框上方伸出的单线表示一个称为 I U n k n o w n的接口,这是每个组件必须实现的,下面很快就要讲述这个重要的接口。
遥控器的棒形图如图 1 4 - 4所示。
这个图显示了两个组件 (电视机 ),通过 I R e m o t e
C o n t r o l接口提供简单的频道变换能力,换句话说就是遥控。这个图并不复杂,但清楚地表示了可以用它来控制电视机。
I U n k n o u n接口是唯一从方框上面出去的接口,所以在图上未标注它。
14.2.2 缺省接口创建 C O M组件时,这个组件可以包含很多接口,C O M允许把其中一个接口设置为客户使用的缺省接口,不能自己选择指定接口的客户将使用缺省接口,其理由下面讲述。
在 V B中创建一个类模块,并编译成一个组件时,并不能控制哪个接口成为缺省接口,一个缺省接口通常不仅包括所定义的类模块,还包括公共的方法和属性。可以通过使用
438计计 ASP 3 高级编程 下载
VB ActiveX项目类模块 类模块 编译 COM组件
COM组件 COM组件
COM组件
COM服务器类模块类模块图 14-4 遥控器棒形图
IRemoteControl
大遥控器小遥控器
IRemoteControl
I m p l e m e n t s关键字使得一个类模块支持附加的接口,但缺省时 A S P不能调用这些接口的方法。
在 A c t i v e X项目中创建一个公共类模块时,V B所创建的接口的名称就是类模块的名称前加一个短下划线,例如,M y T V类模块的缺省接口就是 _ M y T V。
14.2.3 GUID— 实体的确定名称编译一个类模块并创建一个 C O M组件时,V B就为这件组件赋予了一个全局唯一的标识符,
称为 G U I D,G U I D是一个根据系统时钟和网卡的 M A C地址生成的一个 1 2 8位的数字,保证是唯一的。
如果没有网卡,G U I D仍然是唯一的,但只能保证在本地计算机中是唯一的。
G U I D是一些标识符的专用术语,这些标识符跨时间和空间唯一准确地表示一个实体,由于 G U I D采用 1 2 8位数,按每秒增加一千万个 G U I D的速度,可以使用到公元 5 7 7 0年。
当 G I U D用来标识一个类模块时,被称为类标识符 ( C L S I D )。在 C O M中,可以使用 G U I D
来标识很多东西,所以,在不同的环境中 G U I D有不同的名称,这些环境包括接口标识符 ( I I D )
和应用程序标识符 ( A P P I D )。
G U I D的用途为什么需要用一个 1 2 8位数字来标识一个组件?难道是我们给类模块取的名称不够用吗?
答案当然否定的。 全球所有的分析家和开发人员每时每刻都在给他们的类模块命名 (逻辑名称 ),
所以两个人同时使用同一种逻辑名称的情况会经常出现,尤其是当人们为同一个范围的问题设计应用程序时。解决这个问题的办法就是在名字中增加一部分标识符,这并不困难。
1 2 8位数字是毫无含义的,也不好使用。人们在注册和其他必须处理 G U I D的地方用一个字符串来代替 G U I D。以下的 C L S I D用来标识 (物理名称 )用于微软 ADO Co n n e c t i o n组件的一个组件:
这不是一个程序化的标识符 ( P r o g I D ),后面将讨论 P r o g I D。
14.2.4 接口的详细内容正如前面提到的,C O M是一种二进制规范,这个二进制规范包括描述一个接口在存储器中的形式以及如何在运行期中访问。
1,虚拟方法表定义一个接口时,接口方法的次序、每一种方法的参数和各种其他的属性,例如接口的
G U I D等,都要通过接口特征来定义。当编译一个包含 C O M组件的 D L L或 E X E文件时,接口特征 (二进制形式 )就被转换进创建的文件,这个信息用来建立虚拟方法表 ( v t a b l e ),它决定一个客户在运行期如何调用接口的各种方法。为了便于理解,可以把一个 v t a b l e看作一个含有各种函数的 N维数组,这里的 N表示一个接口所含方法的个数,如图 1 4 - 5所示。
这个数组实际上并不含有代码,但含有能定位代码地址的指针。因此,通过使用 v t a b l e,
可知下标 0的项目指向 Tu r n O n O ff方法,下标 1的项目指向 C h a n g e C h a n n e l方法,等等。
当编译使用一个接口的客户代码时,这些数组下标 (例如 0,1,.,,)就放置在所创建的 D L L
或 E X E中,这就是早期绑定。客户知道如何通过按给定的偏移量访问某个函数,从而调用接第 1 4章 C O M,C O M +和 A S P计计 439下载口中的一个方法。客户不必在运行期查询任何附加信息,只要有接口指针就能进行调用。接口指针是一种指向可以调用的函数的数组的指针。
图 14-5 虚拟方法表属性就是函数提供读写对象的数据 (或状态 )的能力的接口方法叫做属性。下面讲述的方法与 C + +中的方法类似,但与 V B中给出的例子不同。从语义的角度看它们是相同的,V B也是一种很好的工具;但在实现时却不是,V B引入的封装层可能导致人们的误解。
只读属性等同于单个接口方法,该方法允许读取一个值。
只写属性等同于单个接口方法,该方法允许更新一个值。
读/写属性等同于两个接口方法,两个方法分别允许读取和更新一个值。
因此,如果有四个读/写属性,V B就会创建 8个方法来读取和更新这四个值。
2,接口的要素一般来说,接口通常至少有一个方法,最多 1 0 2 4个 ( C O M和跨场所调度的限制导致的限制 ),一个接口有多少方法是一个设计问题,这个问题是由程序员决定的,可以有一个或者多个,但不是必须有,一个接口可以没有方法。通常一个设计得很好的接口的方法不超过 1 0到
1 5个。
没有方法的接口是不常见的,但也是有用的。它常用于提供组件和顾客间的一种秘密交流或信号,就像你约好了一没见过面的人,约定他穿着一件特别的衣服,因而当你在人多的场合遇见他时,能很快识别他。客户通过接口能检查确保组件是存在的。
这里的要素化指的是逻辑上把相关的方法一起放到一个接口,因此,如果我说小心接口要素化,意思是你应当特别注意那些放在接口的方法。
3,接口的原则从许多方面来看,接口设计与用户界面设计相似。对于用户界面设计问题,需要考虑用户想通过界面做什么,并且使用户非常简单地知道如何做他们想做的事,并且能通过界面去做,而最后一步 (做 )是最重要的。
不同之处在于,在进行用户界面设计时我们处理的是控件,像文本框和单选按钮,及它们在一个或多个窗体上的布局。对于组件设计,我们处理的是属性和方法,以及它们对一个或多个接口的影响,影响的接口越多,对客户就越有用。
就像用户界面设计,C O M接口设计从某种意义上讲是一个基于经验的过程。也许为某一个项目采用一种方式设计,因为它适合这些客户;也许因为有特殊限制或技术上的可能而采用另一种方式设计;不管采用哪种方式,其目的就是让客户满意。
440计计 ASP 3 高级编程 下载组 件客户本书不可能对所遇到的每个问题都给出一套实际的接口设计大纲,但设计得好的接口有一些共同特征:
确保接口对使用者有用。 C O M有定义接口的能力,这些接口在语言上或者应用程序上是不友好的,也就是说它们不能运行。当把 C O M设计成一种二进制标准时,一些语言就比另一些语言功能更强,就像一些浏览器比其他浏览器功能更多一样。如果 C O M限制这些语言,就不能得到广泛地使用,因此,必须认真考虑接口的性能。例如,一个主要用于像 A S P这样的脚本语言环境的组件,应当使用 Va r i a n t作为参数,并且参数能够输入、修改和输出。这是脚本引擎的一个特征。因此,必须确保接口遵守这些原则并能在这样的环境中正常工作。
接口名称 (或类模块 )和方法名称采用描述性的名称。调用一个叫 I D o S o m e t h i n g接口和使用一个叫 D o I t的方法没什么意义,这些名称应该有意义,应尽可能说明它在客户的问题中所完成的功能。
方法应当很好地要素化,逻辑相关,并不要有太多的方法。如果你已经有一个
ICh a n n e l S e l e c t o r的接口,它只应当含有与频道转换有关的方法。例如,如果需要一个
F i n e Tu n e C h a n n e l的方法,它应该包含在 I C h a n n e l Tu n e r接口中。
属性相同。接口的属性应当与客户感兴趣的信息类型一致。
接口应当是强类型的。像 V B和 C + +这些语言都是强类型的,也就是说我们所定义的变量属于某一种类型,它只会有这种类型的数据,例如一个数 ( L o n g型或 I n t e g e r型 ),如果有人想分配给该变量一个 S t r i n g型数据,这时,编译器就会产生错误。 A S P只支持像
V B S c r i p t这样的弱类型脚本语言,在 V B S c r i p t中,所有的变量都被定义为 Va r i a n t型,这种类型的变量可以含有任何类型的数据。 把接口定义为强类型的好处是使用时简单明了,
可以看到所使用的参数的类型,而不必猜测一个方法能处理什么数据类型。
喜欢使用自己的接口。这是一个全球所有成功的公司所遵循的金科玉律:使用自己的接口,或者至少应该确信所使用的接口是个好接口。
4,接口的不变性一旦设计好一个 C O M接口,并且通过某些形式向客户发布了,这个接口就应当被认为是不可变的,不能对其做任何可能影响其二进制表现的改动。
看一下 I Vo l u m e C o n t r o l接口的描述,其中有 I n c r e a s e Vo l u m e方法和 D e c r e a s e Vo l u m e方法。
如果发布了这个组件,而且人们把这个遥控组件用在他们的应用程序中,并使用了我们的接口及其所有方法。如果我们再决定去掉 D e c r e a s e Vo l u m e方法会怎么样呢?显然,那些应用程序将被严重破坏,或者将会有一些非常烦人的相关问题,如果改变一个方法参数的数量或者类型,也可能会产生同样的问题。
因此,有一些原则需要遵守:
在对一个接口满意前,不要发表他。
如果实在需要改变一个接口,不应修改已存在的这个接口,而是为所需的扩展功能再创建一个接口。这当然要求客户类型支持多重接口,但 A S P目前不支持多重接口。
一个接口一旦发布,就不能改变其中的任何一部分,这包括方法的顺序、参数类型等。
如果改变了一个已经存在的接口 (我们推荐不要这样 ),应重新编译所有使用该接口的客户应用程序。就像我们已经讲过的,使用早期绑定的客户将会把接口的设计硬编码为第 1 4章 C O M,C O M +和 A S P计计 441下载
E X E或 D L L,因此,它们必须升级。
记住 V B隐藏了许多接口信息。因此,当修改或增加一个类模块的公用函数、子程序或属性时,实际上就是在修改缺省接口,V B会自动为一个 A c t i v e X项目中的每个公用类模块创建和管理 C O M接口。
这些都是好的 C O M原则,但应用于 A S P中的组件更具有灵活性,因为它们使用后期绑定。
14.2.5 IUnknown接口前面说过每个 C O M组件都要实现一个叫 I U n k n o w n的接口,这个接口在 C O M中起着极为重要的作用,有两个作用:
引用计数。
通过询问支持的接口,动态地揭示接口功能。
1,QueryInterface
Q u e r y I n t e r f a c e是一个方法,通过该方法,可以在运行期动态显示和查询组件的功能。这种方法接受一个接口标识符 ( I I D,另一种类型的 G U I D ),并且如果此接口被支持的话,就返回被请求的接口,供发出请求的代码使用。
在 V B S c r i p t中,使用 S e t关键字调用该方法,这就是查询组件是否支持这个 I I D代表的接口。
2,对象的生存期和引用计数
C O M允许传递一个接口指针到应用程序中,并且,当一个接口在使用时,不能破坏提供指针的 C O M对象。这可以比作一个四方同时通话的电话会议:在会议结束前,电信局应当使电话线保持连接。
为了跟踪接口指针的使用和一个 C O M对象的生存期,我们使用引用计数。
当一个组件第一次由 C O M运行期创建时,它的“生命”就开始了,而且 I U n k n o w n接口的
A d d R e f方法已经被组件自身隐式调用了。 A d d R e f方法的功能很简单,仅将组件引用计数器的值增加 1。引用计数器的初值为 0,创建组件后,其引用计数器的值为 1。
每当 Q u e r y I n t e r f a c e函数给顾客提供一个接口指针,就调用 A d d R e f方法将引用计数器的值加 1;相反地,当顾客使用接口完毕,就调用 R e l e a s e方法,将引用计数器的值减 1。如果引用计数器的值达到 0,那么对象就知道不再有顾客使用它了,这时,它就自我取消,结束其生存期。
C O M对象必须能够可靠地管理自己的生存期。
作为一个非常简单的概念,引用计数是一个强大的功能。然而,在像 C + +这样的语言中有点麻烦,因为程序员必须记住手工调用 A d d R e f和 R e l e a s e,如果调用不配对,对象就永远不会被取消。在 V B和 A S P中,从不直接调用 A d d R e f和 R e l e a s e,因此不存在调用不配对问题。
14.2.6 使用 I D i s p a t c h— 后期绑定
I D i s p a t c h是一个接口,C O M使用它允许客户应用程序在运行期动态地发现和调用组件缺省接口的方法。这一种调用组件功能的方式叫作后期绑定,因为在调用方法之前,组件必须
442计计 ASP 3 高级编程 下载查询在运行期是否支持它。
在 V B中只要声明一个 O b j e c t类型的变量,就使用 I D i s p a t c h接口:
在 A S P中,除了不用 O b j e c t变量类型而用 S e r v e r对象去创建实例外,代码几乎是完全相同的:
因为 A S P中变量是无类型的,也就是说每个定义的变量都是 Va r i a n t类型的,不能请求一个指定接口。例如,在 V B中可以编写以下代码去访问遥控器对象的音量控制接口,并且通过调用 I n c r e a s e Vo l u m e函数来增大音量:
在 A S P中,不能这样做,因为不能请求一个指定的接口。所能做的就是访问组件的缺省接口,因此,为了达到同样的效果,必须用不同的方式去访问这个功能,确实需把组件设计得稍有不同。
不是设计一个有多重接口的组件,而是设计多个组件,每个组件支持一个接口。因此,
若最初的设计是使一个组件有四个接口,现在设计四个组件,每个组件支持一个接口,这些组件能一起工作,并能以属性方式相互暴露,以便允许在接口间导航。
例如,有一个叫作 Vo l u m e C o n t r o l的遥控器对象属性,返回一个实现 I Vo l u m e C o n t r o l接口
(作为它的缺省接口 )的 C O M对象,一个 A S P页面就能使用返回的属性访问接口的方法。程序代码如下:
如果你使用过 Microsoft Off i c e对象模型,就会对这种类型代码十分熟悉。这不像使用多重接口技术的 V B那么精巧,还需要我们做许多额外的工作,但其有相同的效果。因为随着时间的推移,前面的开发者已经编程创建了多种对象,我们将会发现那些为后期绑定客户设计的组件的每个接口会有更多的方法。
使用多重对象的一种替代方法是作为一个属性返回一个组件的非缺省接口,从而避免对多重对象的需要。这种做法在 C O M +应用程序中不保险而且违反了 C O M的第 1 4章 C O M,C O M +和 A S P计计 443下载一条基本规则:一个对象只应返回一个接口的一个实现。
后期绑定在有些环境下更灵活,但也有一些缺陷:
运行比早期绑定慢,这是因为在调用组件的一个方法之前必须先查询组件是否支持这一个方法,而且 I D i s p a t c h接口效率也不高。
由于是在运行期查询,可能会由于组件不支持某一方法而产生错误。例如,方法的名称拼写错误。
总的来说,在可能的情况下,应该用早期绑定,这在 V B中需要有一个类型库 (后面将讨论 )。
在 A S P中,没有别的选择,只有用后期绑定。
14.2.7 组件信息的中央存储库前面已经讲了接口和组件,现在再来讨论一下怎样使组件在 V B环境内外都可以使用。
第一个问题是:在 V B中 R e f e r e n c e s和 C o m p o n e n t s对话框是如何工作的,从哪里获得组件的信息,如何知道列表框应列出的内容。答案是 Wi n d o w s的注册表。
在 C O M世界里,注册表目前起着领导作用,它是一些分层存储的系统数据,用来保存与
C O M有关的信息和其他内容。在 C O M +中的作用稍有不同,组件信息的中央存储库的一般概念相同,但除了遗留组件外,注册表不再是中央存储库了。
应用程序一般不能直接访问注册表,C O M运行期提供了 A P I封装组件的使用。
为了进一步观察注册表,启动注册表编辑程序 r e g e d i t,e x e,运行后可以看到许多键,如图
1 4 - 6所示。
图 14-6 编辑注册表的界面
C O M的信息存放在 H K E Y _ C L A S S E S _ R O O T ( H K C R )键下,这里并不准备详细讨论怎样使用注册表编辑器以及各种各样的键,如果有时间钻研这个问题是十分值得的。但记住,如果使用这种方式,在不能确认所做的事情对系统的影响时,不要删除任何东西。
关于注册表的详细描述和各种与 C O M有关的键,请参考 Richard Grimes的
,COM and Registry》一文,该文在 W W W,c o m d e v e l o p e r,c o m网站上。
在 V B中编写使用组件和接口的代码时,代码中所使用的名称称为逻辑名称,例如
S o m e C l a s s或 I R e m o t e C o n t r o l。组件和接口的物理名称分别是 C L S I D和 I I D,C O M运行期使用
444计计 ASP 3 高级编程 下载这些标识符 (显示在图的上方 )创建组件和查询接口,因此最终编译到组件和应用程序中的是这些标识符。
需要重点指出,如果由于某些原因这些标识符发生变化,则客户应用程序不再工作。为了理解在 V B中怎样建立这些识符,需要关注项目设置,尤其是版本相容性。
1,版本相容性如果在 V B中设置一个 A c t i v e X项目的 Project Properties,并且选择 C o m p o n e n t选项卡,显示的界面如图 1 4 - 7所示。
图 14-7 Component选项卡
Version Compatility选项告诉 V B如何管理唯一的 G U I D,这些 G U I D分配给组件的 C L S I D和接口的 I I D。有三个选项,下面分别介绍。
(1) No Compatility
No Compatility选项告诉 V B在任何时候重新编译应用程序时,都将为组件的公共类、接口和类型库生成一套新的 G U I D,即使不修改组件本身的接口,也要这样做。这就意味着所获得的组件与使用早期绑定编译的客户应用程序不兼容。
一个类型库是一个描述 C O M服务器中所有的组件和接口的文件,V B自动建立这一文件并且将它作为 C O M服务器中的一个绑定资源。在 R e f e r e n c e对话框中选择引用时,V B用这个类型库使得各种组件和接口能够在 I D E中使用。
(2) Project Compatility
Project Compatility选项告诉 V B为组件的公共类和接口生成一套新的 G U I D,但是不修改组件类型库的 G U I D。
为了使这一选项可以正常工作,必须提供编译过的 E X E或 D L L的路径,当再次编译这个项目时,V B重新使用存在于此文件中的类型库的 G U I D。如果不改变这一编译过的类模块的公共接口,V B也将保留定义在一个已编译引用文件中的 C L S I D和 I I D。
(3) Binary Compatility
Binary Compatility选项告诉 V B总是尝试和组件早期版本相容,如果有一点变化就发出警第 1 4章 C O M,C O M +和 A S P计计 445下载告。对于每一次重新编译,V B试着再次使用在指定类型库中发现的 C L S I D和 I I D。如果组件的接口发生变化,V B将提示以下三个选项:
取消编译,以便返回到代码编辑器中,修改不兼容的语句。
打破兼容性,让 V B为所有组件、组件暴露的接口和类型库创建一套新的 G U I D。
保护组件早期版本的兼容性,在这种情况下,V B重新使用旧组件中的所有 G U I D,甚至忽略已存在的客户端程序用新的版本不能工作这种情况。这一选项有时不可用,例如删除一个类模块后。
如果打破兼容性,系统将询问改变项目的名称和编译的 E X E或 D L L的名字,项目名称将成为类型库的名称,这一过程允许新旧版本的组件共存于同一台机器上,这样使用旧版本组件的客户应用程序将可以像以前那样继续工作。
然而,如果拒绝改变项目的名称和 E X E文件的名字,V B将产生一个新组件,除了含有不兼容的定义的接口外,新组件从旧组件那儿继承所有的 C L S I D,这样可以帮助维护那些不愿意修改接口的客户的兼容性,但是一般应该遵照 V B的建议,改变项目和可执行文件的名字。
2,程序设计标识符如果你用过 C r e a t e O b j e c t方法,应该熟悉程序设计标识符 ( P r o g I D ),它为创建一个 C O M组件提供了一个名称。例如:
当在 V B的一个 A c t i v e X项目中创建一个公用类模块时,产生一个 P r o g I D,如下:
P r o g I D是 H K E Y _ C L A S S E S _ R O O T键下的一个子键,提供了一种在运行期从一个字符串标识符查询 C L S I D的方式。使用 ADO Connection组件作为一个例子,可以看到在 P r o g I D键值下有一个称为 C L S I D的子键,那里有一个缺省的字符串值,这就是字符串格式的 G U I D,如图
1 4 - 8所示。
图 14-8 注册表编辑器的界面
14.3 COM+运行期的变化由于提供了大量的能用于增加应用程序功能的新服务,C O M +也在 C O M运行期中包含了一些变化。
现在有两种类型的组件:配置的和非配置的。
注册表不再是组件元数据的主要信息存储库,而 C O M +类别将是主要的信息存储库。
446计计 ASP 3 高级编程 下载另外,还有许多低层的变化,将在下一章讨论。
14.3.1 配置的和非配置的组件在 C O M +中的非配置的组件是指在 Windows 9x或 Windows NT环境下创建的组件。它们不使用 C O M +类型的服务,并且有关它们的信息仅放在系统的注册表中。
C O M +类别 (注册数据库 )里描述了配置的组件,这些组件使用 C O M +的服务,例如事务处理支持。
配置的组件在 Windows 2000环境中向后兼容,它们只可用于进程内的服务,并作为一个组放置在一个 C O M + A p p l i c a t i o n中。如果已经使用了 M T S,一个 C O M + A p p l i c a t i o n与一个软件包基本相同,其作用是作为服务器的替代进程。如果没有使用 M T S,可以认为一个 C O M +
A p p l i c a t i o n是进程内 C O M服务器的一个集合,进程内是指程序运行在一个可执行文件
( d l l h o s t,e x e )的地址空间内。这些将在下一章里进一步解释。
14.3.2 COM+类别在 C O M +中需要花费很多时间通过 Component Services的图形用户界面 ( G U I )来管理配置的组件,如图 1 4 - 9所示。
图 14-9 Component Services的图形用户界面使用这个 G U I维护的所有与配置有关的组件,都存储在 C O M +类别 ( c a t a l o g )中,而不是像传统的 C O M组件那样存储注册表中。 C O M +类别提供到注册数据库 ( R e g D B )的一个接口,注册数据库是 C O M +用来激活 C O M +组件的优化数据库,与 C O M在 Windows 9x和 N T中使用的注册表相似。
如果你以前使用过 M T S,C O M +类别与 M T S类别基本上是同类的东西。
14.4 创建一个 ASP COM组件对于 C O M和 C O M +,我们做了相当多的阐述,下面看一下它们的作用,通过快速而简单地建立一个 D N A组件来说明为什么 V B是开发组件的一个重要工具。
在这个 D N A组件中,将是创建一个以数据为中心的中间层组件,能够从 SQL Server 7.0自带的 p u b s数据库检索一系列书名。这个组件让 A S P开发者不考虑数据库结构,使他们能够获取、增加和删除书名。通过后面几章的讨论,可知这一组件在以用户为中心的组件中是最适合的,但是现在我们将在 A S P中直接使用它。
第 1 4章 C O M,C O M +和 A S P计计 447下载在 A S P主页中,应该包含一个数据库内书名的视图,包括下列字段:
ID:由创建者赋予的唯一的标识符。
Ti t l e:书名。
Price:书的价格。
Notes:关于书的简单描述。
页面主界面如图 1 4 - 1 0所示。
图 14-10 页面主界面单击位于任何一个项目右边的 D e l e t e按钮,将删除这个书名。按了 D e l e t e按钮后,向用户呈现出一个简单的确认删除的界面。如果单击了 Add a new Ti t l e按钮,将运行 A d d,a s p页面,
允许用户加入一个新书名,如图 1 4 - 11所示。
图 1 4 - 11 Add.asp页面
448计计 ASP 3 高级编程 下载点击 Add Ti t l e按钮,将调用 A d d _ U p d a t e D B,a s p页面,使用组件实际完成插入工作。
14.4.1 组件的接口我们将使用一个十分简单的接口,提供如表 1 4 - 2所示的方法。
表 14-2 组件接口具有的方法及说明方 法 说 明
A d d Ti t l e 向数据库增加一个新书名
D e l e t e Ti t l e 根据所指定的书名 I D在数据库中删除一个书名
O p e n Ti t l e s 打开数据连接,对要读书名的数据集初始化
N e x t Ti t l e 从列表中返回下一个书名
C l o s e Ti t l e s 释放由组件使用的所有资源
I s E O F 指示书名列表是否结束这些方法遵守前面讲过的接口准则,所有名称都是描述性的,实现这个接口的类模块名称为 B o o k Ti t l e s,在 V B中将创建一个名为 _ B o o k Ti t l e s的接口。
我们要实现的这个组件具有状态,这就意味着任何一个方法的调用返回后都不会使对象失效或破坏。因此由一个方法 (例如 O p e n Ti t l e s )调用设置的成员变量也可以被另外的方法 (例如
N e x t Ti t l e )调用使用。在本例中,组件的状态就是数据库连接和记录集。
组件的实现只是简单地包装 A D O数据库连接和用于访问 p u b s数据库的 R e c o r d s e t对象。例如当调用 N e x t Ti t l e方法列举书名时,所做的仅仅是调用 R e c o r d s e t对象。
也可以采取其他途径。例如,在调用 O p e n Ti t l e s时列举所有的书名,并将书名的细节存储在内存中。这样做的优点是:较早释放了数据库连接,其他程序可以继续使用这个数据库连接,增加了系统的可扩展性。缺点是随着存储在内存中的信息的增加,增加了管理这些信息的难度,因而也增加了系统内存量的需求、组件的开发时间和复杂性。
14.4.2 创建组件启动 V B,创建一个新的 ActiveX DDL项目,把缺省类模块名改为 B o o k Ti t l e s,然后再把项目的名称改为 B o o k。由于我们使用 A D O,因此需要增加一个对 Microsoft ActiveX Data Object 2.5 Library
的引用,如图 1 4 - 1 2所示。
1,组件的方法下面检查需要增加到 B o o k Ti t l e s类模块的函数。
(1) OpenTi t l e s方法
O p e n Ti t l e s方法使用 SQLOLEDB OLE DB提供者建立一个与 SQL Server 7.0的连接,一旦建立这一连接,就创建一个包含所有书名列表的记录集,这个缺省的记录集使用 O p e n参数,这意味着不能更新此记录集。代码如下:
第 1 4章 C O M,C O M +和 A S P计计 449下载图 14-12 增加引用的界面
(2) CloseTi t l e s方法
C l o s e Ti t l e s方法释放在 O p e n Ti t l e s中打开的 R e c o r d s e t和 C o n n e c t i o n对象。显示了书名列表后,为了尽可能早地断开与数据库的连接,在书名列表显示完成后调用此方法。尽可能早地释放像数据库连接这样的有限资源总是一个好习惯,l i s t,a s p中的程序如下:
(3) NextTi t l e方法
N e x t Ti t l e方法用于返回当前书名的信息,并通过调用 M o v e N e x t方法前移记录集的指针。
代码如下:
450计计 ASP 3 高级编程 下载注意,所有的参数都定义为 Va r i a n t型,因为这个函数接收的所有参数都定义在 L i s t,a s p页面中,其代码如下:
A S P解释器 (在本例中,更确切的说是 V B S c r i p t引擎 )把这些参数 ( s t r I D等 )定义为 Va r i a n t。
因为 N e x t Ti t l e参数是 B y R e f参数,是一个指向含有传递到函数的 Va r i a n t型参数的内存指针,因此,通过函数可以直接更新其值。
如果企图使用 S t r i n g型和 C u r r e n c y型来定义 N e x t Ti t l e函数,就会得到如图 1 4 - 1 3所示的错误。
图 14-13 显示错误信息的界面第 1 4章 C O M,C O M +和 A S P计计 451下载产生这个错误的原因很简单,只是因为一个 B y R e y参数必须接收一个正确类型的指针。
如果把参数定义为 B y Va l型,那么参数就是由值传递,活动脚本引擎试着转换值的类型。当然,
如果不可能转换,还是会出现同样的错误。
许多商业组件在编写时没考虑到 A S P有这种类型的问题,所以经常需要为这些组件编写一些小的包装程序。
(4) IsEOF方法
I s E O F函数指示是否还有更多的书名,直接反映记录集的 E O F属性,当枚举所有书名时,
用它来确定是否还有书名。代码如下:
(5) AddTi t l e方法
A d d Ti t l e方法与一个数据库建立连接,并且打开一个动态的记录集,此记录集可以更新。
代码如下:
在这个程序中,没有重新利用可能已经由 O p e n Ti t l e s打开的连接,这样做是为了使程序简单。即不必检查数据库是否已经打开,这里总是将其打开,当此子程序退出时会自动释放数据库连接和记录集,因此,不必将每个对象设置为 N o t h i n g。
读者会注意到,在这个程序中,没有使用 Va r i a n t型参数,这样做是为了证明在原型中不一定非使用 Va r i a n t型,只要不把在 A S P页面变量表中定义的变量传送给函数。下面的例子说明了在 Add_UpdateDB.asp ASP页面中如何不使用页面中定义的变量而调用函数:
因为参数没有被定义为变量,A S P解释器就会友好地执行必要的转换,为什么它不对页
452计计 ASP 3 高级编程 下载面定义的变量也做这种自动转换呢?这确实很奇怪,只有寄希望于将来的脚本引擎版本会支持这种转换。
(6) DeleteTi t l e方法这个方法用来删除书名和数据库中 R o y s c h e d,S a l e s和 Ti t l e A u t h o r表中有关的行,这很必要,
因为 p u b s数据库结构由 SQL Server提供。当建立一个书名时,我们没有在所有表中增加行,但是有可能删除不是通过这个 A S P U I创建的书名,为了安全起见,必须这样做。代码如下:
一旦把这些函数复制到了 V B类模块,编译这个类模块就能创建一个含有此组件的 C O M服务器,这样做以后,继续我们的工作,编写使用这个组件的 A S P页面。这与以前编写其他页面没什么区别,因此不再赘述。
2,示例的 A S P页面总共有 4个 A S P页面,它们是:
List.asp:使用组件显示书名的完整列表。
Add.asp:提供一个增加新书名的详细信息的简单界面。
Add_UpDateDB.asp:使用组件和 A d d,a s p传送过来的信息增加一个新的书名到数据库。
Delete.asp:使用组件在数据库中删除一个书名。
(1) List.asp页面这个列表页面是一个非常典型的 A S P页面,它检索相应的值并显示为一个列表,唯一值得注意的是这个页面并不使用 A D D,而是用组件的一个实例:
这里不使用 A D O的直接好处是:开发者使用组件,不必担心连接的细节、表的名称和建立 S Q L查询等问题。
一旦打开列表,程序的主要部分只是简单地枚举每个书名,代码如下:
第 1 4章 C O M,C O M +和 A S P计计 453下载返回的数据用来创建一个有多行记录的表:
注意,这里用一种简单的窗体来为每行提供一个 D e l e t e按钮,在下面将看到,按下这个按钮将调用 D e l e t e,a s p页面,书名代码用它的 I D参数传送。
为完整起见,下面给出整个页面的代码:
454计计 ASP 3 高级编程 下载
(2) Delete.asp页面单击 D e l e t e按钮去删除一个书名时,调用这个页面,通过下面这几行命令删除书名。
这里显示了整个页面,除了提供一个返回主列表的按钮,没有其他控件。返回主列表后,
可能需要刷新画面以查看该书名已从列表中删除了。
整个 A S P页面代码如下:
第 1 4章 C O M,C O M +和 A S P计计 455下载
(3) Add.asp页面
A d d,a s p页面是一个典型的 A S P窗体捕获页面,它让用户输入书名 I D、名称、注释和价格,
然后把这些细节传送给 A d d _ U p d a t e D B,a s p页面。代码如下:
456计计 ASP 3 高级编程 下载
(4) Add_UpdateDB.asp页面根据 A d d,a s p传送来的参数,这个页面用下面两行程序增加了一个新书名:
再次看到,通过使用组件,开发者的工作变得比较简单。整个页面代码如下:
14.5 小结
C O M +是 Windows 2000中提供的一种功能非常强大的技术,除非你喜欢违背潮流,否则就应该在产品中使用该技术,这样使客户能够通过你所创建的组件和接口来访问你开发的功能。
正如你所见,在 V B中创建组件很简单,真正所需要注意的是编写代码实现我们想让客户使用的功能。这些客户可以是 A S P页面、任何其他能使用 C O M的工具或编程语言。
C O M能满足所有语言的需要,由于有些语言的功能远比另一些语言强大,因此必须了解那些可能使用你的组件的客户的限制,例如对于 A S P客户,只有当一个函数的参数定义为
Va r i a n t时,在 A S P页面中定义的变量才能传送给这个函数,我们也看到 A S P实际上只能使用组件的缺省接口。
在后面几章,将更深入研究 C O M +对运行期的进一步改进,并且讨论如何使用一些 C O M +
服务。
第 1 4章 C O M,C O M +和 A S P计计 457下载
组件对象模型 (Component Object Model,C O M )很受编程人员的欢迎,许多使用 C O M熟练的开发人员认为 C O M就像 A S P一样能够给编程人员灵感。可以说 C O M是微软所创造的最优秀的技术之一。就像 A S P一样,C O M是任何严谨的 Wi n d o w s开发者应该了解和掌握的技术。
因此,让我们研究一下 C O M到底是什么,为什么编程人员这么喜欢它,并且了解 C O M和 A S P
的内在联系,以及如何开发利用它。
令人惊奇的是大多数开发者和最终客户在一些方法、形式上已经不知不觉地运用了 C O M。
作为一个 A S P开发者,几乎一直都在使用着 C O M,所有 A S P内置的对象都是 C O M对象。当用到这些对象时,就调用了 C O M对象的方法。 I I S在很大程度上要用到 C O M。几乎每个微软产品都是基于 C O M或者使用 C O M。所以不必担心,C O M并不是什么新内容而且也不难理解。
在 Windows 2000中,已经引入了 C O M +,,+”标志着对 C O M做了一些重要的改动,即引入了略有区别的基本编程模型和许多企业级的服务,例如事务管理和有限资源共享。
本章将讨论以下内容:
COM的内容。
COM对开发者的意义。
COM的工作方式。
COM+有哪些改动。
用 V B编写的一个简单的 ASP COM组件。
本章用到 C O M这个词时,指的是对所有的 C O M版本都适用的部分,对 Wi n d o w s
9 5,Windows 98,Windows NT和 Windows 2000都适用。当用 C O M+时,指的是
Windows 2000中的特殊部分。
14.1 COM的内容
C O M就是指客户端与 C O M对象之间交流的一种二进制规范,换句话说,是指它们如何相互对话。
简单地说,C O M是基于对象的,可用合适的编程语言把一些代码变成一个组件,而且允许在编程语言中运用这个组件或功能,即使使用的语言不同,也不会出现问题,C O M考虑到了这一点。我们不必了解组件程序是如何编写的 (用了什么技术、算法等 ),用的是什么语言,
只须将它作为一个“黑盒子”,知道如何使用,如何通过一个或多个接口 (相关功能的集合 )来访问其功能即可。
例如,你知道微软用什么语言编写 A S P对象模型或 A D O吗?我们只需了解有哪些可用的
C O M对象、方法和属性,就能使用它们的功能。这些方法和属性称为对象的接口。
如果你认为 A S P的 C r e a t e O b j e c t函数与 C r e a e t e C O M O b j e c t一样,S e r v e r,C r e a t e
O b j e c t函数与 S e r v e r,C r e a t e C O M O b j e c t一样,则说明你知道你在使用 C O M了。
C O M能实现客户与其正在使用的 C O M对象间的透明定位,客户和组件可运行在不同的进程中,甚至在不同的机器上。另外 C O M充分考虑到了机器的远距离连接的需求,使用一个组件,
不管距离远近,都用同样的方式。 C O M提供了完整的基础结构能使所有的组件工作,你只需把注意力集中在重要的环节上,例如编程和运用组件。 C O M对客户和组件开发者的要求是:
组件开发者应使用一种能创建 C O M组件的编程语言,例如 V B,V C + +,所创建的 C O M
组件符合 C O M规范。
客户有一种编程语言或工具 (例如 Microsoft Wo r d或 A S P ),懂得如何实例化和使用 C O M
组件,而且要遵守 C O M规范。像 Microsoft Wo r d和 A S P这样的工具实际上使用其他组件来完成这种创建,Wo r d用的是 V B A,A S P用的是活动脚本 (Active Scripting)。
C O M+并未改变 C O M的基本模型,只是在环境 (将在下一章进行更详细的讨论 )
方面进行了一些扩展,并且引入了 C O M+服务。
上面已经简单地讲了什么是 C O M和 C O M+,其基本原理就是这么简单。
,C O M对象”和,C O M组件”这两个词经常混用,虽然这从技术上讲并不准确,
但一般也不会出错。严格地讲,它们是不同的,前者是后者的一个实例。
14.1.1 COM无处不在
C O M很复杂,要想了解 C O M各方面的细节很费时间,需要大量的阅读和编写代码,一般来说,要达到熟练并按对 C O M和 C O M+的理解编写正确的代码,粗略地算大概需要六个月。
然而,如果一开始就在应用程序中充分利用它,并且用 V B创建 C O M组件,正确地练习,则仅需几个星期。经过一段时间之后,就能掌握基本要点,并能善于使用它。因为理解 C O M确实很费时间,所以一开始就要确保经常创建一些可处理的范例。
本书不准备介绍 C O M的全部内容,也没必要。本章的目的是让读者对 C O M的关键部分有一个总体的了解,重点介绍一些对 A S P组件开发者们很有用的细节。你如果需要更详细地了解 C O M,请参阅以下两本好书。
,Beginning ASP Components》,Wrox 出版社出版 (ISBN 1-861002-88-2)
,VB COM》,Wrox 出版社出版 (ISBN 1-861002-13-0)。
下面将详细介绍 C O M,不必担心刚开始所遇到的任何令人头疼和迷惑的细节,只要认真学习一切都会解决。
14.1.2 COM+的三个方面
C O M可以分为三个基本部分
二进制规范。
运行期或库。
服务。
1,二进制规范
C O M规范规定了像 V B这样的开发工具在把类模块编译成 C O M时所需遵守的一些准则。
A S P这样的技术用它决定如何创建一个组件 ( C O M对象 )的实例并调用它的方法。对开发者们来说,这个规范也定义了一个跨语言的一致性编程接口。 C O M规范没限定的是 C O M自身如何
434计计 ASP 3 高级编程 下载实现。
在本书印刷的时候,COM 规范在已放在下面的网站上,h t t p,/ / W W W,m i c r o s o f t
,c o m / c o m / r e s o u r c e s / c o m d o c s,a s p
所有 C O M规范都是用一种语言中立方式制定的,这使编写客户端程序所使用的语言与编写 C O M对象所使用的语言可以不同,只要支持 C O M规范即可。请记住,C O M是一种二进制规范。
因为 C O M定义了这个通用的基于对象的通信中介,它真正简化了传统的一些繁重的过程。
过去编写客户端 /服务器应用程序,必须为不同的客户类型编写多种 A P I (例如,V C + +和 V B各一种 )。试想在 V B这样的程序语言中使用 C / C + +的 d e c l a r e语句的功能,V B程序员经常被不必要的内容搞糊涂,而且不得不弄懂 C / C + +的结构。然而也许有人会说 d e c l a r e语句和其他的语言组接机制并不太差,但是 C O M更简单,采用的是一种更自然和更一致的方式。
2,运行期或 /库
C O M运行期就是 C O M规范的一种实现,并且随各种版本的 Wi n d o w s提供。尽管运行期的核心 A P I是在 o l e 3 2,d l l中,但其本身存在于许多 D L L中。在开发 A S P时,我们时刻都使用着
C O M运行期和像 o l e 3 2,d l l这样的 D L L提供的所有功能。尽管有时我们并不知道,但为了完成任务确实调用了 C O M运行期。
C O M运行期有时被称为 C O M库。但作者偏爱前者,因为这暗示环境是“执行期间”;有些人喜欢用后者,因为它也是一个函数库。 C O M运行期是一个主要的内容,
希望大家能熟练地运用它。
(1) ASP页面中的 C O M客户在 A S P页面中,通过 A S P解释器间接地使用
C O M运行期,而 A S P解释器在技术上使用活动脚本,这个技术把 V B S c r i p t,J a v a S c r i p t和其他脚本语言转换成 C O M调用,其过程如图 1 4 - 1所示。
例如,当在 A S P页面中使用 C r e a t e O b j e c t或
S e r v e r,C r e a t e O b j c t函数时,可用下面的代码:
A S P页面的代码由 A S P解释器的活动脚本引擎转换为对 C O M运行期的调用。 C O M运行期实际负责创建 ADO Connection对象。
(2) VB中的 C O M客户在 V B中,使用 N E W语句时,就会调用 C O M运行期。当使用 R e f e r e n c e s对话框向一个项目增加 A D O支持时,所做的一切就是告诉 V B你所要使用的一些 C O M组件。所选择的引用决定了这些组件,并且允许像使用任何其他 V B固有类型一样使用它们:
第 1 4章 C O M,C O M +和 A S P计计 435下载图 14-1 ASP页在与 C O M运行期的关系活动脚本ASP解释器
COM运行期作为一位开发者,你发现运行期是 C O M中一个非常有用的部分。 C O M规范是 C O M的准则,从细节上规定了所有的方面 (我们应该了解它 ),但我们每天所用的是运行期。
在 V B中,如果创建的组件不在相同的 D L L中,n e w语句只能转入 C O M运行期,如果是在相同的 D L L中,则 V B不需要使用 C O M运行期,这时 V B可以直创建组件。
(3) 服务服务是一些编译过的代码 (目前只有微软能编写 )。它们能提供一些可以增强组件能力的且容易使用的重要功能。如果你用过 M T S,应该用过声明属性和声明安全,通过这两种方法管理员在运行期可以影响组件的行为和使用。
C O M+中的属性是一项描述组件的运行期特性的信息 (元数据 ),可以用组件服务浏览器来设置它们。通过用这种方法设置组件的一种或多种属性,就能控制它的运行期而不需要额外的编程和重新编译,这是重用代码的最优形式。可以充分利用微软编写的基本代码而不是由自己来开发。例如,一个 C O M+应用程序的
Tr a n s a c t i o n s选项卡,如图 1 4 - 2所示。
在此选项卡中选择 R e q u i r e d选项,就告诉了 C O M+我们所用的组件要用 C O M+提供的事务服务,即告诉 C O M+每个新的组件实例都应加入到一个事务中。
在第 1 9章将要讲到,事务处理能使一组组件做为一个整体单位共同工作。
(4) 没有 C O M,A S P将不再是 ASP
没有 C O M,A S P就不会这么容易扩充,使用也不会如此简单,也许它将不存在。
除非 A S P是建立在一些相同的规范/实现,否则就必须以不同的方式调用不同的组件,而且要根据组件是用什么语言编写的或者是哪个公司提供的。设想一下,为了能引用组件的一些方法,必须弄懂所有方式,这是 C + +开发者多年以来一直面对的一个问题,到今也没解决。
但是 C O M解决了,因为最近许多公司都逐渐适应了 C O M。没有 C O M,就要花很多时间弄懂组件编写者的意图及不同的 A P I。应该感谢 C O M提供了一个容易使用和基于对象编程的范型。
微软估计有 3 0 0万开发者利用 C O M开发 C O M应用程序,每天有 2亿人使用 C O M应用程序,这使 C O M成为世界范围内最成功的对象模型。
14.1.3 COM开发工具就编写组件的语言而言,微软所提供的主要是 V C + +和 V B,本书中两者都要讲到。
V C + +在性能和灵活性的统一上有些缺陷,它使开发者需要深入理解 C O M的内部工作情况。但是,一旦理解了,再使用微软活动模板库 (Active timeplate library,AT L ),编写组件就像用 V B一样又快又简单。但首先,必须了解相应的内容。
436计计 ASP 3 高级编程 下载图 14-2 Tr a n s a c t i o n s选项卡
V B是一个很好的折衷方案如果想不需经过长时间的 C O M学习就能快速地编写组件,并且对组件的性能要求不高,
那么使用 V B是一种很好的折衷方案。 V B隐含了大部分的 C O M运行期库,这意味着不需再花时间学习 C O M,就可以编写 C O M组件;如果用其他的语言,例如 V C + +,则必须熟悉 C O M之后才能创建 C O M组件。
在 V B中,对 C O M有一点基本的理解就能帮助我们优化组件 (通过理解为什么要那样做 ),
这就是我们要介绍更多关于 C O M的内容的原因。尽管 V B隐含了 C O M的大部分,理解 V B如何将其语言结构映射到 C O M,并且了解 V B中 C O M的限制是很重要的。
V B是 A S P的自然发展道路,尤其是对那些习惯于使用 V B S c r i p t工作的人。本书将用很大的篇幅讲述如何用 V B开发组件,同样也会讲述如何使用 C + +开发组件,这与 J a v a有一些相似之处。我们还要讲述如何用 J a v a S c r i p t开发组件。
本书用 VB 6.0作为开发工具,尽管 VB 6.0与 VB 5.0稍微有一点不同,但对于本书讲述的一些主要内容,用早期的版本一样可以做。
14.2 接口
C O M领域的许多人认为 C O M最重要的和最强大的方面是基于接口的编程。
如果编写代码去完成某项功能的话,其接口就是一系列定义某些东西如何使用的方法。
功能与实现的抽象观念已存在多年。只要有一些面向对象的知识的人都会很快对抽象数据类型 (Abstrct Data Ty p e,A D T )做一个正确的比较。
基于接口编程就好像控制电视机,遥控器使我们能够通过一个接口来遥控电视,并不需要知道遥控器的内部工作方式,但应知道如何通过遥控器上的各种按钮来控制选台、调节音量和开关电视。
遥控器按钮提供的功能可以由一个接口定义,称为 I R e m o t C o n t r o l。通过这个接口,各种控制就能实现,遥控器接口具有表 1 4 - 1所示的方法。
表 14-1 遥控器接口具有的方法及说明方 法 说 明
Tu r n O n O ff 如果电视机已经打开,则关闭;反之亦然
C h a n g e C h a n n e l 转换到指定的频道
I n c r e a s e Vo l u m e 增加音量
D e c r e a s e Vo l u m e 降低音量
G e t C h a n n e l 返回目前选定的频道如果有不同厂家生产的三台电视机,由于使用了接口定义,各个遥控器都能使用上述的方法实现相同的功能,每个遥控器也许内部工作情况不同,但知道如何使用接口 (这里的接口就是遥控器上的按钮 )就足够了,只要知道面前是哪台电视机,就能通过他的遥控器调换频道,
看自己喜欢的节目。
从内部过程看,遥控器上接口的实现是通过另外一个 C O M接口与电视机交流,这个接口就是 I Te l e v i s i o n,这个接口有与 I R e m o t e C o n t r o l相似的方法。如果每台电视机都实现相同第 1 4章 C O M,C O M +和 A S P计计 437下载
I Te l e v i s i o n接口,那么一个遥控器就能控制所有的电视机!不管电视机是哪个厂家生产的,只要提供的控制接口相同,遥控器就能控制它。
14.2.1 组件上面例子中的遥控器就是一个 C O M组件,在 V B中编译一个包含在一个 A c t i v e X项目中的类模块时,就创建了一个组件,如果这个项目含有多个类模块,那么就创建了多个组件。
组件就是通过实现一个或多个接口来提供功能的某种东西。
简单地说,组件就是一个具有唯一名称的功能体,并以某种形式的 D L L或 E X E封装或分布。在 V B中编译一个含有四个类模块的 A c t i v e x对象时,所创建的就是含有四个 C O M组件的
C O M服务器,每个组件对应一个类模块,生成了四个 C O M接口,每个接口对应一个组件,这些接口 (类模块 )通常提供了能够访问的方法和属性。类模块与 C O M组件的关系如图 1 4 - 3所示。
图 14-3 类模块与 C O M组件的关系棒形图 (Lollypop Diagram)
C O M中运用了一种简单的图解方法来表现组件支持的接口,即棒形图。这些棒形图中用一个方框表示组件,方框中的名称就是组件名称,方框左侧伸出的部分表示组件的接口,方框上方伸出的单线表示一个称为 I U n k n o w n的接口,这是每个组件必须实现的,下面很快就要讲述这个重要的接口。
遥控器的棒形图如图 1 4 - 4所示。
这个图显示了两个组件 (电视机 ),通过 I R e m o t e
C o n t r o l接口提供简单的频道变换能力,换句话说就是遥控。这个图并不复杂,但清楚地表示了可以用它来控制电视机。
I U n k n o u n接口是唯一从方框上面出去的接口,所以在图上未标注它。
14.2.2 缺省接口创建 C O M组件时,这个组件可以包含很多接口,C O M允许把其中一个接口设置为客户使用的缺省接口,不能自己选择指定接口的客户将使用缺省接口,其理由下面讲述。
在 V B中创建一个类模块,并编译成一个组件时,并不能控制哪个接口成为缺省接口,一个缺省接口通常不仅包括所定义的类模块,还包括公共的方法和属性。可以通过使用
438计计 ASP 3 高级编程 下载
VB ActiveX项目类模块 类模块 编译 COM组件
COM组件 COM组件
COM组件
COM服务器类模块类模块图 14-4 遥控器棒形图
IRemoteControl
大遥控器小遥控器
IRemoteControl
I m p l e m e n t s关键字使得一个类模块支持附加的接口,但缺省时 A S P不能调用这些接口的方法。
在 A c t i v e X项目中创建一个公共类模块时,V B所创建的接口的名称就是类模块的名称前加一个短下划线,例如,M y T V类模块的缺省接口就是 _ M y T V。
14.2.3 GUID— 实体的确定名称编译一个类模块并创建一个 C O M组件时,V B就为这件组件赋予了一个全局唯一的标识符,
称为 G U I D,G U I D是一个根据系统时钟和网卡的 M A C地址生成的一个 1 2 8位的数字,保证是唯一的。
如果没有网卡,G U I D仍然是唯一的,但只能保证在本地计算机中是唯一的。
G U I D是一些标识符的专用术语,这些标识符跨时间和空间唯一准确地表示一个实体,由于 G U I D采用 1 2 8位数,按每秒增加一千万个 G U I D的速度,可以使用到公元 5 7 7 0年。
当 G I U D用来标识一个类模块时,被称为类标识符 ( C L S I D )。在 C O M中,可以使用 G U I D
来标识很多东西,所以,在不同的环境中 G U I D有不同的名称,这些环境包括接口标识符 ( I I D )
和应用程序标识符 ( A P P I D )。
G U I D的用途为什么需要用一个 1 2 8位数字来标识一个组件?难道是我们给类模块取的名称不够用吗?
答案当然否定的。 全球所有的分析家和开发人员每时每刻都在给他们的类模块命名 (逻辑名称 ),
所以两个人同时使用同一种逻辑名称的情况会经常出现,尤其是当人们为同一个范围的问题设计应用程序时。解决这个问题的办法就是在名字中增加一部分标识符,这并不困难。
1 2 8位数字是毫无含义的,也不好使用。人们在注册和其他必须处理 G U I D的地方用一个字符串来代替 G U I D。以下的 C L S I D用来标识 (物理名称 )用于微软 ADO Co n n e c t i o n组件的一个组件:
这不是一个程序化的标识符 ( P r o g I D ),后面将讨论 P r o g I D。
14.2.4 接口的详细内容正如前面提到的,C O M是一种二进制规范,这个二进制规范包括描述一个接口在存储器中的形式以及如何在运行期中访问。
1,虚拟方法表定义一个接口时,接口方法的次序、每一种方法的参数和各种其他的属性,例如接口的
G U I D等,都要通过接口特征来定义。当编译一个包含 C O M组件的 D L L或 E X E文件时,接口特征 (二进制形式 )就被转换进创建的文件,这个信息用来建立虚拟方法表 ( v t a b l e ),它决定一个客户在运行期如何调用接口的各种方法。为了便于理解,可以把一个 v t a b l e看作一个含有各种函数的 N维数组,这里的 N表示一个接口所含方法的个数,如图 1 4 - 5所示。
这个数组实际上并不含有代码,但含有能定位代码地址的指针。因此,通过使用 v t a b l e,
可知下标 0的项目指向 Tu r n O n O ff方法,下标 1的项目指向 C h a n g e C h a n n e l方法,等等。
当编译使用一个接口的客户代码时,这些数组下标 (例如 0,1,.,,)就放置在所创建的 D L L
或 E X E中,这就是早期绑定。客户知道如何通过按给定的偏移量访问某个函数,从而调用接第 1 4章 C O M,C O M +和 A S P计计 439下载口中的一个方法。客户不必在运行期查询任何附加信息,只要有接口指针就能进行调用。接口指针是一种指向可以调用的函数的数组的指针。
图 14-5 虚拟方法表属性就是函数提供读写对象的数据 (或状态 )的能力的接口方法叫做属性。下面讲述的方法与 C + +中的方法类似,但与 V B中给出的例子不同。从语义的角度看它们是相同的,V B也是一种很好的工具;但在实现时却不是,V B引入的封装层可能导致人们的误解。
只读属性等同于单个接口方法,该方法允许读取一个值。
只写属性等同于单个接口方法,该方法允许更新一个值。
读/写属性等同于两个接口方法,两个方法分别允许读取和更新一个值。
因此,如果有四个读/写属性,V B就会创建 8个方法来读取和更新这四个值。
2,接口的要素一般来说,接口通常至少有一个方法,最多 1 0 2 4个 ( C O M和跨场所调度的限制导致的限制 ),一个接口有多少方法是一个设计问题,这个问题是由程序员决定的,可以有一个或者多个,但不是必须有,一个接口可以没有方法。通常一个设计得很好的接口的方法不超过 1 0到
1 5个。
没有方法的接口是不常见的,但也是有用的。它常用于提供组件和顾客间的一种秘密交流或信号,就像你约好了一没见过面的人,约定他穿着一件特别的衣服,因而当你在人多的场合遇见他时,能很快识别他。客户通过接口能检查确保组件是存在的。
这里的要素化指的是逻辑上把相关的方法一起放到一个接口,因此,如果我说小心接口要素化,意思是你应当特别注意那些放在接口的方法。
3,接口的原则从许多方面来看,接口设计与用户界面设计相似。对于用户界面设计问题,需要考虑用户想通过界面做什么,并且使用户非常简单地知道如何做他们想做的事,并且能通过界面去做,而最后一步 (做 )是最重要的。
不同之处在于,在进行用户界面设计时我们处理的是控件,像文本框和单选按钮,及它们在一个或多个窗体上的布局。对于组件设计,我们处理的是属性和方法,以及它们对一个或多个接口的影响,影响的接口越多,对客户就越有用。
就像用户界面设计,C O M接口设计从某种意义上讲是一个基于经验的过程。也许为某一个项目采用一种方式设计,因为它适合这些客户;也许因为有特殊限制或技术上的可能而采用另一种方式设计;不管采用哪种方式,其目的就是让客户满意。
440计计 ASP 3 高级编程 下载组 件客户本书不可能对所遇到的每个问题都给出一套实际的接口设计大纲,但设计得好的接口有一些共同特征:
确保接口对使用者有用。 C O M有定义接口的能力,这些接口在语言上或者应用程序上是不友好的,也就是说它们不能运行。当把 C O M设计成一种二进制标准时,一些语言就比另一些语言功能更强,就像一些浏览器比其他浏览器功能更多一样。如果 C O M限制这些语言,就不能得到广泛地使用,因此,必须认真考虑接口的性能。例如,一个主要用于像 A S P这样的脚本语言环境的组件,应当使用 Va r i a n t作为参数,并且参数能够输入、修改和输出。这是脚本引擎的一个特征。因此,必须确保接口遵守这些原则并能在这样的环境中正常工作。
接口名称 (或类模块 )和方法名称采用描述性的名称。调用一个叫 I D o S o m e t h i n g接口和使用一个叫 D o I t的方法没什么意义,这些名称应该有意义,应尽可能说明它在客户的问题中所完成的功能。
方法应当很好地要素化,逻辑相关,并不要有太多的方法。如果你已经有一个
ICh a n n e l S e l e c t o r的接口,它只应当含有与频道转换有关的方法。例如,如果需要一个
F i n e Tu n e C h a n n e l的方法,它应该包含在 I C h a n n e l Tu n e r接口中。
属性相同。接口的属性应当与客户感兴趣的信息类型一致。
接口应当是强类型的。像 V B和 C + +这些语言都是强类型的,也就是说我们所定义的变量属于某一种类型,它只会有这种类型的数据,例如一个数 ( L o n g型或 I n t e g e r型 ),如果有人想分配给该变量一个 S t r i n g型数据,这时,编译器就会产生错误。 A S P只支持像
V B S c r i p t这样的弱类型脚本语言,在 V B S c r i p t中,所有的变量都被定义为 Va r i a n t型,这种类型的变量可以含有任何类型的数据。 把接口定义为强类型的好处是使用时简单明了,
可以看到所使用的参数的类型,而不必猜测一个方法能处理什么数据类型。
喜欢使用自己的接口。这是一个全球所有成功的公司所遵循的金科玉律:使用自己的接口,或者至少应该确信所使用的接口是个好接口。
4,接口的不变性一旦设计好一个 C O M接口,并且通过某些形式向客户发布了,这个接口就应当被认为是不可变的,不能对其做任何可能影响其二进制表现的改动。
看一下 I Vo l u m e C o n t r o l接口的描述,其中有 I n c r e a s e Vo l u m e方法和 D e c r e a s e Vo l u m e方法。
如果发布了这个组件,而且人们把这个遥控组件用在他们的应用程序中,并使用了我们的接口及其所有方法。如果我们再决定去掉 D e c r e a s e Vo l u m e方法会怎么样呢?显然,那些应用程序将被严重破坏,或者将会有一些非常烦人的相关问题,如果改变一个方法参数的数量或者类型,也可能会产生同样的问题。
因此,有一些原则需要遵守:
在对一个接口满意前,不要发表他。
如果实在需要改变一个接口,不应修改已存在的这个接口,而是为所需的扩展功能再创建一个接口。这当然要求客户类型支持多重接口,但 A S P目前不支持多重接口。
一个接口一旦发布,就不能改变其中的任何一部分,这包括方法的顺序、参数类型等。
如果改变了一个已经存在的接口 (我们推荐不要这样 ),应重新编译所有使用该接口的客户应用程序。就像我们已经讲过的,使用早期绑定的客户将会把接口的设计硬编码为第 1 4章 C O M,C O M +和 A S P计计 441下载
E X E或 D L L,因此,它们必须升级。
记住 V B隐藏了许多接口信息。因此,当修改或增加一个类模块的公用函数、子程序或属性时,实际上就是在修改缺省接口,V B会自动为一个 A c t i v e X项目中的每个公用类模块创建和管理 C O M接口。
这些都是好的 C O M原则,但应用于 A S P中的组件更具有灵活性,因为它们使用后期绑定。
14.2.5 IUnknown接口前面说过每个 C O M组件都要实现一个叫 I U n k n o w n的接口,这个接口在 C O M中起着极为重要的作用,有两个作用:
引用计数。
通过询问支持的接口,动态地揭示接口功能。
1,QueryInterface
Q u e r y I n t e r f a c e是一个方法,通过该方法,可以在运行期动态显示和查询组件的功能。这种方法接受一个接口标识符 ( I I D,另一种类型的 G U I D ),并且如果此接口被支持的话,就返回被请求的接口,供发出请求的代码使用。
在 V B S c r i p t中,使用 S e t关键字调用该方法,这就是查询组件是否支持这个 I I D代表的接口。
2,对象的生存期和引用计数
C O M允许传递一个接口指针到应用程序中,并且,当一个接口在使用时,不能破坏提供指针的 C O M对象。这可以比作一个四方同时通话的电话会议:在会议结束前,电信局应当使电话线保持连接。
为了跟踪接口指针的使用和一个 C O M对象的生存期,我们使用引用计数。
当一个组件第一次由 C O M运行期创建时,它的“生命”就开始了,而且 I U n k n o w n接口的
A d d R e f方法已经被组件自身隐式调用了。 A d d R e f方法的功能很简单,仅将组件引用计数器的值增加 1。引用计数器的初值为 0,创建组件后,其引用计数器的值为 1。
每当 Q u e r y I n t e r f a c e函数给顾客提供一个接口指针,就调用 A d d R e f方法将引用计数器的值加 1;相反地,当顾客使用接口完毕,就调用 R e l e a s e方法,将引用计数器的值减 1。如果引用计数器的值达到 0,那么对象就知道不再有顾客使用它了,这时,它就自我取消,结束其生存期。
C O M对象必须能够可靠地管理自己的生存期。
作为一个非常简单的概念,引用计数是一个强大的功能。然而,在像 C + +这样的语言中有点麻烦,因为程序员必须记住手工调用 A d d R e f和 R e l e a s e,如果调用不配对,对象就永远不会被取消。在 V B和 A S P中,从不直接调用 A d d R e f和 R e l e a s e,因此不存在调用不配对问题。
14.2.6 使用 I D i s p a t c h— 后期绑定
I D i s p a t c h是一个接口,C O M使用它允许客户应用程序在运行期动态地发现和调用组件缺省接口的方法。这一种调用组件功能的方式叫作后期绑定,因为在调用方法之前,组件必须
442计计 ASP 3 高级编程 下载查询在运行期是否支持它。
在 V B中只要声明一个 O b j e c t类型的变量,就使用 I D i s p a t c h接口:
在 A S P中,除了不用 O b j e c t变量类型而用 S e r v e r对象去创建实例外,代码几乎是完全相同的:
因为 A S P中变量是无类型的,也就是说每个定义的变量都是 Va r i a n t类型的,不能请求一个指定接口。例如,在 V B中可以编写以下代码去访问遥控器对象的音量控制接口,并且通过调用 I n c r e a s e Vo l u m e函数来增大音量:
在 A S P中,不能这样做,因为不能请求一个指定的接口。所能做的就是访问组件的缺省接口,因此,为了达到同样的效果,必须用不同的方式去访问这个功能,确实需把组件设计得稍有不同。
不是设计一个有多重接口的组件,而是设计多个组件,每个组件支持一个接口。因此,
若最初的设计是使一个组件有四个接口,现在设计四个组件,每个组件支持一个接口,这些组件能一起工作,并能以属性方式相互暴露,以便允许在接口间导航。
例如,有一个叫作 Vo l u m e C o n t r o l的遥控器对象属性,返回一个实现 I Vo l u m e C o n t r o l接口
(作为它的缺省接口 )的 C O M对象,一个 A S P页面就能使用返回的属性访问接口的方法。程序代码如下:
如果你使用过 Microsoft Off i c e对象模型,就会对这种类型代码十分熟悉。这不像使用多重接口技术的 V B那么精巧,还需要我们做许多额外的工作,但其有相同的效果。因为随着时间的推移,前面的开发者已经编程创建了多种对象,我们将会发现那些为后期绑定客户设计的组件的每个接口会有更多的方法。
使用多重对象的一种替代方法是作为一个属性返回一个组件的非缺省接口,从而避免对多重对象的需要。这种做法在 C O M +应用程序中不保险而且违反了 C O M的第 1 4章 C O M,C O M +和 A S P计计 443下载一条基本规则:一个对象只应返回一个接口的一个实现。
后期绑定在有些环境下更灵活,但也有一些缺陷:
运行比早期绑定慢,这是因为在调用组件的一个方法之前必须先查询组件是否支持这一个方法,而且 I D i s p a t c h接口效率也不高。
由于是在运行期查询,可能会由于组件不支持某一方法而产生错误。例如,方法的名称拼写错误。
总的来说,在可能的情况下,应该用早期绑定,这在 V B中需要有一个类型库 (后面将讨论 )。
在 A S P中,没有别的选择,只有用后期绑定。
14.2.7 组件信息的中央存储库前面已经讲了接口和组件,现在再来讨论一下怎样使组件在 V B环境内外都可以使用。
第一个问题是:在 V B中 R e f e r e n c e s和 C o m p o n e n t s对话框是如何工作的,从哪里获得组件的信息,如何知道列表框应列出的内容。答案是 Wi n d o w s的注册表。
在 C O M世界里,注册表目前起着领导作用,它是一些分层存储的系统数据,用来保存与
C O M有关的信息和其他内容。在 C O M +中的作用稍有不同,组件信息的中央存储库的一般概念相同,但除了遗留组件外,注册表不再是中央存储库了。
应用程序一般不能直接访问注册表,C O M运行期提供了 A P I封装组件的使用。
为了进一步观察注册表,启动注册表编辑程序 r e g e d i t,e x e,运行后可以看到许多键,如图
1 4 - 6所示。
图 14-6 编辑注册表的界面
C O M的信息存放在 H K E Y _ C L A S S E S _ R O O T ( H K C R )键下,这里并不准备详细讨论怎样使用注册表编辑器以及各种各样的键,如果有时间钻研这个问题是十分值得的。但记住,如果使用这种方式,在不能确认所做的事情对系统的影响时,不要删除任何东西。
关于注册表的详细描述和各种与 C O M有关的键,请参考 Richard Grimes的
,COM and Registry》一文,该文在 W W W,c o m d e v e l o p e r,c o m网站上。
在 V B中编写使用组件和接口的代码时,代码中所使用的名称称为逻辑名称,例如
S o m e C l a s s或 I R e m o t e C o n t r o l。组件和接口的物理名称分别是 C L S I D和 I I D,C O M运行期使用
444计计 ASP 3 高级编程 下载这些标识符 (显示在图的上方 )创建组件和查询接口,因此最终编译到组件和应用程序中的是这些标识符。
需要重点指出,如果由于某些原因这些标识符发生变化,则客户应用程序不再工作。为了理解在 V B中怎样建立这些识符,需要关注项目设置,尤其是版本相容性。
1,版本相容性如果在 V B中设置一个 A c t i v e X项目的 Project Properties,并且选择 C o m p o n e n t选项卡,显示的界面如图 1 4 - 7所示。
图 14-7 Component选项卡
Version Compatility选项告诉 V B如何管理唯一的 G U I D,这些 G U I D分配给组件的 C L S I D和接口的 I I D。有三个选项,下面分别介绍。
(1) No Compatility
No Compatility选项告诉 V B在任何时候重新编译应用程序时,都将为组件的公共类、接口和类型库生成一套新的 G U I D,即使不修改组件本身的接口,也要这样做。这就意味着所获得的组件与使用早期绑定编译的客户应用程序不兼容。
一个类型库是一个描述 C O M服务器中所有的组件和接口的文件,V B自动建立这一文件并且将它作为 C O M服务器中的一个绑定资源。在 R e f e r e n c e对话框中选择引用时,V B用这个类型库使得各种组件和接口能够在 I D E中使用。
(2) Project Compatility
Project Compatility选项告诉 V B为组件的公共类和接口生成一套新的 G U I D,但是不修改组件类型库的 G U I D。
为了使这一选项可以正常工作,必须提供编译过的 E X E或 D L L的路径,当再次编译这个项目时,V B重新使用存在于此文件中的类型库的 G U I D。如果不改变这一编译过的类模块的公共接口,V B也将保留定义在一个已编译引用文件中的 C L S I D和 I I D。
(3) Binary Compatility
Binary Compatility选项告诉 V B总是尝试和组件早期版本相容,如果有一点变化就发出警第 1 4章 C O M,C O M +和 A S P计计 445下载告。对于每一次重新编译,V B试着再次使用在指定类型库中发现的 C L S I D和 I I D。如果组件的接口发生变化,V B将提示以下三个选项:
取消编译,以便返回到代码编辑器中,修改不兼容的语句。
打破兼容性,让 V B为所有组件、组件暴露的接口和类型库创建一套新的 G U I D。
保护组件早期版本的兼容性,在这种情况下,V B重新使用旧组件中的所有 G U I D,甚至忽略已存在的客户端程序用新的版本不能工作这种情况。这一选项有时不可用,例如删除一个类模块后。
如果打破兼容性,系统将询问改变项目的名称和编译的 E X E或 D L L的名字,项目名称将成为类型库的名称,这一过程允许新旧版本的组件共存于同一台机器上,这样使用旧版本组件的客户应用程序将可以像以前那样继续工作。
然而,如果拒绝改变项目的名称和 E X E文件的名字,V B将产生一个新组件,除了含有不兼容的定义的接口外,新组件从旧组件那儿继承所有的 C L S I D,这样可以帮助维护那些不愿意修改接口的客户的兼容性,但是一般应该遵照 V B的建议,改变项目和可执行文件的名字。
2,程序设计标识符如果你用过 C r e a t e O b j e c t方法,应该熟悉程序设计标识符 ( P r o g I D ),它为创建一个 C O M组件提供了一个名称。例如:
当在 V B的一个 A c t i v e X项目中创建一个公用类模块时,产生一个 P r o g I D,如下:
P r o g I D是 H K E Y _ C L A S S E S _ R O O T键下的一个子键,提供了一种在运行期从一个字符串标识符查询 C L S I D的方式。使用 ADO Connection组件作为一个例子,可以看到在 P r o g I D键值下有一个称为 C L S I D的子键,那里有一个缺省的字符串值,这就是字符串格式的 G U I D,如图
1 4 - 8所示。
图 14-8 注册表编辑器的界面
14.3 COM+运行期的变化由于提供了大量的能用于增加应用程序功能的新服务,C O M +也在 C O M运行期中包含了一些变化。
现在有两种类型的组件:配置的和非配置的。
注册表不再是组件元数据的主要信息存储库,而 C O M +类别将是主要的信息存储库。
446计计 ASP 3 高级编程 下载另外,还有许多低层的变化,将在下一章讨论。
14.3.1 配置的和非配置的组件在 C O M +中的非配置的组件是指在 Windows 9x或 Windows NT环境下创建的组件。它们不使用 C O M +类型的服务,并且有关它们的信息仅放在系统的注册表中。
C O M +类别 (注册数据库 )里描述了配置的组件,这些组件使用 C O M +的服务,例如事务处理支持。
配置的组件在 Windows 2000环境中向后兼容,它们只可用于进程内的服务,并作为一个组放置在一个 C O M + A p p l i c a t i o n中。如果已经使用了 M T S,一个 C O M + A p p l i c a t i o n与一个软件包基本相同,其作用是作为服务器的替代进程。如果没有使用 M T S,可以认为一个 C O M +
A p p l i c a t i o n是进程内 C O M服务器的一个集合,进程内是指程序运行在一个可执行文件
( d l l h o s t,e x e )的地址空间内。这些将在下一章里进一步解释。
14.3.2 COM+类别在 C O M +中需要花费很多时间通过 Component Services的图形用户界面 ( G U I )来管理配置的组件,如图 1 4 - 9所示。
图 14-9 Component Services的图形用户界面使用这个 G U I维护的所有与配置有关的组件,都存储在 C O M +类别 ( c a t a l o g )中,而不是像传统的 C O M组件那样存储注册表中。 C O M +类别提供到注册数据库 ( R e g D B )的一个接口,注册数据库是 C O M +用来激活 C O M +组件的优化数据库,与 C O M在 Windows 9x和 N T中使用的注册表相似。
如果你以前使用过 M T S,C O M +类别与 M T S类别基本上是同类的东西。
14.4 创建一个 ASP COM组件对于 C O M和 C O M +,我们做了相当多的阐述,下面看一下它们的作用,通过快速而简单地建立一个 D N A组件来说明为什么 V B是开发组件的一个重要工具。
在这个 D N A组件中,将是创建一个以数据为中心的中间层组件,能够从 SQL Server 7.0自带的 p u b s数据库检索一系列书名。这个组件让 A S P开发者不考虑数据库结构,使他们能够获取、增加和删除书名。通过后面几章的讨论,可知这一组件在以用户为中心的组件中是最适合的,但是现在我们将在 A S P中直接使用它。
第 1 4章 C O M,C O M +和 A S P计计 447下载在 A S P主页中,应该包含一个数据库内书名的视图,包括下列字段:
ID:由创建者赋予的唯一的标识符。
Ti t l e:书名。
Price:书的价格。
Notes:关于书的简单描述。
页面主界面如图 1 4 - 1 0所示。
图 14-10 页面主界面单击位于任何一个项目右边的 D e l e t e按钮,将删除这个书名。按了 D e l e t e按钮后,向用户呈现出一个简单的确认删除的界面。如果单击了 Add a new Ti t l e按钮,将运行 A d d,a s p页面,
允许用户加入一个新书名,如图 1 4 - 11所示。
图 1 4 - 11 Add.asp页面
448计计 ASP 3 高级编程 下载点击 Add Ti t l e按钮,将调用 A d d _ U p d a t e D B,a s p页面,使用组件实际完成插入工作。
14.4.1 组件的接口我们将使用一个十分简单的接口,提供如表 1 4 - 2所示的方法。
表 14-2 组件接口具有的方法及说明方 法 说 明
A d d Ti t l e 向数据库增加一个新书名
D e l e t e Ti t l e 根据所指定的书名 I D在数据库中删除一个书名
O p e n Ti t l e s 打开数据连接,对要读书名的数据集初始化
N e x t Ti t l e 从列表中返回下一个书名
C l o s e Ti t l e s 释放由组件使用的所有资源
I s E O F 指示书名列表是否结束这些方法遵守前面讲过的接口准则,所有名称都是描述性的,实现这个接口的类模块名称为 B o o k Ti t l e s,在 V B中将创建一个名为 _ B o o k Ti t l e s的接口。
我们要实现的这个组件具有状态,这就意味着任何一个方法的调用返回后都不会使对象失效或破坏。因此由一个方法 (例如 O p e n Ti t l e s )调用设置的成员变量也可以被另外的方法 (例如
N e x t Ti t l e )调用使用。在本例中,组件的状态就是数据库连接和记录集。
组件的实现只是简单地包装 A D O数据库连接和用于访问 p u b s数据库的 R e c o r d s e t对象。例如当调用 N e x t Ti t l e方法列举书名时,所做的仅仅是调用 R e c o r d s e t对象。
也可以采取其他途径。例如,在调用 O p e n Ti t l e s时列举所有的书名,并将书名的细节存储在内存中。这样做的优点是:较早释放了数据库连接,其他程序可以继续使用这个数据库连接,增加了系统的可扩展性。缺点是随着存储在内存中的信息的增加,增加了管理这些信息的难度,因而也增加了系统内存量的需求、组件的开发时间和复杂性。
14.4.2 创建组件启动 V B,创建一个新的 ActiveX DDL项目,把缺省类模块名改为 B o o k Ti t l e s,然后再把项目的名称改为 B o o k。由于我们使用 A D O,因此需要增加一个对 Microsoft ActiveX Data Object 2.5 Library
的引用,如图 1 4 - 1 2所示。
1,组件的方法下面检查需要增加到 B o o k Ti t l e s类模块的函数。
(1) OpenTi t l e s方法
O p e n Ti t l e s方法使用 SQLOLEDB OLE DB提供者建立一个与 SQL Server 7.0的连接,一旦建立这一连接,就创建一个包含所有书名列表的记录集,这个缺省的记录集使用 O p e n参数,这意味着不能更新此记录集。代码如下:
第 1 4章 C O M,C O M +和 A S P计计 449下载图 14-12 增加引用的界面
(2) CloseTi t l e s方法
C l o s e Ti t l e s方法释放在 O p e n Ti t l e s中打开的 R e c o r d s e t和 C o n n e c t i o n对象。显示了书名列表后,为了尽可能早地断开与数据库的连接,在书名列表显示完成后调用此方法。尽可能早地释放像数据库连接这样的有限资源总是一个好习惯,l i s t,a s p中的程序如下:
(3) NextTi t l e方法
N e x t Ti t l e方法用于返回当前书名的信息,并通过调用 M o v e N e x t方法前移记录集的指针。
代码如下:
450计计 ASP 3 高级编程 下载注意,所有的参数都定义为 Va r i a n t型,因为这个函数接收的所有参数都定义在 L i s t,a s p页面中,其代码如下:
A S P解释器 (在本例中,更确切的说是 V B S c r i p t引擎 )把这些参数 ( s t r I D等 )定义为 Va r i a n t。
因为 N e x t Ti t l e参数是 B y R e f参数,是一个指向含有传递到函数的 Va r i a n t型参数的内存指针,因此,通过函数可以直接更新其值。
如果企图使用 S t r i n g型和 C u r r e n c y型来定义 N e x t Ti t l e函数,就会得到如图 1 4 - 1 3所示的错误。
图 14-13 显示错误信息的界面第 1 4章 C O M,C O M +和 A S P计计 451下载产生这个错误的原因很简单,只是因为一个 B y R e y参数必须接收一个正确类型的指针。
如果把参数定义为 B y Va l型,那么参数就是由值传递,活动脚本引擎试着转换值的类型。当然,
如果不可能转换,还是会出现同样的错误。
许多商业组件在编写时没考虑到 A S P有这种类型的问题,所以经常需要为这些组件编写一些小的包装程序。
(4) IsEOF方法
I s E O F函数指示是否还有更多的书名,直接反映记录集的 E O F属性,当枚举所有书名时,
用它来确定是否还有书名。代码如下:
(5) AddTi t l e方法
A d d Ti t l e方法与一个数据库建立连接,并且打开一个动态的记录集,此记录集可以更新。
代码如下:
在这个程序中,没有重新利用可能已经由 O p e n Ti t l e s打开的连接,这样做是为了使程序简单。即不必检查数据库是否已经打开,这里总是将其打开,当此子程序退出时会自动释放数据库连接和记录集,因此,不必将每个对象设置为 N o t h i n g。
读者会注意到,在这个程序中,没有使用 Va r i a n t型参数,这样做是为了证明在原型中不一定非使用 Va r i a n t型,只要不把在 A S P页面变量表中定义的变量传送给函数。下面的例子说明了在 Add_UpdateDB.asp ASP页面中如何不使用页面中定义的变量而调用函数:
因为参数没有被定义为变量,A S P解释器就会友好地执行必要的转换,为什么它不对页
452计计 ASP 3 高级编程 下载面定义的变量也做这种自动转换呢?这确实很奇怪,只有寄希望于将来的脚本引擎版本会支持这种转换。
(6) DeleteTi t l e方法这个方法用来删除书名和数据库中 R o y s c h e d,S a l e s和 Ti t l e A u t h o r表中有关的行,这很必要,
因为 p u b s数据库结构由 SQL Server提供。当建立一个书名时,我们没有在所有表中增加行,但是有可能删除不是通过这个 A S P U I创建的书名,为了安全起见,必须这样做。代码如下:
一旦把这些函数复制到了 V B类模块,编译这个类模块就能创建一个含有此组件的 C O M服务器,这样做以后,继续我们的工作,编写使用这个组件的 A S P页面。这与以前编写其他页面没什么区别,因此不再赘述。
2,示例的 A S P页面总共有 4个 A S P页面,它们是:
List.asp:使用组件显示书名的完整列表。
Add.asp:提供一个增加新书名的详细信息的简单界面。
Add_UpDateDB.asp:使用组件和 A d d,a s p传送过来的信息增加一个新的书名到数据库。
Delete.asp:使用组件在数据库中删除一个书名。
(1) List.asp页面这个列表页面是一个非常典型的 A S P页面,它检索相应的值并显示为一个列表,唯一值得注意的是这个页面并不使用 A D D,而是用组件的一个实例:
这里不使用 A D O的直接好处是:开发者使用组件,不必担心连接的细节、表的名称和建立 S Q L查询等问题。
一旦打开列表,程序的主要部分只是简单地枚举每个书名,代码如下:
第 1 4章 C O M,C O M +和 A S P计计 453下载返回的数据用来创建一个有多行记录的表:
注意,这里用一种简单的窗体来为每行提供一个 D e l e t e按钮,在下面将看到,按下这个按钮将调用 D e l e t e,a s p页面,书名代码用它的 I D参数传送。
为完整起见,下面给出整个页面的代码:
454计计 ASP 3 高级编程 下载
(2) Delete.asp页面单击 D e l e t e按钮去删除一个书名时,调用这个页面,通过下面这几行命令删除书名。
这里显示了整个页面,除了提供一个返回主列表的按钮,没有其他控件。返回主列表后,
可能需要刷新画面以查看该书名已从列表中删除了。
整个 A S P页面代码如下:
第 1 4章 C O M,C O M +和 A S P计计 455下载
(3) Add.asp页面
A d d,a s p页面是一个典型的 A S P窗体捕获页面,它让用户输入书名 I D、名称、注释和价格,
然后把这些细节传送给 A d d _ U p d a t e D B,a s p页面。代码如下:
456计计 ASP 3 高级编程 下载
(4) Add_UpdateDB.asp页面根据 A d d,a s p传送来的参数,这个页面用下面两行程序增加了一个新书名:
再次看到,通过使用组件,开发者的工作变得比较简单。整个页面代码如下:
14.5 小结
C O M +是 Windows 2000中提供的一种功能非常强大的技术,除非你喜欢违背潮流,否则就应该在产品中使用该技术,这样使客户能够通过你所创建的组件和接口来访问你开发的功能。
正如你所见,在 V B中创建组件很简单,真正所需要注意的是编写代码实现我们想让客户使用的功能。这些客户可以是 A S P页面、任何其他能使用 C O M的工具或编程语言。
C O M能满足所有语言的需要,由于有些语言的功能远比另一些语言强大,因此必须了解那些可能使用你的组件的客户的限制,例如对于 A S P客户,只有当一个函数的参数定义为
Va r i a n t时,在 A S P页面中定义的变量才能传送给这个函数,我们也看到 A S P实际上只能使用组件的缺省接口。
在后面几章,将更深入研究 C O M +对运行期的进一步改进,并且讨论如何使用一些 C O M +
服务。
第 1 4章 C O M,C O M +和 A S P计计 457下载