注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

yeye55的博客

编程就象品一杯清茶,清淡却又深厚。

 
 
 

日志

 
 
关于我

宅男一只,喜欢编程。没事的时候就喜欢抱着电脑看看电影、看看动漫、听听音乐、打打网游、逛逛论坛。清静的时候喜欢专研编程技术,但是发现自己至今仍然只是一只三脚猫。

网易考拉推荐

Delphi XE4 语言指南 - 10 接口  

2014-11-25 19:58:41|  分类: XE4参考指南 |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |

  本指南名称:Delphi XE4 语言指南,作者:叶叶,于2014年11月25日在网易博客上发表。页面地址:http://yeye55blog.blog.163.com/blog/static/197241021201410252936156/ 。本指南全文可以在上述页面中下载。请尊重他人劳动成果,转载或引用时请注明出处。

10 接口

  一个对象接口,或简称“接口(interface)”,定义了一些可以由类来实现的方法。接口声明就像是类声明,但是接口不能被直接实例化,并且接口声明的方法没有对应的实现。相反,这些接口的方法是由支持该接口的任何一个类来负责提供实现的。一个接口类型的变量可以引用一个实现该接口的类的对象。但是,这样的变量只能被用于调用在该接口中声明的方法。接口提供了一些多重继承的优点,却没有语义上的困难。接口对于使用分布式对象模型也是必不可少的(例如:SOAP)。使用分布式对象模型,可以让支持接口的自定义对象与使用C++、Java、以及其他语言编写的对象进行交互。

10.1 接口声明

  与类一样,只能在程序或单元的最外层的范围内声明接口类型,不能在过程或函数的内部声明接口类型。声明一个接口类型的语法格式如下所示。

type 接口名称 = interface (父接口名称)
    ['{GUID}']
    成员列表
end;

  其中的接口名称必须是一个有效的标识符。其中的“(父接口名称)”和“['{GUID}']”是可选的。注意,父接口名称和GUID的指定内容必须支持与Windows COM的互操作性。如果接口是通过COM来进行访问的,那么一定要指定父接口名称和GUID。在大多数方面,接口声明类似于类声明,但是与类声明相比接口声明有如下限制。

  一、在成员列表中只能包含方法和属性。字段是不允许出现在接口中的。

  二、由于接口没有字段,所以属性的read说明符和write说明符只能指定方法。

  三、接口的所有成员都是公开的。可见性说明符和存储说明符不允许在接口中使用。但是数组属性可以被声明为默认的。

  四、接口没有构造方法和析构方法。接口不能被实例化,并且只能通过实现了该接口的类的对象来进行访问。

  五、接口中的方法不能被声明为虚拟的、动态的、抽象的、或者是进行覆盖。由于接口不实现自己的方法,所以这些声明没有任何意义。

  一个接口声明的例子如下所示。

01 type IMalloc = interface (IInterface)
02     ['{00000002-0000-0000-C000-000000000046}'] 
03     function Alloc(Size : Integer) : Pointer; stdcall; 
04     function Realloc(P : Pointer; Size : Integer) : Pointer; stdcall; 
05     procedure Free(P : Pointer); stdcall;
06     function GetSize(P : Pointer) : Integer; stdcall;
07     function DidAlloc(P : Pointer) : Integer; stdcall;
08     procedure HeapMinimize; stdcall;
09 end;

代码10.1 接口声明的例子

  在一些接口声明中,保留字interface会被替换为dispinterface,关于这一点的详细介绍请参考第10.5节的自动化对象。

  与类一样,接口也会继承父接口中的所有方法。与类不同的是接口没有实现这些方法。接口继承了实现这些方法的责任,这些责任将被传递给支持该接口的任何一个类。接口声明时可以指定一个父接口。如果在声明中没有指定父接口,那么该接口将直接派生于IInterface接口。在System单元中声明的IInterface接口是其他所有接口的最终父接口。注意,IInterface接口相当于是IUnknown接口。在平台无关的应用程序中通常应该使用IInterface接口,而对于包含依赖Windows的具体程序可以保留对IUnknown接口的使用。

  在Windows中,IInterface接口声明了三个方法:QueryInterface方法、_AddRef方法和_Release方法。QueryInterface方法提供了一种手段,可以获取一个对象支持的不同接口的引用。_AddRef方法和_Release方法为接口引用的生存期提供了内存管理。实现这些方法的最简单的办法就是从System单元中的TInterfacedObject类中派生出一个实现类来。当然,也可以为这些方法编写一个空函数的实现。不过,对于COM对象,必须通过_AddRef方法和_Release方法来进行管理。注意,QueryInterface方法、_AddRef方法和_Release方法必须支持与Windows COM的互操作性。如果接口是通过COM来进行访问的,那么一定要实现这些方法。

  在接口声明中可以指定一个全局唯一标识符(globally unique identifier,GUID)。GUID在成员列表之前进行声明,并且使用一个字符串字面量来进行表示。声明一个GUID的语法格式如下所示。

['{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}']

  其中的每一个x都是一个十六进制数字(0至9或A至F)。类型库编辑器会自动为新接口生成GUID。在IDE的代码编辑器中可以使用快捷键Ctrl+Shift+G在当前的代码位置上插入一个GUID的声明。GUID是一个16字节的二进制值,用来表示一个接口的唯一标识。如果一个接口声明了一个GUID,那么可以使用接口查询来获取它的实现引用。注意,GUID仅用于对COM的互操作性。在System单元中声明的TGUID类型和PGUID类型可以用于操作GUID。这两个类型的声明如下所示。

01 PGUID = ^TGUID;
02 TGUID = packed record
03     D1 : LongWord;
04     D2 : Word;
05     D3 : Word;
06     D4 : array [0..7] of Byte;
07     class operator Equal(const Left, Right : TGUID) : Boolean;
08     class operator NotEqual(const Left, Right : TGUID) : Boolean;
09     class function Empty : TGUID; static;
10 end;

代码10.2 TGUID类型和PGUID类型的声明

  在System.SysUtils单元中提供了一个名为Supports的重载函数,该函数用于测试类类型及其实例是否支持由GUID所表示的特定接口。例如下面的这两条语句。

if Supports(Allocator, IMalloc) then ...
if Supports(Allocator, IID_IMalloc) then ...

  当指定的类类型及其实例支持由GUID所表示的特定接口时,Supports函数返回True,否则Supports函数返回False。Supports函数的使用类似于Delphi的is和as运算符。一个最重要的区别是,Supports函数的第二个参数可以是一个GUID或者是一个与GUID相关联的接口,而is和as运算符的右操作数只能是类型名称。关于is和as运算符的详细介绍请参考第9.7节。

  接口方法的默认调用约定是register约定。但是,对于在模块间进行共享的接口(尤其是在不同语言中编写的接口)应该将所有的方法都声明为stdcall约定。在Windows中,可以对双重接口(dual interface)的实现方法使用safecall约定。

  在接口中声明的属性只能通过接口类型的表达式来进行访问,它们不能通过类类型的变量来进行访问。另外,接口属性只有在编译时所在的程序中是可见的。在一个接口中,属性的read说明符和write说明符只能使用方法,因为字段是不可用的。

  如果在一个接口的声明中,没有指定父接口、也没有声明GUID或成员列表、同时省略了end,只保留了一个interface保留字和一个分号“;”,那么这就是一个接口的前向声明。一个接口的前向声明必须通过相同接口的定义声明来解决。前向声明和定义声明必须位于相同的type声明部分。也就是说,在前向声明和定义声明之间,除了其他类型的声明不能出现其他内容。接口的前向声明通常用于声明相互依赖的接口。一个使用前向声明的例子如下所示。

01 type
02     IControl = interface;       //前向声明
03
04     IWindow = interface
05         ['{00000115-0000-0000-C000-000000000044}']
06         function GetControl(Index : Integer) : IControl;
07     end;
08
09     IControl = interface          //定义声明
10         ['{00000115-0000-0000-C000-000000000049}']
11         function GetWindow : IWindow;
12     end;

代码10.3 接口的前向声明

  接口不允许进行相互派生。例如,IWindow接口派生于IControl接口,而IControl接口又派生于IWindow接口,这是错误的。

10.2 接口实现

  接口在声明之后必须在一个类中进行实现然后才可以使用。由一个类实现的接口必须在类声明中的父类名称之后进行指定,这样的语法格式如下所示。

type 类名称 = class (父类名称, 接口名称1, ..., 接口名称n)
    成员列表
end;

  在这种情况下,父类名称不能被省略。例如下面的这段代码。

01 type
02     TMemoryManager = class (TInterfacedObject, IMalloc, IErrorInfo)
03         //此处省略部分代码...
04     end;

代码10.4 实现接口的类声明

  在代码10.4中声明了一个名为TMemoryManager的类,它实现了IMalloc和IErrorInfo两个接口。如果一个类实现了一个接口,那么它必须实现(或继承一个实现)该接口中声明的每一个方法。在System单元中声明的TInterfacedObject类如下所示。

01 type
02     TInterfacedObject = class(TObject, IInterface)
03     protected
04         FRefCount : Integer;
05         function QueryInterface(const IID : TGUID; out Obj) : HResult; stdcall;
06         function _AddRef : Integer; stdcall;
07         function _Release : Integer; stdcall;
08     public
09         procedure AfterConstruction; override;
10         procedure BeforeDestruction; override;
11         class function NewInstance : TObject; override;
12         property RefCount : Integer read FRefCount;
13     end;

代码10.5 TInterfacedObject类的声明

  TInterfacedObject类实现了IInterface接口,所以在TInterfacedObject类中声明和实现了IInterface接口中的全部三个方法。实现接口的类也可以被用作基类。例如,代码10.4中的TMemoryManager类就是TInterfacedObject类的直接子类。在Windows平台上,每一个接口都继承自IInterface接口,所以一个实现接口的类必须实现QueryInterface方法、_AddRef方法和_Release方法。System单元中的TInterfacedObject类实现了这三个方法,它可以为派生于它的其他实现接口的类提供方便。

  当一个接口被实现时,每一个接口中的方法都会映射到实现类中的一个方法上。实现方法必须与接口方法具有相同的返回值类型、相同的调用约定、相同数目的参数、以及在相同位置上的相同参数类型。在默认的情况下,每一个接口中的方法都会映射到实现类中的同名方法上。但是,可以在类声明中使用方法解决子句(method resolution clause)来覆盖默认的基于名称的映射。当一个类要实现两个或两个以上的具有相同名称方法的接口时,也需要使用方法解决子句来解决命名冲突。针对过程的方法解决子句的语法格式如下所示。

procedure 接口名称.接口方法名称 = 实现方法名称;

  针对函数的方法解决子句的语法格式如下所示。

function 接口名称.接口方法名称 = 实现方法名称;

  其中的实现方法名称是一个在当前类或者是它的一个父类中声明的方法。在类声明中,实现方法名称所指定的方法可以在方法解决子句之后声明,但是该方法不能是在另一个模块中声明的父类中的私有方法。一个使用方法解决子句的例子如下所示。

01 type
02     TMemoryManager = class (TInterfacedObject, IMalloc, IErrorInfo)
03         function IMalloc.Alloc = Allocate;
04         procedure IMalloc.Free = Deallocate;
05         //此处省略部分代码...
06     end;

代码10.6 方法解决子句的使用

  在代码10.6中,IMalloc接口中的Alloc方法和Free方法分别映射到TMemoryManager类中的Allocate方法和Deallocate方法。

  方法解决子句不能修改从父类中引入的映射。一个子类可以通过覆盖实现方法来修改特定的接口方法的实现,但是这要求实现方法被声明为虚拟的或动态的。一个类也可以重新实现继承自父类的整个接口,这需要在子类的声明中重新列出涉及的接口。例如下面的这段代码。

01 type
02     IWindow = interface
03         ['{00000115-0000-0000-C000-000000000146}']
04         procedure Draw;
05     end;
06
07     TWindow = class(TInterfacedObject, IWindow)
08         //TWindow类实现了IWindow接口中的Draw方法
09         procedure Draw;
10     end;
11
12     TFrameWindow = class(TWindow, IWindow)
13         //TFrameWindow类重新实现了IWindow接口中的Draw方法
14         procedure Draw;
15     end;

代码10.7 接口的重新实现

  接口的重新实现会隐藏继承的相同接口的实现。因此,重新实现接口对父类中的方法解决子句没有影响。另外,在Windows平台上,允许在实现类中通过使用implements指令由一个属性来委托实现一个接口。例如下面的这个属性声明。

property MyInterface : IMyInterface read FMyInterface implements IMyInterface;

  声明了一个名为MyInterface的属性,该属性用于实现IMyInterface接口。implements指令必须位于属性声明的说明符之后。可以在该指令中列出多个接口,接口之间使用逗号分隔。委托属性适用于以下规则。

  一、必须是类类型或接口类型。

  二、不能是数组属性或者有一个index说明符。

  三、必须有一个read说明符。如果属性的read说明符指定了一个方法,那么该方法必须使用默认的register调用约定,并且不能是动态的(可以是虚拟的)或者是使用了message指令。

  如果委托属性是接口类型,那么该接口或者是派生于它的接口必须出现在声明该属性的类的实现接口列表声明中。该委托属性必须返回一个对象,该对象所属的类必须完全实现由implements指令所指定的接口。在这种方式下不能使用方法解决子句。例如下面的这段代码。

01 type
02     IMyInterface = interface
03         procedure P1;
04         procedure P2;
05     end;
06
07     TMyImplClass = class(TInterfacedObject, IMyInterface)
08         procedure P1;
09         procedure P2;
10     end;
11
12     TMyClass = class(TObject, IMyInterface)
13         FMyInterface : IMyInterface;
14         property MyInterface : IMyInterface      read FMyInterface
15                                                                                              implements IMyInterface;
16     end;
17
18 //此处省略部分代码...
19
20 procedure MyProc;
21 var
22     MyClass : TMyClass;
23     MyInterface : IMyInterface;
24 begin
25     MyClass := TMyClass.Create;
26     MyClass.FMyInterface := TMyImplClass.Create;
27     MyInterface := MyClass;
28     MyInterface.P1;     //调用TMyImplClass.P1
29     MyInterface.P2;     //调用TMyImplClass.P2
30 end;

代码10.8 委托给一个接口类型的属性

  如果委托属性是类类型,那么会从该类及其父类中查找指定接口的方法实现。所以,可以让一些方法由属性所指定的类来实现,而另一些方法由声明该属性的类来实现。通常的方式是使用方法解决子句来解决歧义或者是指定一个特定的方法。一个接口不能由多个类类型的属性来实现。例如下面的这段代码。

01 type
02     IMyInterface = interface
03         procedure P1;
04         procedure P2;
05     end;
06
07     TMyImplClass = class
08         procedure P1;
09         procedure P2;
10     end;
11
12     TMyClass = class(TInterfacedObject, IMyInterface)
13         FMyImplClass : TMyImplClass;
14         property MyImplClass : TMyImplClass   read FMyImplClass
15                                                                                              implements IMyInterface;
16
17         procedure IMyInterface.P1 = MyP1;
18         procedure MyP1;
19     end;
20
21 //此处省略部分代码...
22
23 procedure MyProc;
24 var
25     MyClass : TMyClass;
26     MyInterface : IMyInterface;
27 begin
28     MyClass := TMyClass.Create;
29     MyClass.FMyImplClass := TMyImplClass.Create;
30     MyInterface := MyClass;
31     MyInterface.P1;     //调用TMyClass.MyP1
32     MyInterface.P2;     //调用TMyImplClass.P2
33 end;

代码10.9 委托给一个类类型的属性

10.3 接口引用

  如果声明了一个接口类型的变量,那么该变量可以引用实现了该接口的任意类的实例。通过接口引用变量可以调用接口中的方法,而这些接口方法的实现在编译时可以是未知的。但是,它们会受到以下限制。

  一、通过一个接口类型的表达式只能访问该接口中声明的方法和属性,不能访问实现类中的其他成员。

  二、一个接口类型的表达式不能引用实现了其子接口的类的对象,除非该类(或者是它的一个子类)显式实现了该父接口。

  例如下面的这段代码。

01 type
02     IAncestor = interface
03     end;
04
05     IDescendant = interface(IAncestor)
06         procedure P1;
07     end;
08
09     TSomething = class(TInterfacedObject, IDescendant)
10         procedure P1;
11         procedure P2;
12     end;
13
14 //此处省略部分代码...
15
16 procedure MyProc;
17 var
18     D : IDescendant;
19     A : IAncestor;
20 begin
21     D := TSomething.Create;        //正确
22     A := TSomething.Create;        //错误
23     D.P1;       //正确
24     D.P2;       //错误
25 end;

代码10.10 实现接口引用

  在代码10.10中,变量A被声明为IAncestor类型的。因为TSomething类没有在它的实现接口列表中列出IAncestor接口,所以TSomething类的实例不能被赋值给变量A。但是,如果将代码10.10中的第9行的TSomething类的声明修改如下。

TSomething = class(TInterfacedObject, IAncestor, IDescendant)

  那么代码10.10中的第22行就是一个有效的赋值。在代码10.10中,变量D被声明为IDescendant类型的。虽然变量D引用的是一个TSomething类的实例,但是不能通过变量D来访问TSomething类中的P2方法,因为P2方法不是在IDescendant接口中声明的。但是,如果将代码10.10中的第18行的变量D的声明修改如下。

D : TSomething;

  那么代码10.10中的第24行就是一个有效的方法调用。

  在Windows平台上,接口引用通常是使用引用计数来进行管理的,这依赖于从System单元的IInterface接口中继承的_AddRef方法和_Release方法。对于使用了引用计数的默认实现,当一个对象仅通过接口来进行引用时,没必要手动销毁它,当它的最后一个引用离开了其作用域,那么该对象就会被自动销毁。一些类的实现接口会绕过这个默认的生存期管理,并且一些混合对象只有在该对象没有所有者的情况下才会使用引用计数。

  接口类型的全局变量只能被初始化为nil。要确定一个接口类型的表达式是否引用了一个对象,可以将它传递给标准函数Assigned。一个给定类类型的变量是与该类实现的任意接口类型赋值兼容的。一个接口类型的变量是与任意父接口类型赋值兼容的。可以将nil赋值给任意的接口类型的变量。一个接口类型的表达式可以被赋值给变体。如果接口是IDispatch接口或者是其子接口,那么变体接收的类型代码为varDispatch,否则变体接收的类型代码为varUnknown。一个类型代码为varEmpty、varUnknown或varDispatch的变体可以被赋值给IInterface类型的变量。一个类型代码为varEmpty或varDispatch的变体可以被赋值给IDispatch类型的变量。一个接口类型的表达式可以被转换为变体。如果接口是IDispatch接口或者是其子接口,那么返回的变体的类型代码为varDispatch,否则返回的变体的类型代码为varUnknown。一个类型代码为varEmpty、varUnknown或varDispatch的变体可以被转换为IInterface类型。一个类型代码为varEmpty或varDispatch的变体可以被转换为IDispatch类型。

10.4 接口运算符

  接口运算符(interface operator)用于对接口进行操作。as运算符用于进行带检查的接口类型转换。as运算符也被称为接口查询(interface query),它可以从一个对象引用或者是另一个接口引用中产生一个接口类型的表达式,该表达式将基于对象的实际(运行时)类型。例如,对于表达式“MyObject as MyInterface”。其中的MyObject是一个表达式,该表达式为接口类型、变体类型或者是实现了接口的类的实例。其中的MyInterface是任意的一个带有GUID的接口。如果MyObject为nil,那么接口查询将返回nil。否则,接口查询将MyInterface的GUID传递给MyObject中的QueryInterface方法。除非QueryInterface方法返回零,否则接口查询将会引发一个异常。如果QueryInterface方法返回零(表示该对象的类实现了该接口),那么接口查询将会返回一个对于该对象的接口引用。

  as运算符也可以将一个接口引用重新转换回它所引用的那个对象。这种转换只适用于从Delphi对象中获取的接口。例如下面的这段代码。

01 var
02     LIntfRef : IInterface;
03     LObj : TInterfacedObject;
04 begin
05     {创建一个接口对象,并从中提取一个接口。}
06     LIntfRef := TInterfacedObject.Create;
07
08     {将该接口转换回原始的对象。}
09     LObj := LIntfRef as TInterfacedObject;
10 end;

代码10.11 将接口引用转换为对象

  在代码10.11中演示了如何从接口引用中获取原始的对象。当希望操作接口引用的对象时,这项技术是非常有用的。如果接口不是从给定的类中提取的,那么as运算符将会引发一个异常。例如下面的这段代码。

01 var
02     LIntfRef : IInterface;
03     LObj : TEdit;
04 begin
05     {创建一个接口对象,并从中提取一个接口。}
06     LIntfRef := TComponent.Create(nil);
07
08     try
09         {将该接口转换为TEdit类型。}
10         LObj := LIntfRef as TEdit;
11     except
12         ShowMessage('LIntfRef没有引用TEdit类的实例!');
13     end;
14 end;

代码10.12 as运算符引发的异常

  也可以使用普通的类型转换(不安全)将一个接口引用转换为对象。在对对象进行不安全类型转换的情况下是不会引发任何异常的。不安全的对象到对象的类型转换与不安全的接口到对象的类型转换是不同的,前者有可能会返回一个不兼容类型的有效指针,而后者有可能会返回nil。一个进行不安全类型转换的例子如下所示。

01 var
02     LIntfRef : IInterface;
03     LObj : TObject;
04 begin
05     {创建一个接口对象,并从中提取一个接口。}
06     LIntfRef := TComponent.Create(nil);
07
08     {将该接口转换为TEdit类型。}
09     LObj := TEdit(LIntfRef);
10
11     if LObj = nil then
12         ShowMessage('LIntfRef没有引用TEdit类的实例!');
13
14     {将该接口转换为TObject类型。}
15     LObj := TObject(LIntfRef);
16
17     if LObj <> nil then
18         ShowMessage('LIntfRef引用了TObject类或其子类的实例!');
19 end;

代码10.13 不安全类型转换

  为了避免潜在的nil引用,可以使用is运算符来验证一个接口引用是否提取自给定的类。例如下面的这条语句。

if LIntfRef is TCustomObject then ...

  注意,当使用不安全类型转换或者是as和is运算符时,必须确保是针对Delphi对象进行使用。

10.5 自动化对象

  如果一个对象的类实现了在System单元中声明的IDispatch接口,那么这个对象就是一个自动化对象(Automation object)。通常使用变体来访问自动化对象。当一个变体引用了一个自动化对象时,可以通过该变体调用该对象中的方法以及读写它的属性。要做到这一点,必须在单元、程序或库的uses子句中引用System.Win.ComObj单元。

  调度接口类型(Dispatch interface type)定义了一些通过IDispatch接口实现的自动化对象中的方法和属性。在运行时调用一个调度接口中的方法是通过IDispatch接口中的Invoke方法来进行实现的。一个类无法实现调度接口。声明一个调度接口类型的语法格式如下所示。

type 接口名称 = dispinterface
    ['{GUID}']
    成员列表
end;

  其中的接口名称必须是一个有效的标识符。其中的“['{GUID}']”是可选的。其中的成员列表包含了方法和属性的声明。调度接口声明类似于普通的接口声明,但是它不能指定一个父接口。一个调度接口声明的例子如下所示。

01 type
02     IStringsDisp = dispinterface
03         ['{EE05DFE2-5549-11D0-9EA9-0020AF3D82DA}']
04         property ControlDefault[Index : Integer] : OleVariant dispid 0; default;
05         function Count : Integer; dispid 1;
06         property Item[Index : Integer] : OleVariant dispid 2;
07         procedure Remove(Index : Integer); dispid 3;
08         procedure Clear; dispid 4;
09         function Add(Item : OleVariant) : Integer; dispid 5;
10         function _NewEnum : IUnknown; dispid -4;
11     end;

代码10.14 调度接口声明的例子

  调度接口中的方法的原型是调用底层实现的IDispatch接口中的Invoke方法。调度接口中的属性不能包含访问说明符。它们可以被声明为只读的或只写的。数组属性可以被声明为默认的。调度接口中的所有的属性、数组属性、方法参数和返回值的类型必须是自动化的。也就是说,必须是Byte类型、Currency类型、Real类型、Double类型、LongInt类型、Integer类型、Single类型、SmallInt类型、AnsiString类型、WideString类型、TDateTime类型、Variant类型、OleVariant类型、WordBool类型、以及所有的接口类型。在调度接口的方法和属性的声明中可以包含一个dispid指令。在dispid指令之后必须紧跟一个整数常量用来为成员指定一个自动化调度ID(Automation dispatch ID)。在dispid指令中指定一个已经使用的ID会导致错误。在调度接口的方法声明中不能使用除了dispid之外的其他指令。在调度接口的属性声明中不能使用除了dispid和default之外的其他指令。

  自动化对象的方法的调用是在运行时进行绑定的,并且不需要先前的方法声明。这些调用的有效性在编译时是不进行检查的。一个调用自动化方法的例子如下所示。

01 var
02     Word : Variant;
03 begin
04     Word := CreateOleObject('Word.Basic');
05     Word.FileNew('Normal');
06     Word.Insert('This is the first line'#13);
07     Word.Insert('This is the second line'#13);
08     Word.FileSaveAs(' c:\temp\test.txt ', 3);
09 end;

代码10.15 访问自动化对象

  在代码10.15中调用了在System.Win.ComObj单元中声明的CreateOleObject函数,该函数会返回一个引用了自动化对象的IDispatch接口,该接口被赋值给了变体类型的变量Word。可以将接口类型的参数传递给自动化方法。在自动化的控制器和服务器之间传递二进制数据的首选方案是使用元素类型为varByte的变体数组。这样的数组不需要翻译它们的数据,并且可以使用VarArrayLock和VarArrayUnlock例程进行有效的访问。

  自动化对象的方法调用或属性访问的语法格式类似于普通的方法调用或属性访问。不过,在自动化方法的调用中可以使用位置参数(positional parameter)和命名参数(named parameter)。注意,有些自动化服务器不支持命名参数。位置参数就是一个简单的表达式。命名参数的语法格式为“参数标识符 := 表达式”。在一个方法调用中,位置参数必须位于任意的命名参数之前。命名参数的顺序可以是任意的。有些自动化服务器允许在方法调用中省略参数,并接受它们的默认值。例如下面的这段代码。

01 var
02     Word : Variant;
03 begin
04     Word.FileSaveAs('test.doc');
05     Word.FileSaveAs('test.doc', 6);
06     Word.FileSaveAs('test.doc', , , 'secret');
07     Word.FileSaveAs('test.doc', Password := 'secret');
08     Word.FileSaveAs(Password := 'secret', Name := 'test.doc');
09 end;

代码10.16 自动化对象的方法调用

  调用自动化方法时的参数类型可以是整数类型、实数类型、字符串类型、Boolean类型和变体类型。如果参数表达式只包含了一个变量引用,并且该变量引用的类型是Byte类型、SmallInt类型、Integer类型、Single类型、Double类型、Currency类型、TDateTime类型、AnsiString类型、WordBool类型或Variant类型,那么该参数将通过引用来进行传递。如果参数表达式不是这些类型之一,或者它不只是一个变量,那么该参数将按值来进行传递。将一个参数通过引用传递给一个接受值参数的方法时,COM会从该引用参数中获取一个值。将一个参数按值传递给一个接受引用参数的方法时会导致一个错误。

  双重接口(dual interface)是一个通过自动化的既支持编译时绑定也支持运行时绑定的接口。双重接口必须派生于IDispatch接口。双重接口中的所有方法(除了继承自IInterface接口和IDispatch接口的那些方法)必须使用safecall调用约定。所有的方法参数和返回值的类型必须是自动化的。也就是说,必须是Byte类型、Currency类型、Real类型、Double类型、Real48类型、Integer类型、Single类型、SmallInt类型、AnsiString类型、ShortString类型、TDateTime类型、Variant类型、OleVariant类型和WordBool类型。

  评论这张
 
阅读(518)| 评论(0)
推荐 转载

历史上的今天

在LOFTER的更多文章

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017