Delphi接口和编程两大陷阱_第1页
Delphi接口和编程两大陷阱_第2页
Delphi接口和编程两大陷阱_第3页
Delphi接口和编程两大陷阱_第4页
Delphi接口和编程两大陷阱_第5页
已阅读5页,还剩9页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

1、Delphi 接口和编程两大陷阱Delphi 接口和编程两大陷阱 2012-06-01 00:20 150 人阅读 评论 (1) 收藏举报Delphi 接口编程的两大陷阱前一阵写了一个通过接口扩展功能的例子,当时由于指针和 接口的转换,导致了很多错误,最近又接触到了一个类和接 口混用的例子,导致程序的指针在传递中变了地址或者内 容,导致读到了错误的地址,现在将接口和类之间的情况进 行一下汇总。陷阱一、接口的类型转换陷阱 1) 不能把一个对象引用强制转换成这个引用的类型没 有声明实现的接口,即使这个对象实际实现了这个接口呵 呵,优点拗口 。 2) 当把一个对象变量赋给一个接口变量, 在把这个接

2、口变量赋还给对象变量时,这个对象变量的地址已经变了,也就是不再是原来的对象了,而是指向一个错误的地址 例如:I1 = interfacefunction Do: Boolean;end;TC1 = classATT1: 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 没有声明实现

3、I1 所以不能转换成 I1 , 即使 OBJ1 确实实现了 I1。如果把对象转为接口再转回来也会有问题。OBJ2 := TC2.Create;OBJ2.ATT1 := 0;Intf1 := OBJ2;/ 正确。TC2(Intf1).ATT1 := 0; / 运行期非法地址访问错误。OBJ2.ATT1 := 0; / 运行期非法地址访问错误。也就是,从对象引用转换成指针引用后,地址改变了,但是 由指针引用再转回对象引用时地址没有变回来 Delphi 的 bug?。陷阱二、接口的生存期管理 我认为接口是不需要生存期管理的,因为接口根本不可能生 成真正的对象。但是 Delphi 却又一次打击了我的常

4、识咦, 为什么要说“又”呢? ,它的接口是有生存期的,而且必 须实现以下三个方法:function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;function _AddRef: Integer; stdcall;function _Release: Integer; stdcall;每次都要实现这三个方法是比较麻烦的,而且更重要的是,我不知道 Delphi 什么时候用以及怎么用这三个方法?所以 我也不知道怎么实现这三个方法。如果不想自己实现这三个方法,你可以使用TComponent 。因为 TComponent 已经

5、实现了这三个方法,所以可以从它继 承,就不用实现这三个方法了。这样就可以放心使用了吗?答案是否认的。 因为 Delphi 在你 把接口变量置为 nil 时偷偷的因为很出乎我的意料调用 了 _Releaseofunction _IntfClear(var Dest: IInterface): Pointer;varP: Pointer;begin if Dest nil thenbeginP := Pointer(Dest);Pointer(Dest) := nil;IInterface(P)._Release;end;end;而Release 时又做了什么呢?function TCompone

6、nt._Release:Integer;beginif FVCLComObject = nil thenelseResult := IVCLComObject(FVCLComObject)._Release;end;不是 Com 对象的话,就什么也没作。我们作的不是 Com 对 象,是不是就没有任何问题了呢?答案依然是否认的,考虑 如下情况: OBJ2 := TC2.Create;tryIntf1 := OBJ2;Intf1.DO;FinallyOBJ2.Free;End;会怎么样呢?会出非法地址访问错误。为什么?上面说过把 接口引用设为 nil 时,会调用 _IntfClear ,而 _In

7、tfClear 又会调 用对象的Release,而这时这个对象已经释放了,自然就出 非法地址访问错误啦。有人说多此一举吗,接口引用只是个地址,没必要手动设为nil 。OBJ2 := TC2.Create;tryIntf1 := OBJ2;Intf1.DO;FinallyOBJ2.Free;End;结果可能还会出你的意料,还是非法地址访问错误。为什 么?因为 Delphi 编译器耍了个小聪明, 它认为你忘记把这个 地址引用置为 nil 了,所以你会自动给你加上,看来 Delphi 编译器聪明过头了 J。怎么解决呢?方法 1,先把接口引用置为 nil ,再释放对象。Intf1 := nil;OBJ

8、2.Free;方法 2,把接口引用强制转成指针类型再置为nil 。Pointer(Intf1) := nil;此时相当于直接把地址清零,不会调用 _IntfClear 。我倾向于使用第二种方法,这样你就不用考虑先释放谁的问 题了。而且有些设计模式中你可能只持有接口引用,而且你 也不知道引用的对象什么时候释放, 此时就必须使用方法 2例如考虑 Composite 模式。TComposite = class(TComponent, I1)PrivateinterList: TXContainer;/ 一个容器类 ,存放“叶子”的接口引用PublicProcedure Add (AIntf: I1

9、;function DO: Boolean;End;它应该释放它的“叶子”吗?显然不是,那“叶子”是不是 一定会晚于这个 “合成对象” 对象释放呢?我想也不一定吧。 如果强制这样规定的话,就失去很多的灵活性。所以我们肯 定想这些接口引用置 nil 时,不会和原对象发生什么关系, 以免对象被释放后出非法地址访问错误。考虑使用什么容器 呢? array? TList ? TInterfaceList ?首先想到肯定是 TInterfaceList 了,因为我们是要容纳的就是 接口。但是对他进行 Free 时,它会把它所有容纳的接口置为 nil ,这正是我们不想要的。 或者我们可以在 Free 之前

10、先把它 存储的接口引用转为指针再置为 nil 。for I := 0 to interList.Count -1 doPointer(interList.Itemsi) := nil;可惜的是,编译错误“ Error XXXX.pas(XX): Left side cannot be assigned to”。然后我们试一下 array。interList: Array of I1;动态数组是不要释放的。好似很好用,但是编译器释放它时 还是会对每个元素置为 nil 的,而且是作为接口,仍然有非 法地址访问错误的可能。可以这样for i := Low(arr) to High(arr) doPo

11、inter(arri) := nil;但是这毕竟是违反编码习惯的,而且每次使用都要记得作, 不记得作也可能不会马上出错,所以有可能成为隐患。最后,就是使用 TList 。不过 TList 中是指针, 所以 Add 时必 须这样procedure XXX.Add(AIntf: I1)BeginInterList.Add(Pointer(AIntf);End;由于它本来就保存的是指针,所以释放时也不需要特殊处理。好似比较完美,但是还是有一个陷阱,如果你写了这样的代 码会怎么样呢?interList.Add(TC2.Create);或者Obj2 := TC2.Create;interList.Add

12、(Obj2);错!因为保存的是纯粹的指针,所以转化为接口时,对象引用到接口引用的地址转换没有进行它也不知道如何进行所以调用接口声明的方法时就又是一个非法地址访问错误。 只能这么写:interList.Add(Pointer(I1(TC2.Create);虽然有些麻烦, 相比之下这已是最正确方案 我所知的 了因为你如果你忘记转会在第一次调用时就出错,比较容易发 现错误相比于使用 array。1、使用 Tlist 来管理接口引用。2、增加时要把对象转型成 Interface 再转型成 Pointer, 如果本来就是接口引用的话直接转为 Pointer 即可。3、对于没有使用 Tlist 来管理的接

13、口引用。对于接口的 引用要用下面的方法手动置为nil : Pointer IntfRef := nil;另外,TlnterfacedObject也实现了 llnterface的三个方法。所以从它继承也可以省去自己实现这三个方法的麻烦。但是它的_Release是这样实现的:function TlnterfacedObject._Release: lnteger;beginResult := lnterlockedDecrement(FRefCount);if Result = 0 thenDestroy;end;接口引用的置nil会调用接口的Release,所以有可能会把对 象给释放掉,我当时可是被它吓了一跳,多亏我没用过它。 也就是这样实现下比从 TComponent 继承带来更大的麻烦。 除非特殊用途,不建议使用。上面对接口的所有讨论,只限于普通使用的语言级的接口, 没有讨论 Com+

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

最新文档

评论

0/150

提交评论