《Delphi编写COM简介(转)》由会员分享,可在线阅读,更多相关《Delphi编写COM简介(转)(40页珍藏版)》请在金锄头文库上搜索。
1、 软件重用是业界追求的目标,人们一直希望能够像搭积木一样随意“ 装配”应用程序,组件对象就充当了积木的角色。所谓组件对象,实际上就是预定义好的、能完成一定功能的服务或接口。问题是,这些组件对象如何与应用程序、如何与其他组件对象共存并相互通信和交互?这就需要制定? 个规范,让这些组件对象按统一的标准方式工作。COM 是个二进制规范,它与源代码无关。这样,即使 COM 对象由不同的编程语言创建,运行在不同的进程空间和不同的操作系统平台,这些对象也能相互通信。COM 既是规范,也是实现,它以 COM 库(OLE32.dll 和贴OLEAut32.dll)的形式提供了访问 COM 对象核心功能的标准接
2、口以及一组 API函数,这些 API 函数用于创建和管理 COM 对象。COM 本质上仍然是客户服务器模式。客户(通常是应用程序)请求创建 COM 对象并通过 COM 对象的接口操纵 COM 对象。服务器根据客户的请求创建并管理 COM 对象。客户和服务器这两种角色并不是绝对的。 组件对象与一般意义上的对象既相似也有区别。一般意义上的对象是一种把数据和操纵数据的方法封装在一起的数据类型的实例,而组件对象则使用接口(Interface) 而不是方法来描述自己并提供服务。所谓接口,其精确定义是“基于对象的一组语义上相关的功能”,实际上是一个纯虚类,真正实现接口的是接口对象)(Interface O
3、bject)。一个 COM 对象可以只有一个接口,例如 Wndows 9598 外壳扩展;也可以有许多接口,例如 ActiveX 控件一般就有多个接口,客户可以从很多方面来操纵 ActiveX 控件。接口是客户与服务器通信的唯一途径。如果一个组件对象有多个接口,则通过一个接口不能直接访问其他接口。但是,COM 允许客户调用 COM 库中的 QueryInterface()去查询组件对象所支持的其他接口。从这个意义上讲,组件对象有点像接口对象的经纪人。在调用 QueryInterface()后,如果组件对象正好支持要查询的接口,则QueryInterface()将返回该接口的指针。如果组件对象不
4、支持该接口,则QueryInterface()将返回一个出错信息。所以,QueryInterface()是很有用的,它可以动态了解组件对象所支持的接口。接口是团向对象编程思想的一种体现,它隐藏了 COM 对象实现服务的细节。COM 对象可以完全独立于访问它的客户,只要接口本身保持不变即可。如果需要更新接口,则可以重新定义一个新的接口,对于使用老接口的客户来说,代码得到了最大程度的保护。Delphi 通过向导可以非常迅速和方便的直接建立实现 COM 对象的代码,但是整个 COM 实现的过程被完全的封装,甚至没有 VCL 那么结构清晰可见。一个没有 C+下 COM 开发经验甚至没有接触过 COM
5、开发的 Delphi 程序员,也能够很容易的按照教程设计一个接口,但是,恐怕深入一想,连生成的代码代表何种意义,哪些能够定制都不清楚。前几期 “DELPHI 下的 COM 编程技术”一文已经初步介绍了 COM 的一些基本概念,我则想谈一些个人的理解,希望能给对 Delphi 下 COM 编程有疑惑的朋友带来帮助。COM (组件对象模型 Component Object Model)是一个很庞大的体系。简单来说,COM 定义了一组 API 与一个二进制的标准,让来自不同平台、不同开发语言的独立对象之间进行通信。COM 对象只有方法和属性,并包含一个或多个接口。这些接口实现了 COM 对象的功能,
6、通过调用注册的 COM 对象的接口,能够在不同平台间传递数据。COM 光标准和细节就可以出几本大书。这里避重就轻,仅仅初步的解释Delphi 如何进行 COM 的封装及实现。对于上述 COM 技术经验不足的 Delphi程序开发者来说,Delphi 通过模版生成的代码就像是给你一幅抽象画照着画一样,画出来了却不一定知道画的究竟是什么,也不知该如何下手画自己的东西。本文能够帮助你解决这类疑惑。再次讲解一些概念“DELPHI 下的 COM 编程技术”一文已经介绍了不少 COM 的概念,比如GUID、CLSID、IID,引用计数, IUnKnown 接口等,下面再补充一些相关内容:COM 与 DCO
7、M、COM+、OLE、ActiveX 的关系DCOM(分布式 COM)提供一种网络上访问其他机器的手段,是 COM 的网络化扩展,可以远程创建及调用。COM+是 Microsoft 对 COM 进行了重要的更新后推出的技术,但它不简单等于 COM 的升级,COM+是向后兼容的,但在某些程度上具有和 COM 不同的特性,比如无状态的、事务控制、安全控制等等。以前的 OLE 是用来描述建立在 COM 体系结构基础上的一整套技术,现在 OLE仅仅是指与对象连接及嵌入有关的技术;ActiveX 则用来描述建立在 COM 基础上的非 COM 技术,它的重要内容是自动化(Automation),自动化允许
8、一个应用程序(称为自动化控制器)操纵另一个应用程序或库(称为自动化服务器)的对象,或者把应用程序元素暴露出来。由此可见 COM 与以上的几种技术的关系,并且它们都是为了让对象能够跨开发工具跨平台甚至跨网络的被使用。Delphi 下的接口Delphi 中的接口概念类似 C+中的纯虚类,又由于 Delphi 的类是单继承模式(C+是多继承的),即一个类只能有一个父类。接口在某种程度上可以实现多继承。接口类的声明与一般类声明的不同是,它可以象多重继承那样,类名 = class (接口类 1,接口类 2 ),然后被声明的接口类则重载继承类的虚方法,来实现接口的功能。以下是 IInterface、IUn
9、known、IDispatch 的声明,大家看出这几个重要接口之间是什么样的联系了吗?任何一个 COM 对象的接口,最终都是从 IUnknown继承的,而 Automation 对象,则还要包含 IDispatch,后面 DCOM 部分我们会看到它的作用。IInterface = interface00000000-0000-0000-C000-000000000046function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;function _AddRef: Integer; stdcall;function _
10、Release: Integer; stdcall;end;IUnknown = IInterface;IDispatch = interface(IUnknown)00020400-0000-0000-C000-000000000046function GetTypeInfoCount(out Count: Integer): HResult; stdcall;function GetTypeInfo(Index, LocaleID: Integer; out TypeInfo): HResult; stdcall;function GetIDsOfNames(const IID: TGUI
11、D; Names: Pointer;NameCount, LocaleID: Integer; DispIDs: Pointer): HResult; stdcall;function Invoke(DispID: Integer; const IID: TGUID; LocaleID: Integer;Flags: Word; var Params; VarResult, ExcepInfo, ArgErr: Pointer): HResult; stdcall;end;对照“DELPHI 下的 COM 编程技术”一文,可以明白 IInterface 中的定义,即接口查询及引用记数,这也是访
12、问和调用一个接口所必须的。QueryInterface 可以得到接口句柄,而 AddRef 与 Release 则负责登记调用次数。COM 和接口的关系又是什么呢?COM 通过接口进行组件、应用程序、客户和服务器之间的通信。COM 对象需要注册,而一个 GUID 则是作为识别接口的唯一名字。假如你创建了一个 COM 对象,它的声明类似 Txxxx= class(TComObject, Ixxxx),前面是 COM 对象的基类,后面这个接口的声明则是: Ixxxx = interface(IUnknown) 。所以说 IUnknown 是 Delphi 中 COM 对象接口类的祖先。到这一步,我
13、想大家对接口类的来历已经有初步了解了。聚合接口是 COM 实现的基础,接口也是可继承的,但是接口并没有实现自己,仅仅只有声明。那么怎么使 COM 对象对接口的实现得到重用呢?答案就是聚合。聚合就是一个包含对象(外部对象)创建一个被包含对象(内部对象),这样内部对象的接口就暴露给外部对象。简单来说,COM 对象被注册后,可以找到并调用接口。但接口不是仅仅有个定义吗,它必然通过某种方式找到这个定义的实现,即接口的“实现类”的方法,这样才最终通过外部的接口转入进行具体的操作,并通过接口返回执行结果。进程内与进程外(In-Process, Out-Process)进程内的接口的实现基础是一个 DLL,
14、进程外的接口则是建立在应用程序(EXE)上的。通常我们建立进程外接口的目的主要是为了方便调试(跟踪DLL 是件很麻烦的事),然后在将代码改为进程内发布。因为进程内比进程外的执行效率会高一些。(也就是先建立进程内的接口,再将其改为进程内发布。)COM 对象创建在服务器的进程空间。如果是 EXE 型服务器,那么服务器和客户端不在同一进程;如果是 DLL 型服务器,则服务器和客户端就是一个进程。所以进程内还能节省内存空间,并且减少创建实例的时间。StdCall 与 SafeCallDelphi 生成的 COM 接口默认的方法函数调用方式是 stdcall 而不是缺省的Register。这是为了保证不
15、同语言编译器的接口兼容。双重接口(在后面讲解自动化时会提到双重接口)中则默认的是 SafeCall。它的意义除了按 SafeCall 约定方式调用外,还将封装方法以便向调用者返回HResult 值。SafeCall 的好处是能够捕获所有异常,即使是方法中未被代码处理的异常,也可以被外套处理并通过 HResult 返回给调用者。WideString 等一些有差异的类型接口定义中缺省的字符参数或返回值将不再是 String 而是WideString。WideString 是 Delphi 中符合 OLE 32-bit 版本的 Unicode 类型,当是字符时,WideString 与 String
16、 几乎等同,当处理 Unicode 字符时,则会有很大差别。联想到 COM 本身是为了跨平台使用,可以很容易的理解为什么数据通信时需要使用 WideString 类型。同样的道理,integer 类型将变成 SYSINT 或者 Int64、SmallInt 或者 Shortint,这些细微的变化都是为了符合规范。通过向导生成基础代码打开创建新工程向导(菜单“File-New-Other”或“New Items 按钮”),选择ActiveX 页。先建立一个 ActiveX Library。编译后即是个 DLL 文件(进程内)。然后在同样的页面再建立一个 COM Object。接着你将看到如下向导,除了填写类名外(接口名会自动根据类名填充),创建有实例模式(Instancing)和线程模式(Threading Model )的选项。实例模式-决定客户端请求后,COM 对象如何创建实例:Internal:供 COM 对象内部使用,不会响应客户端请求,只