Delphi的接口陷阱

原创 2004年09月14日 16:18:00

Delphi的接口陷阱

现在我所知的有两大陷阱:<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />

陷阱一、接口的类型转换陷阱

a)       不能把一个对象引用强制转换成这个引用的类型没有声明实现的接口,即使这个对象实际实现了这个接口(呵呵,优点拗口)。

b)       当把一个对象变量赋给一个接口变量,在把这个接口变量赋还给对象变量时,这个对象变量的地址已经变了,也就是不再是原来的对象了,而是指向一个错误的地址。

例如:

I1 = interface
    function Do: Boolean;
end;


TC1 = Class
    ATT1: Integer;
end;


TC2 = Class(TC1, I1)
    ATT2: Integer;
    function Do: Boolean;
end;

Intf1: I1;

OBJ1: TC!;

OBJ2: TC2;

OBJ2 := TC2.Create;
OBJ1 := OBJ2.
I1(OBJ2).DO;
正确。
I1(OBJ1).DO;
编译失败。

因为OBJ1的类型TC1没有声明实现I1所以不能转换成I1,即使OBJ1确实实现了I1

还有,如果把对象转为接口再转回来也会有问题。

OBJ2 := TC2.Create;

OBJ2.ATT1 := 0;
Intf1 := OBJ2;//
正确。

OBJ2 := Intf1

TC2(Intf1).ATT1 := 0; //运行期非法地址访问错误。

OBJ2.ATT1 := 0; //运行期非法地址访问错误。

也就是,从对象引用转换成指针引用后,地址改变了,但是由指针引用再转回对象引用时地址没有变回来(Delphibug?)。

 

陷阱二、接口的生存期管理

依据我的常识(此处是编程常识,不是Delphi使用常识)来讲,我认为接口是不需要生存期管理的,因为接口根本不可能生成真正的对象。但是Delphi却又一次打击了我的常识(咦,为什么要说“又”呢?),它的接口是有生存期的,而且必须实现以下三个方法:

    function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;

    function _AddRef: Integer; stdcall;

function _Release: Integer; stdcall;

每次都要实现这三个方法是比较麻烦的,而且更重要的是,我不知道Delphi什么时候用以及怎么用这三个方法?所以我也不知道怎么实现这三个方法。J

如果不想自己实现这三个方法,你可以使用TComponent。因为TComponent已经实现了这三个方法,所以可以从它继承,就不用实现这三个方法了。

这样就可以放心使用了吗?答案是否定的。因为Delphi在你把接口变量置为nil时偷偷的(因为很出乎我的意料)调用了_Release

function _IntfClear(var Dest: IInterface): Pointer;

var

  P: Pointer;

begin

  Result := @Dest;

  if Dest <> nil then

  begin

    P := Pointer(Dest);

    Pointer(Dest) := nil;

    IInterface(P)._Release;

  end;

end;

_Release时又做了什么呢?

function TComponent._Release: Integer;

begin

  if FVCLComObject = nil then

    Result := -1   // -1 indicates no reference counting is taking place

  else

    Result := IVCLComObject(FVCLComObject)._Release;

end;

不是Com对象的话,就什么也没作。我们作的不是Com对象,是不是就没有任何问题了呢?答案依然是否定的,考虑如下情况:

OBJ2 := TC2.Create;

try

Intf1 := OBJ2;

Intf1.DO;

Finally

    OBJ2.Free;

    Intf1 := nil;

End;

会怎么样呢?会出非法地址访问错误。为什么?上面说过把接口引用设为nil时,会调用_IntfClear,而_IntfClear又会调用对象的_Release,而这时这个对象已经释放了,自然就出非法地址访问错误啦。

有人说多此一举吗,接口引用只是个地址,没必要手动设为nil

OBJ2 := TC2.Create;

try

Intf1 := OBJ2;

Intf1.DO;

Finally

    OBJ2.Free;

End;

结果可能还会出你的意料,还是非法地址访问错误。为什么?因为Delphi编译器耍了个小聪明,它认为你忘记把这个地址引用置为nil了,所以你会自动给你加上,看来Delphi编译器聪明过头了J

怎么解决呢?

方法1,先把接口引用置为nil,再释放对象。

    Intf1 := nil;

    OBJ2.Free;

方法2,把接口引用强制转成指针类型再置为nil

    Pointer(Intf1) := nil;

此时相当于直接把地址清零,不会调用_IntfClear

我倾向于使用第二种方法,这样你就不用考虑先释放谁的问题了。而且有些设计模式中你可能只持有接口引用,而且你也不知道引用的对象什么时候释放,此时就必须使用方法2

例如考虑Composite模式。

TComposite = class(TComponent, I1)

Private

    interList: TXContainer;//一个容器类,存放“叶子”的接口引用。

Public

    Procedure Add (AIntf: I1;

    function DO: Boolean;

End;

它应该释放它的“叶子”吗?显然不是,那“叶子”是不是一定会晚于这个“合成对象”对象释放呢?我想也不一定吧。如果强制这样规定的话,就失去很多的灵活性。所以我们肯定想这些接口引用置nil时,不会和原对象发生什么关系,以免对象被释放后出非法地址访问错误。考虑使用什么容器呢?arrayTListTInterfaceList

首先想到肯定是TInterfaceList了,因为我们是要容纳的就是接口。但是对他进行Free时,它会把它所有容纳的接口置为nil,这正是我们不想要的。或者我们可以在Free之前先把它存储的接口引用转为指针再置为nil

  for I := 0 to interList.Count -1 do

Pointer(interList.Items[i]) := nil;

可惜的是,编译错误“[Error] XXXX.pas(XX): Left side cannot be assigned to”。

然后我们试一下array

interList: Array of I1;

动态数组是不要释放的。好像很好用,但是编译器释放它时还是会对每个元素置为nil的,而且是作为接口,仍然有非法地址访问错误的可能。可以这样

  for i := Low(arr) to High(arr) do

Pointer(arr[i]) := nil;

但是这毕竟是违反编码习惯的,而且每次使用都要记得作,不记得作也可能不会马上出错,所以有可能成为隐患。

最后,就是使用TList。不过TList中是指针,所以Add时必须这样

procedure XXX.Add(AIntf: I1)

Begin

    InterList.Add(Pointer(AIntf));

End;

由于它本来就保存的是指针,所以释放时也不需要特殊处理。

好像比较完美,但是还是有一个陷阱,如果你写了这样的代码会怎么样呢?

interList.Add(TC2.Create);

或者

Obj2 := TC2.Create;

interList.Add(Obj2);

错!因为保存的是纯粹的指针,所以转化为接口时,对象引用到接口引用的地址转换没有进行(它也不知道如何进行),所以调用接口声明的方法时就又是一个非法地址访问错误。只能这么写:

interList.Add(Pointer(I1(TC2.Create)));

虽然有些麻烦,相比之下这已是最佳方案(我所知的)了。因为你如果你忘记转会在第一次调用时就出错,比较容易发现错误(相比于使用array)。

所以我建议:

1,  使用Tlist来管理接口引用。

2,  增加时要把对象转型成Interface再转型成Pointer,如果本来就是接口引用的话直接转为Pointer即可。

3,  对于没有使用Tlist来管理的接口引用。对于接口的引用要用下面的方法手动置为nilPointerIntfRef:= nil;

另外,TInterfacedObject也实现了IInterface的三个方法。所以从它继承也可以省去自己实现这三个方法的麻烦。但是它的_Release是这样实现的:

function TInterfacedObject._Release: Integer;

begin

  Result := InterlockedDecrement(FRefCount);

  if Result = 0 then

    Destroy;

end;

我们前边说过,接口引用的置nil会调用接口的_Release,所以有可能会把对象给释放掉,我当时可是被它吓了一跳,多亏我没用过它。也就是这样实现下比从TComponent继承带来更大的麻烦。除非特殊用途,不建议使用。

上面对接口的所有讨论,只限于普通使用的语言级的接口,没有讨论Com+接口。Delphi的这些诡异的接口的实现,和它是从Com+接口发展而来是有很大关系的,也就是由于背负了过重的历史包袱而作出的妥协的结果。

【Delphi】接口类型中的陷阱

function abc(A: Integer): Integer; 这是一个Delphi的函数声明,看上去很简单,只有一个参数而已,但是真实情况呢?在编译成二进制代码后,实际上函数的参数已经有3个了...
  • aqtata
  • aqtata
  • 2014年02月11日 16:08
  • 1727

Delphi接口的底层实现

Delphi接口的底层实现引言       接口是面向对象程序语言中一个很重要的元素,它被描述为一组服务的集合,对于客户端来说,我们关心的只是提供的服务,而不必关心服务是如何实现的;对于服务端的类来说...
  • linzhengqun
  • linzhengqun
  • 2007年03月04日 16:25
  • 6659

Delphi 接口机制真相

接口(interface)在Delphi中是一个很有意思的东西。Delphi 3开始支持接口,从而形成了COM编程的基础;然而,Delphi中的接口也可用在非COM开发中,实现类似抽象类(含有抽象方法...
  • jiangwzh
  • jiangwzh
  • 2012年03月29日 14:41
  • 8151

delphi下的接口编程学习笔记

Delphi下的接口编程     Delphi下的接口编程学习笔记   1.1  ...
  • rocklee
  • rocklee
  • 2015年09月22日 21:12
  • 1467

delphi实现一个类继承抽象类并实现接口

 问题描述:TSaleOutDao即要继承抽象类TABizDao又要实现接口TIBizDao.解决办法:先用抽象类继承TIBizDao,然后再把需要实现的接口方法声明为抽象方法。然后再用TSaleOu...
  • xiao_jun_0820
  • xiao_jun_0820
  • 2008年09月18日 14:29
  • 4259

Delphi中Interface接口的使用方法

  unit Unit1;interfaceuses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms...
  • delphi1234
  • delphi1234
  • 2008年02月20日 18:41
  • 9088

Delphi 接口:两个接口有相同名称的方法

假设有两个接口定义,里面有相同名称的方法。然后有一个类,要同时实现这两个接口。语法上该怎么写才正确? 请看代码: type IMyIntfA = interface ['{03...
  • pcplayer
  • pcplayer
  • 2017年06月28日 14:26
  • 302

SQL备忘-基本控制语句复习及@@ROWCOUNT陷阱纪实

作者fbysssmsn:jameslastchina@hotmail.com  blog:blog.csdn.net/fbysss声明:本文由fbysss原创,转载请注明出处关键字:SQL语句    ...
  • fbysss
  • fbysss
  • 2006年07月08日 10:19
  • 2855

Delphi接口使用实例介绍

对于Object Pascal语言来说,最近一段时间最有意义的改进就是从Delphi3开始支持接口(interface),接口定义了能够与一个对象进行交互操作的一组过程和函数。对一个接口进行定义包含两...
  • chinmusam
  • chinmusam
  • 2011年06月16日 17:37
  • 2581

delphi 陷阱

初学DelphiI的人,由于各种原因,对DelphiI中的许多概念不能很好的理解,并由此带来了许多的问题,或者是开发出的程序稳性不好,一会能运行,一会又不能运行;或者是遇到一个问题久思不得其解,还误以...
  • mengzhongren2
  • mengzhongren2
  • 2011年10月13日 14:12
  • 93
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:Delphi的接口陷阱
举报原因:
原因补充:

(最多只允许输入30个字)