Delphi COM编程介绍(转)

转自http://www.cnblogs.com/abchjb/articles/461725.html

        软件重用是业界追求的目标,人们一直希望能够像搭积木一样随意“装配”应用程序,组件对象就充当了积木的角色。所谓组件对象,实际上就是预定义好的、能完成一定功能的服务或接口。问题是,这些组件对象如何与应用程序、如何与其他组件对象共存并相互通信和交互?这就需要制定?个规范,让这些组件对象按统一的标准方式工作。
COM是个二进制规范,它与源代码无关。这样,即使COM对象由不同的编程语言创建,运行在不同的进程空间和不同的操作系统平台,这些对象也能相互通信。COM既是规范,也是实现,它以COM库(OLE32.dll和贴OLEAut32.dll)的形式提供了访问COM对象核心功能的标准接口以及一组API函数,这些API函数用于创建和管理COM对象。COM本质上仍然是客户服务器模式。客户(通常是应用程序)请求创建COM对象并通过COM对象的接口操纵COM对象。服务器根据客户的请求创建并管理COM对象。客户和服务器这两种角色并不是绝对的。
组件对象与一般意义上的对象既相似也有区别。一般意义上的对象是一种把数据和操纵数据的方法封装在一起的数据类型的实例,而组件对象则使用接口(Interface)而不是方法来描述自己并提供服务。所谓接口,其精确定义是“基于对象的一组语义上相关的功能”,实际上是一个纯虚类,真正实现接口的是接口对象)(Interface Object)。一个COM对象可以只有一个接口,例如Wndows 95/98外壳扩展;也可以有许多接口,例如ActiveX控件一般就有多个接口,客户可以从很多方面来操纵ActiveX控件。接口是客户与服务器通信的唯一途径。如果一个组件对象有多个接口,则通过一个接口不能直接访问其他接口。但是,COM允许客户调用COM库中的QueryInterface()去查询组件对象所支持的其他接口。从这个意义上讲,组件对象有点像接口对象的经纪人。
在调用QueryInterface()后,如果组件对象正好支持要查询的接口,则QueryInterface()将返回该接口的指针。如果组件对象不支持该接口,则QueryInterface()将返回一个出错信息。
所以,QueryInterface()是很有用的,它可以动态了解组件对象所支持的接口。接口是团向对象编程思想的一种体现,它隐藏了COM对象实现服务的细节。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继承[加了Typed],早期版本如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对象的实现类。现在解释它是怎么被注册,以及何时创建的。

这一切的小把戏都在最后Delphi单元里的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 <> nil then
  begin
    retStr := COMSvr.GetCOMInfo(2, ' client 2 ');
    showmessage(retStr);
    COMSvr := nil;
  end else
    showmessage(' ?¡§??¡§2??????¡ì2?3¡§|1| ');
end;

最终值是从当前程序外的一个“接口”返回的,我们甚至可以不知道这个接口的实现!第一次接触COM的人,成功执行此程序并弹出对话框后,

也许会体会到一种技术如斯奇妙的感觉,因为你仅仅调用了“接口”,就可以完成你猜测中的东西。

创建一个分布式DCOM(自动化接口)

IDispatch

在delphi6之前的版本中,所有接口的祖先都是IUnknown,后来为了避免跨平台操作中接口概念的模糊,又引入了IInterface接口。

使用向导生成DCOM的步骤和COM几乎一致。而生成的代码仅将接口类的父类换为TAutoObject,类工厂类换为TAutoObjectFactory。这其实没有

太大的不同,因为TAutoObject等于是一个标准COM外加IDispatch接口,而TAutoObjectFactory是从TTypedComObjectFactory直接继承的:

TAutoObject = class(TTypedComObject, IDispatch)

TAutoObjectFactory = class(TTypedComObjectFactory)

自动化服务器支持双重接口,而且必须实现IDispatch。因讨论范畴限制,本文只能简单提出,IDispatch是DCOM和COM技术实现上的一个重要区

别。打开_TLB.pas单元,可以找到Ixxx = interface(IDispatch)和Ixxx = dispinterface的定义,这在前面COM的例子里面是没有的。

创建过程中的差异

使用类型库编辑器的时候,有两处和COM不同的地方。首先Return Type必须选择HRESULT,否则会提示错误,这是为了满足双重接口的需要。当

Return Type选择HRESULT后,你会发现方法定义将变成procedure(过程)而不是预想中的function(函数)。

怎么才能让方法有返回值呢?还需要在Parameters最后多添加一个参数,然后将该参数改名与方法名一致,设置参数类型为指针(如果找不到

某种类型的指针类型,可以直接在类型后面加*,如图,BSTR*是BSTR的指针类型)。最后在Modifier列设置Parameter Flags为RetVal,同时

Out将被自动选中,而In将被取消。

刷新后,得到下列代码。添加方法的具体实现,大功告成:

             TSampleAuto = class(TAutoObject, ISampleAuto)

             protected

               function GetAutoSerInfo(ValInt: SYSINT;const ValStr: WideString): WideString; safecall;

             end;

远程接口调用

远程接口的调用需要使用CreateRemoteComObject函数,其它如接口的声明等等与COM接口调用相同。CreateRemoteComObject函数比

CreateComObject 多了一个参数,即服务器的计算机名称,这样就比COM多出了远程调用的查询能力。前面“接口定义说明”一节的代码可以对

照CreateComObject、CreateRemoteComObject的区别。

自定义COM的对象

接口一个重要的好处是:发布一个接口,可以不断更新其功能而不用升级客户端。因为不论应用升级还是业务改变,客户端的调用方式都是一

致的。

既然我们已经弄清楚Delphi是怎样实现一个接口的,那能否不使用向导,自己定义接口呢?这样做可以用一个接口继承出不同的接口实现类,

来完成不同的功能。同时也方便了小组开发、客户端开发、进程内/外同步编译以及调试。

接口单元:xxx_TLB.pas

前面略讲了接口的定义需要注意的方面。接口除了没有实例化外,它与普通类还有以下区别:接口中不能定义字段,所有属性的读写必须由方

法实现;接口没有构造和析构函数,所有成员都是public;接口内的方法不能定义为virtual,dynamic,abstract,override。

首先我们要建立一个接口。前面讲过接口的定义只存在于一个地方,即xxx_TLB.pas单元里面。使用类型库编辑器可以产生这样一个单元。还是

在新建项目的ActiveX页,选择最后一个图标(Type Library)打开类型库编辑器,按F12键就可以看到TLB文件(保存为.tlb)了。没有定义任

何接口的时候,TLB文件里除了一大段注释外只定义了LIBID(类型库的GUID)。假如关闭了类型库编辑器也没有关系,可以随时通过菜单View

– Type Library打开它。

先建立一个新接口(使用向导的话这步已经自动完成了),然后如前面操作一样建立方法、属性…生成的TLB文件内容与向导生成_TLB单元大致

相同,但仅有定义,缺乏“co+类名”之类的接口创建代码。

再观察代码,将发现接口是从IDispatch继承的,必须将这里的IDispatch改为IUnknown。保存将会得到.tlb文件,而我们想要的是一个单元

(.pas)文件,仅仅为了声明接口,所以把代码拷贝复制并保存到一个新的Unit。

自定义CLSID

从注册和调用部分可以看出CLSID的重要作用。CLSID是一个GUID(全局唯一接口表示符),用来标识对象。GUID是一个16个字节长的128位二进

制数据。Delphi声明一个GUID常量的语法是:

             Class_XXXXX : TGUID = ''''{xxxxxxxx-xxxxx-xxxxx-xxxxx-xxxxxxxx}'''';

在Delphi的编辑界面按Ctrl+Shift+G键可以自动生成等号后的数据串。GUID的声明并不一定在_TLB单元里面,任何地方都可以声明并引用它。

接口类声明与实现

新建一个ActiveX Library工程,加入刚才定义的TLB单元,再新建一个Unit。我的TLB单元取名为MyDef_TLB.pas,定义了一个接口

IMyInterface = interface(IUnknown),以及一个方法function SampleMethod(val: Smallint): SYSINT; safecall;现在让我们看看全部接口

类声明及实现的代码:

unit uMyDefCOM;

interface

uses ComObj, Comserv, ActiveX, MyDef_TLB;

const
  Class_MySvr: TGUID = '{1C0E5D5A-B824-44A4-AF6C-478363581D43}';

type
  TMyIClass = class(TComObject, IMyInterface)
    procedure Initialize; override;
    destructor Destroy; override;
  private
    FInitVal: word;
  public
    function SampleMethod(val: Smallint): SYSINT; safecall;
  end;

  TMySvrFactory = class(TComObjectFactory)
    procedure UpdateRegistry(Register: Boolean); override;
  end;

implementation

procedure TMyIClass.Initialize;
begin
  inherited;
  FInitVal := 100;
end;

destructor TMyIClass.Destroy;
begin
  inherited;
end;

function TMyIClass.SampleMethod(val: Smallint): SYSINT;
begin
  Result := val + FInitVal;
end;

procedure TMySvrFactory.UpdateRegistry(Register: Boolean);
begin
  inherited;
  if Register then
  begin
    CreateRegKey('MyApp\' + ClassName, 'GUID', GUIDToString(Class_MySvr));
  end  else
  begin
    DeleteRegKey('MyApp\' + ClassName);
  end;
end;

initialization
  TMySvrFactory.Create(ComServer, TMyIClass, Class_MySvr,  'MySvr', '', ciMultiInstance, tmApartment);

end.

Class_MySvr是自定义的CLSID,TMyIClass是接口实现类,TMySvrFactory是类工厂类。

COM对象的初始化

procedure Initialize是接口的初始化过程,而不是常见的Create方法。当客户端创建接口后,将首先执行里面的代码,与Create的作用一样

。一个COM对象的生存周期内,难免需要初始化类成员或者设置变量的初值,所以经常需要重载这个过程。

相对应的,destructor Destroy则和类的标准析构过程一样,作用也相同。

类工厂注册

在代码的最后部分,假如使用TComObjectFactory来注册,就和前面所讲的完全一样了。我在这里刻意用类TMySvrFactory继承了一次,并且重

载了UpdateRegistry 方法,以便向注册表中写入额外的内容。这是种小技巧,希望大家根据本文的思路,摸清COM/DCOM对象的Delphi实现结构

后,可以举一反三。毕竟随心所欲的控制COM对象,能提供的功能远不如此。

转载于:https://www.cnblogs.com/railgunman/archive/2010/11/27/1889701.html

目录清单:  01界面设计           制作一个屏幕保护程序           制作一个漂亮的VCD播放器           制作几何形状的窗体           制作桌面小精灵           制作相框           实现带阴影效果的画框           给Memo组件的显示区域加边框  02系统设置           一个托盘程序           如何实现目录的复制、删除和移动           如何改换壁纸和启动、关闭屏保           文件管理器           格式化磁盘           获取Windows、System和Temp目录           获取文件日期信息           获取驱动器信息           重启或关闭计算机           隐藏任务栏和桌面图标  03硬件访问与控制           实例1-获取和设置显示器模式           实例10 两种不同的方法获取端口信息           实例2-电源电池信息           实例3-内存状态信息           实例4-CPU型号信息           实例5-硬盘和光盘的操作           实例6-获取不同的声音播放设备           实例7-键盘相关设置           实例8-鼠标相关设置           实例9-从注册表中读取各类硬件信息  04控件应用           LISTBOX控件的折行显示           在控件中添加消息处理           将系统中的com接口包装为控件           带背景图的标签           我的资源管理器           打造自己的文件分割器           编写一个滚动提示控件           自制邮件发送客户端  05文件操作           Delphi中的INI文件编程           删除或移动正在使用的文件           如何在Delphi中操作Excel           实现应用程序的文件拖放功能           将EXE文件换成SWF文件           将SWF文件换成EXE文件           文件捆绑           统计中英文字个数  06图形图像多媒体           实例一--艺术字效果           实例七---photoshop图层效果           实例三---图象效果处理           实例九---在IntraWeb中显示图片数据           实例二--渐变色和动画矩形效果           实例五--编织带           实例八--动画屏保           实例六--镜框效果           实例十--matlab结合           实例十--电子地图开发之GIS组件--Mapobject深入开发           实例四---scanline属性  07数据库应用           实例1  Session获取BDE信息           实例10   决策图的使用           实例11  ThreadedIBX           实例2  可视化创建数据表格           实例3  SQL语句的使用           实例4  “一对多表”的设计           实例5   文本和数据记录的换           实例6 利用ADO的从SQL Server 2000中读取数据           实例7  多媒体数据库的使用           实例8  IntraWeb技术的实现           实例9  Rave报表的数据库使用  08网络与通讯           实例一 用DELPHI实现网络信使服务           实例七 NetClock           实例三 断点续传           实例二 升级精灵           实例五 网络文件传输           实例六 Ping           实例四 邮件群发  09趣味游戏           实例1--小狗过桥游戏           实例10--深入opengl游戏           实例2--键盘练习           实例3--字母拼图游戏           实例4--乒乓球           实例5-6--打地鼠---2个部分           实例7-8--扫雷--2个部分           实例9--一个简单的opengl游戏
Delphi是全新的可视化编程环境,为我们提供了一种方便、快捷的Windows应用程序开发工具。它使用了Microsoft Windows图形用户界面的许多先进特性和设计思想,采用了弹性可重复利用的完整的面向对象程序语言(Object-Oriented Language)、当今世界上最快的编辑器、最为领先的数据库技术。对于广大的程序开发人员来讲,使用Delphi开发应用软件,无疑会大大地提高编程效率,而且随着应用的深入,您将会发现编程不再是枯燥无味的工作——Delphi的每一个设计细节,都将带给您一份欣喜。  1.1 Delphi基本概念介绍  1.1.1 Delphi的基本形式  Delphi实际上是Pascal语言的一种版本,但它与传统的Pascal语言有天壤之别。一个Delphi程序首先是应用程序框架,而这一框架正是应用程序的“骨架”。在骨架上即使没有附着任何东西,仍可以严格地按照设计运行。您的工作只是在“骨架”中加入您的程序。缺省的应用程序是一个空白的窗体(Form),您可以运行它,结果得到一个空白的窗口。这个窗口具有Windows窗口的全部性质:可以被放大缩小、移动、最大最小化等,但您却没有编写一行程序。因此,可以说应用程序框架通过提供所有应用程序共有的东西,为用户应用程序的开发打下了良好的基础。Delphi已经为您做好了一切基础工作——程序框架就是一个已经完成的可运行应用程序,只是不处理任何事情。您所需要做的,只是在程序中加入完成您所需功能的代码而已。 在空白窗口的背后,应用程序的框架正在等待用户的输入。由于您并未告诉它接收到用户输入后作何反应,窗口除了响应Windows的基本操作(移动、缩放等)外,它只是接受用户的输入,然后再忽略。Delphi把Windows编程的回调、句柄处理等繁复过程都放在一个不可见的Romulam覆盖物下面,这样您可以不为它们所困扰,轻松从容地对可视部件进行编程。 1.1.2 面向对象编程的概念  面向对象的程序设计(Object-Oriented Programming,简记为OOP)Delphi诞生的基础。OOP立意于创建软件重用代码,具备更好地模拟现实世界环境的能力,这使它被公认为是自上而下编程的优胜者。它通过给程序中加入扩展语句,把函数“封装”进Windows编程所必需的“对象”中。面向对象的编程语言使得复杂的工作条理清晰、编写容易。说它是一场革命,不是对对象本身而言,而是对它们处理工作的能力而言。对象并不与传统程序设计和编程方法兼容,只是部分面向对象反而会使情形更糟。除非整个开发环境都是面向对象的,否则对象产生的好处还没有带来的麻烦多。而Delphi是完全面向对象的,这就使得Delphi成为一种触手可及的促进软件重用的开发工具,从而具有强大的吸引力。 一些早期的具有OOP性能的程序语言如C++,Pascal,Smalltalk等,虽然具有面向对象的特征,但不能轻松地画出可视化对象,与用户交互能力较差,程序员仍然要编写大量的代码。Delphi的推出,填补了这项空白。您不必自己建立对象,只要在提供的程序框架中加入完成功能的代码,其余的都交给Delphi去做。欲生成漂亮的界面和结构良好的程序丝毫不必绞尽脑汁,Delphi将帮助您轻松地完成。它允许在一个具有真正OOP扩展的可视化编程环境中,使用它的Object Pascal语言。这种革命性的组合,使得可视化编程与面向对象的开发框架紧密地结合起来。 1.2 Delphi 快速入门  在这一节中,我们来开发一个小程序。随着开发的过程,逐步介绍Delphi的主要部件及其操作方法。建议读者按照本书介绍的过程,在您的电脑上直接操作。您将对Delphi的可视化编程有一个直观、快捷的了解,必将起到事半功倍的效果。  1.2.1 进入Delphi的可视化编程环境 1.2.1.1 安装Delphi  Delphi的安装与其它应用软件并无不同。2.0版必须在Windows 95以上的操作系统中使用。启动Windows 95或Windows NT后,将Delphi的光盘放入光驱(CD-ROM)中,运行光盘上的\INSTALL\SETUP.EXE文件,它的安装程序会提示您正确地装入Delphi。如果您是在微软中文Windows环境中安装Delphi,请参照附录A来设置您的BDE环境,以便于处理中文数据。  1.2.1.2 进入Delphi 环境 为避免隐藏在Delphi后的Program Manager和曾经运行过的其它程序扰乱版面,分散您的注意力,不妨在启动Delphi前关掉其它应用程序;启动Delphi后,再最小化隐藏在后面的Delphi 2.0程序组。这样屏幕上就只留下Delphi窗口可见了。 首次加载Delphi,屏幕上会出现四个窗口: ● 标题为“Delphi-Project1”的Delphi主窗口 ● Object Inspector窗口 ● 标题为“Form1”的窗体(Form)窗口 ● 标题为“Unit1.PAS”的代码编辑窗口。刚启动时这一窗口的大部分被“Form1”窗体所掩盖。将“Form1”窗体移开,或单击Form1窗体下方的状态行,可以使其全部可见。在“Form1”窗体的任意可见位置单击鼠标,可以恢复主窗体可见 以下我们将对这四个窗口分别进行介绍。  1.2.2 Delphi可视化编程环境介绍  1.2.2.1 主窗口(Main Form)  Delphi的主窗口位于屏幕的上端,包括Menu(菜单)、Speed Bar(加速条)Component Panel(部件选项板)。Menu是下拉式主菜单。Speed Bar位于主窗口的左下端,由两排共14个加速按钮组成。这些按钮是菜单功能的快捷方式,各种图标直观地表示了它能执行的动作。Component Panel由一行、若干页对象按钮所组成,利用它来选择需要的部件并将它放到窗体中去。  1.2.2.2 Object Inspector(对象检视器)  Object Inspector窗口含有两页:Properties页显示窗体中当前被选择部件的属性信息,并允许改变对象的属性;Events页列出了当前部件可以响应的事件。按动Object Inspector下端的“Events”页标签,使得Events页可见,这一定的事件后边的空白处,可以定义对象接受到相应事件时执行的动作。首次启动时,Object Inspector窗口显示的是当前窗体Form1的属性。Object Inspector根据对象属性的多少,决定是否有滚行显示。移动滚行条,可以查看当前对象的全部属性。 此外,Object Inspector上还有Object Selector(对象选择器),位于Object Inspector上方的下拉式菜单中。它显示了窗体上所有部件的名称和类型,也包含窗体本身。您可以用Object Selector很容易地在窗体的各个部件之间切换,也可以快速地回到窗体本身。当窗体中含有较多的对象时,您会发现这是切换对象尤其是回到窗体的最快捷途径。 想使Object Inspector一直可见,可将鼠标移到Object Inspector上,按动右键,以启动Object Inspector的弹出式菜单,将其设置为Stay On Top。这对初学者常是一个很重要的设置方式。  1.2.2.3 窗体窗口  Forms窗口是开展大部分设计的工作区域。首次启动Delphi 2.0时显示的是窗体Form1。可以把部件放在窗体中,通过移动位置、改变尺寸等操作随心所欲地安排它们,以此来开发应用程序的用户界面。您可以把窗体想象成一个可以放置其它部件的容器。窗体上有栅格(Grids),供放置部件时对齐位置用,在程序运行时Grids是不可见的。 一个真正的应用程序可能有不止一个窗口,您可以选用不同的窗体进行设计。其它窗体可以是对话框(Dialog Box)、数据录入框等。  1.2.2.4 代码窗口  代码窗口一开始处于窗体窗口之下。因为在Delphi中,设计用户界面直接在窗体中进行,运行结果和设计样板完全一致。当部件被放到窗体上时,Delphi会自动生成大部分的用户界面代码。您所应做的只是在它为您生成的框架中加入完成所需功能的程序段而已。点动Form1的状态行使代码窗口可见。 这个窗口中是代码编辑器。可以在其中书写Delphi应用程序的源代码。当程序中含有不止一个窗口时,会有几个库单元的源程序出现在代码编辑器中。代码编辑器的标题条中显示了当前正在编辑的库单元文件名。要查看某一特定程序的源代码,只需用鼠标点动写有该库单元文件名的页标签,就可以对该库单元进行编辑了。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值