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 = 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 = iinterface;

idispatch = interface(iunknown)

[''''{00020400-0000-0000-c000-000000000046}'''']

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),前面是com对象的基类,后面这个接口的声明则是: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中符合ole 32-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)

的选项。

实例模式决定客户端请求后,com对象如何创建实例:

internal:供com对象内部使用,不会响应客户端请求,只能通过com对象内部的其他

方法来建立;

single instance:不论当前系统内部是否存在相同com对象,都会建立一个新的程序

及独立的对象实例;

mulitple instance:如果有多个相同的com对象,只会建立一个程序,多个com对象

的实例共享公共代码,并拥有自己的数据空间。

single/ mulitple instance有各自的优点,mulitple虽然节省了内存但更加费时。即single模式需要更多的内存资源,而mulitple模式需要更

多的cpu资源,且single的实例响应请求的负荷较为平均。该参数应根据服务器的实际需求来考虑。

线程模式有五种:

single:仅单线程,处理简单,吞吐量最低;

apartment:com程序多线程,com对象处理请求单线程;

free:一个com对象的多个实例可以同时运行。吞吐量提高的同时,也要求对com对象

进行必要的保护,以避免多个实例冲突;

both:同时支持aartment和free两种线程模式。

neutral:只能在com+下使用。

虽然free和both的效率得到提高,但是要求较高的技巧以避免冲突(这是很不容易调试的),所以一般建议使用delphi的缺省方式。

类型库编辑器(type library)

假设我们建立一个叫做tsample的类和isample的接口(如图),然后使用类型库编辑器创建一个方法getcominfo(在右边树部分点击右键弹出

菜单选择new-method或者点击上方按钮),并于左边parameters页面建立两个参数(valint : integer , valstr : string),返回值为bstr

。如图:

可以看到,除了常用类型外,参数和返回值还可以支持很多指针、ole对象、接口类型。建立普通的com对象,其returen type是可以任意的,

这是和dcom的一个区别。

双击modifier列弹出窗口,可以选择参数的方式:in、out分别对应const、out定义,选择has default value可设置参数缺省值。

delphi生成代码详解

点击刷新按钮刷新后,上面类型库编辑器对应的delphi自动生成的代码如下:

unit ucom;

{$warn symbol_platform off}

interface

uses

windows, activex, classes, comobj, pcom_tlb, stdvcl;

type

tsample = class(ttypedcomobject, isample)

protected

function getcominfo(valint: sysint; const valstr: widestring): widestring;

stdcall;

end;

implementation

uses comserv;

function tsample.getcominfo(valint: sysint;const valstr: widestring): widestring;

begin

end;

initialization

ttypedcomobjectfactory.create(comserver, tsample, class_sample,

cimultiinstance, tmapartment);

end.

引用单元

有三个特殊的单元被引用:comobj,comserv和pcom_tlb。comobj里定义了com接口类的父类ttypedcomobject和类工厂类

ttypedcomobjectfactory(分别从tcomobject和tcomobjectfactory继承,早期版本如delphi4建立的com,就直接从tcomobject继承和使用

tcomobjectfactory了); comserv单元里面定义了全局变量comserver: tcomserver,它是从tcomserverobject继承的,关于这个变量的作用

,后面将会提到。

这几个类都是delphi实现com对象的比较基础的类,tcomobject(com对象类)和tcomobjectfactory(com对象类工厂类)本身就是iunknown的

两个实现类,包含了一个com对象的建立、查询、登记、注册等方面的代码。tcomserverobject则用来注册一个com对象的服务信息。

接口定义说明

再看接口类定义tsample = class(ttypedcomobject, isample)。到这里,已经可以通过涉及的父类的作用大致猜测到tsample是如何创建并注

册为一个标准的com对象的了。那么接口isample又是怎么来的呢?pcom_tlb单元是系统自动建立的,其名称加上了_tlb,它里面包含了isample

= interface(iunknown)的接口定义。前面提到过,所有com接口都是从iunknown继承的。

在这个单元里我们还可以看到三种id(类型库id、iid及com注册所必须的clsid)的定义:libid_pcom,iid_isample和class_sample。关键是

这时接口本身仅仅只有定义代码而没有任何的实现代码,那接口创建又是在何处执行的?_tlb单元里还有这样的代码:

cosample = class

class function create: isample;

class function createremote(const machinename: string): isample;

end;

class function cosample.create: isample;

begin

result := createcomobject(class_sample) as isample;

end;

class function cosample.createremote(const machinename: string): isample;

begin

result := createremotecomobject(machinename, class_sample) as isample;

end;

由delphi的向导和类型编辑器帮助生成的接口定义代码,都会绑定一个“co+类名”的类,它实现了创建接口实例的代码。createcomobject和

createremotecomobject函数在comobj单元定义,它们就是使用clsid创建com/dcom对象的函数!

初始化:注册com对象的类工厂

类工厂负责接口类的统一管理——实际上是由支持iclassfactory接口的对象来管理的。类工厂类的继承关系如下:

iclassfactory = interface(iunknown)

tcomobjectfactory=class(tobject,iunknown,iclassfactory,iclassfactory2) ttypedcomobjectfactory = class(tcomobjectfactory)

我们知道了接口isample是怎样被创建的,接口实现类tsample又是如何被定义为com对象的实现类。现在解释它是怎么被注册,以及何时创建的

。这一切的小把戏都在最后initialization的部分,这里有一条类工厂建立的语句。

initialization是delphi用于初始化的特殊部分,此部分的代码将在整个程序启动的时候首先执行。回顾前面的内容并观察一下

ttypedcomobjectfactory的参数:comserver是用于注册/撤消注册com服务的对象,tsample是接口实现类,class_sample是接口唯一对应的

guid,cimultiinstance是实例模式,tmapartment是线程模式。一个com对象应该具备的特征和要素都包含在了里面!

那么com对象的管理又是怎么实现的呢?在comobj单元里面可以见到一条定义function comclassmanager: tcomclassmanager;

这里tcomclassmanager顾名思义就是com对象的管理类。任何一个祖先类为tcomobjectfactory的对象被建立时,其create里面会执行这样一句

:

comclassmanager.addobjectfactory(self);

addobjectfactory方法的原形为procedure tcomclassmanager.addobjectfactory(factory: tcomobjectfactory);相对应的还有

removeobjectfactory方法。具体的代码我就不贴出来了,相信大家已经猜测到了它的作用——将当前对象(self)加入到comclassmanager管

理的对象链(ffactorylist)中。

封装的秘密

读者应该还有最后一个疑问:假如服务器通过类工厂的注册以及guid确定一个com对象,那当客户端调用的时候,服务器是如何启动包含com对

象的程序的呢?

当你建立activex library的工程的时候,将发现一个和普通dll模版不同的地方——它定义了四个输出例程:

exports

dllgetclassobject,

dllcanunloadnow,

dllregisterserver,

dllunregisterserver;

这四个例程并不是我们编写的,它们都在comserv单元例实现。单元还定义了类tcomserver,并且在初始化部分创建了类的实例,即前面提到过

的全局变量comserver。

例程dllgetclassobject通过clsid得到支持iclassfactory接口的对象;例程dllcanunloadnow判断dll是否可从内存卸载;dllregisterserver

和dllunregisterserver负责dll的注册和解除注册,其具体的功能由comserver实现。

接口类的具体实现

好了,现在自动生成代码的来龙去脉已经解释清楚了,下一步就是由我们来添加接口方法的实现代码。在function tsample.getcominfo的部分

添加如下代码。我写的例子很简单,仅仅是根据传递的参数组织一条字符串并返回。以此证明接口正确调用并执行了该代码:

function tsample.getcominfo(valint: sysint;const valstr: widestring): widestring;

const

server1 = 1;server2 = 2;server3 = 3;

var

s : string;

begin

s := ''''this is com server : '''';

case valint of

server1: s := s + ''''server1'''';

server2: s := s + ''''server2'''';

server3: s := s + ''''server3'''';

end;

s := s + #13 + #10 + ''''execute client is '''' + valstr;

result := s;

end;

注册、创建com对象及调用接口

随便建立一个application用于测试上面的com。必要的代码很少,创建一个接口的实例然后执行它的方法。当然我们得先行注册com,否则调用

根据clsid找不接口的话,将报告“无法向注册表写入项”。如果接口定义不一致,则会报告“interface not supported”。

编译上面的这个com工程,然后选择菜单“run – register activex server”,或者通过windows下system/system32目录中的regsvr32.exe程

序注册编译好的dll文件。regsvr32的具体参数可以通过regsvr32/?来获得。对于进程外(exe型)的com对象,执行一次应用程序就注册了。

提示dll注册成功后,就应该可以正确执行下列客户端程序了:

uses comobj, pcom_tlb;

procedure ttest.button1click(sender: tobject);

var

comsvr : isample;

retstr : string;

begin

comsvr := createcomobject(class_sample) as isample;

if comsvr 关注我收藏该文与我联系


======================================================
在最后,我邀请大家参加新浪APP,就是新浪免费送大家的一个空间,支持PHP+MySql,免费二级域名,免费域名绑定 这个是我邀请的地址,您通过这个链接注册即为我的好友,并获赠云豆500个,价值5元哦!短网址是http://t.cn/SXOiLh我创建的小站每天访客已经达到2000+了,每天挂广告赚50+元哦,呵呵,饭钱不愁了,\(^o^)/
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值