DELPHI下的COM编程
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
Delphi下的COM编程
作者:岑心
Delphi通过向导可以非常迅速和方便的直接建立实现COM对象的代码,但是整个COM实现的过程被完全的封装,甚至没有VCL那么结构清晰可见。
一个没有C下COM开发经验甚至没有接触过COM开发的Delphi程序员,也能够很容易的按照教程设计一个接口,但是,恐怕深入一想,连生成的代码代表何种意义,哪些能够定制都不清楚。前几期“DELPHI下的COM编程技术”一文已经初步介绍了COM的一些基本概念,我则想谈一些个人的理解,希望能给对Delphi下COM编程有疑惑的朋友带来帮助。COM(组件对象模型Component Object Model)是一个很庞大的体系。简单来说,COM定义了一组API与一个二进制的标准,让来自不同平台、不同开发语言的独立对象之间进行通信。COM对象只有方法和属性,并包含一个或多个接口。这些接口实现了COM对象的功能,通过调用注册的COM对象的接口,能够在不同平台间传递数据。
COM光标准和细节就可以出几本大书。这里避重就轻,仅仅初步的解释Delphi如何进行COM的封装及实现。对于上述COM技术经验不足的Delphi程序开发者来说,Delphi通过模版生成的代码就像是给你一幅抽象画照着画一样,画出来了却不一定知道画的究竟是什么,也不知该如何下手画,根据专家观察,这样的理论和现象都是值得各位站长深思的,所以希望大家多做研究学习,争取总结出更多更好的经验!自己的东西。本文能够帮助你解决这类疑惑。
再次讲解一些概念
“DELPHI下的COM编程技术”一文已经介绍了不少COM的概念,比如GUID、CLSID、IID,引用计数,IUnKnown接口等,下面再补充一些相关内容
COM与DCOM、COM、OLE、ActiveX的关系:
DCOM(分布式COM)提供一种网络上访问其他机器的手段,是COM的网络化扩展,可以远程创建及调用。COM是Microsoft对COM进行了重要的更新后推出的技术,但它不简单等于COM的升级,COM是向后兼容的,但在某些程度上具有和COM不同的特性,比如无状态的、事务控制、安全控制等等。以前的OLE是用来描述建立在COM体系结构基础上的一整套技术,现在OLE仅仅是指与对象连接及嵌入有关的技术;ActiveX则用来描述建立在COM基础上的非COM技术,它的重要内容是自动化(Automation),自动化允许一个应用程序(称为自动化控制器)操纵另一个应用程序或库(称为自动化服务器)的对象,或者把应用程序元素暴露出来。由此可见COM与以上的几种技术的关系,并且它们都是为了让对象能够跨开发工具跨平台甚至跨网络的被使用。
Delphi下的接口
Delphi中的接口概念类似C++中的纯虚类,又由于Delphi的类是单继承模式(C++是多继承的),即一个类只能有一个父类。接口在某种程度上可以实现多继承。
接口类的声明与一般类声明的不同是,它可以象多重继承那样,类名=class(接口类1,接口类2…),然后被声明的接口类则重载继承类的虚方法,来实现接口的功能。
以下是IInterface、IUnknown、IDispatch的声明,大家看出这几个重要接口之间是什么样的联系了吗?任何一个COM对象的接口,最终都是从IUnknown继承的。而Automation对象,则还要包含IDispatch,后面DCOM部分我们会看到它的作用。
//IInterface
IInterface=interface//根据专家观察,这样的理论和现象都是值得各位站长深思的,所以希望大家多做研究
学习,争取总结出更多更好的经验!
[''''{00000000-0000-0000-C000-000000000046}'''']
function QueryInterface(const IID:TGUID;out Obj):HResult;stdcall;
function_AddRef:Integer;stdcall;
function_Release:Integer;stdcall;
end;
//IUnknown
IUnknown=IInterface;
//IDispatch
IDispatch=interface(IUnknown)
[''''{00020400-0000-0000-C000-000000000046}'''']
//IDispatch接口方法定义
function GetTypeInfoCount(out Count:Integer):HResult;stdcall;
function GetTypeInfo(Index,LocaleID:Integer;out TypeInfo):HResult;stdcall;
function GetIDsOfNames(const IID:TGUID;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中的定义,即接口查询及引用记数,这也是访问和调用一个接口所必须的。QueryInterface可以得到接口句柄,而AddRef与Release则负责登记调用次数。
COM和接口的关系又是什么呢?COM通过接口进行组件、应用程序、客户和服务器之间的通信。COM对象需要注册,而一个GUID则是作为识别接口的唯一名字。假如你创建了一个COM对象,它的声明类似Txxxx=class(TComObject,Ixxxx),前面的TComObject是COM对象的基类,后面的Ixxxx接口的声明则是:Ixxxx=interface(IUnknown)。所以说IUnknown 是Delphi中COM对象接口类的祖先。到这一步,我想大家对接口类的来历已经有初步了解了。
接口是COM实现的基础,接口也是可继承的,但是接口并没有实现自己,仅仅只有声明。那么怎么使COM对象对接口的实现得到重用呢?答案就是聚合。聚合就是一个包含对象(外部对象)创建一个被包含对象(内部对象),这样内部对象的接口就暴露给外部对象。简单来说,COM对象被注册后,可以找到并调用接口。但接口不是仅仅有个定义吗,它必然通过某种方式找到这个定义的实现,即接口的“实现类”的方法,这样才最终通过外部的接口转入进行具体的操作,并通过接口返回执行结果。
进程内与进程外(In-Process,Out-Process)
进程内的接口的实现基础是一个DLL,进程外的接口则是建立在应用程序(EXE)上的。通常我们建立进程外接口的目的主要是为了方便调试(跟踪DLL是件很麻烦的事),然后在将代码改为进程内发布。因为进程内比进程外的执行效率会高一些。COM对象创建在服务器的进程空间。如果是EXE型服务器,那么服务器和客户端不在同一进程;如果是DLL型服务器,则服务器和客户端就是一个进程。所以进程内还能节省内存空间,并且减少创建实例的时间。
StdCall与SafeCall
Delphi生成的COM接口默认的方法函数调用方式是stdcall而不是缺省的Register。这是为了保证不同语言编译器的接口兼容。双重接口(在后面讲解自动化时会提到双重接口)中则默认的是SafeCall。它的意义除了按SafeCall约定方式调用外,还将封装方法以便向调用者返回HResult值。SafeCall的好处是能够捕获所有异常,即使是方法中未被代码处理的异常,也可以被外套处理并通过HResult返回给调用者。
WideString等一些有差异的类型
接口定义中缺省的字符参数或返回值将不再是String而是WideString。WideString是Delphi中符合OLE32-bit 版本的Unicode类型,当是字符时,WideString与String几乎等同,当处理Unicode字符时,则会有很大差别。联想到COM本身是为了跨平台使用,可以很容易的理解为什么数据通信时需要使用WideString类型。同样的道理,integer 类型将变成SYSINT或者Int64、SmallInt或者Shortint,这些细微的变化都是为了符合规范。
通过向导生成基础代码,打开创建新工程向导(菜单“File-New-Other”或“New Items按钮”),选择ActiveX 页。先建立一个ActiveX Library。编译后即是个DLL文件(进程内)。然后在同样的页面再建立一个COM Object。实例模式与线程模式接着你将看到如下向导,除了填写类名外(接口名会自动根据类名填充),还有实例创建方式(Instancing)和线程模式(Threading Model)的选项。