网上有篇文章《Delphi接口编程的两大陷阱》,里面提到接口的生存期管理的问题。但该文章里面提到的两个问题,其实都是对 Delphi 不理解导致的。
 
先说该篇文章中提到的第一个问题为什么是该文章作者不理解 DELPHI 导致他认为那是不可理解的陷阱。然后俺再来重点解释接口的生命周期管理。
 
一. 接口 - 对象。
假设有接口定义:
IMyTask = interface
  procedure SayHello;
end;
然后有个类实现了该接口:
TMyClass = class(TComponent, IMyTask)
public
  procedure SayHello;
end;
 
然后有个该类的对象实例:MyObj := TMyClass.Create(Application); 和一个接口变量定义 MyIntf: IMyTask;
这时候,按 DELPHI 的语法规则,当然可以这样做: 
MyIntf := MyObj as IMyTask;
也可以不用 AS 这样写:MyIntf := MyObj;
其实还有很多其它写法。总之,这里是从 MyObj 对象实例,取得它实现的接口的指针!而不是做类型转换!
前面提到的那篇文章的作者却以为这样做是【类型转换】,因此他就试图通过类型转换来做:MyObj := MyIntf...这样做当然是不行的!因为他的理解错误,使得他认为那是个陷阱。
事实上,一个对象可以实现多个接口。比如 TComponent 肯定也实现了IInterfaceComponentReference 接口。因此,我们还可以:
AInft: IInterfaceComponentReference;
AIntf := MyObj as IInterfaceComponentReference;
注意到没,一个对象可以拥有多个接口,因此,获取的该对象的接口的指针,肯定不是指向该对象的。否则两个接口的指针就打架了!所以,这里不能直接做类型转换,把指针转为对象!
那么,当我们有一个接口,如何获得它所在的对象呢?TComponent 实现的 IInterfaceComponentReference  接口刚好就帮我们实现了这个:function GetComponent: TComponent;
因此,如果是继承自 TComponent 的对象,它肯定有实现 IInterfaceComponentReference接口。因此,上面的 MyIntf: IMyTask 这个接口就可以通过以下方式获得它的对象指针:
AObj : TComponent;
AObj := (MyIntf as IInterfaceComponentReference).GetComponent;
 
------------------------------------------------------------
二. 接口生存周期的管理:
在 Delphi 里面使用接口,一个实现某个接口的类,必须实现 IInterface 接口的三个方法。
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;
上述3个方法里面,_Release 方法就负责释放对象自己。
当然,我们自己写一个类的时候,不用自己去实现这三个方法,只要我们的类从 TInterfacedObject 继承。TInterfacedObject 类已经实现了上述三个方法。
继承自 TInterfacedObject 的类,其对象被创建后,要使用该对象的方法、属性,俺隆重建议你引用该对象的接口,而不是引用该对象的对象实例。因为 TInterfacedObject 类里面已经写好,当接口引用计数为 0 的时候,自动释放该对象本身。也就是说,你的程序里到处使用该对象的某个接口,只要你想释放该对象,则只需要将对该接口的引用设置为 nil,也就是只要没有任何变量引用到该接口,该接口对应的对象实例会自动被释放掉。这样一来,你就不会有内存泄漏的问题了!
但是.......但是.......但是来了,需要注意的事情来了!
如果,你的类,是从 TComponent 类继承下来的,而且实现了你自己写的某个接口..........
TComponent 类的对象实例的接口引用为 0 的时候,并不会释放对象实例自己!你必须自己去释放对象本身,调用对象的 Free 方法。
这还是比较简单的概念。麻烦的是,如果你的对象A,拥有另外一个对象B的接口引用,当对象A被释放的时候,A内部的接口引用自然变成 nil,则会导致对象B内部的引用计数减一。问题是,如果在这之前,对象B已经被 FREE 了,这时候就会出现 AV 错误。
因此,这时候,一定要注意释放顺序!
当你给某个 TForm 的子类比如 TForm2 增加一个你自己定义的接口的时候,这样的错误就容易出现了。
以下是俺对该使用场景做的总结,此总结经过俺自己写代码测试确认过:
1. 如果该 Form 是属于 Application 的,则程序退出时,该 FORM 比主FORM先被消灭;
  2. 如果主 FORM 里面引用了该 FORM 实现的接口,则主 FORM 被消灭时,
     delphi 内部会自动将该接口引用置为 nil,导致 nil 了一个对象已经不存在的接口,
     导致 AV 错误。
  3. 但是,如果运行期,用按钮释放该 FORM,然后再用按钮事件设置该接口为 nil,
     也就是设置了一个对象不存在的接口为 nil,并不会导致 AV 错误;
  4. 实现接口的 FORM,运行期如果只释放其接口,其对象实例不会被释放
    (好像继承自 TComponent 的类都是这样的)
  5. 再次测试,如果被创建的有接口的 Form 不属于 Application 而是其 Owner 是创建它的主 Form,
     则不需要在主FORM被销毁之前,做任何释放它的接口的动作,也不会出现 AV 错误。
 
再次重复一下上面的说法:
因此,程序退出时,要在主 FORM 里面的 OnClose 里面主动释放接口。
  这里可以不用释放 FForm,等程序真正关闭时由 Application 来自动释放这个 Form。
  也就是说,在这个 Form 释放前,必须先释放它被引用的接口。
  这段测试也说明,主FORM的 OnClose 比其它 Form 的被释放更早一步执行。
  一个普通的 Delphi 程序在关闭程序的时候,大概的执行顺序:
  关闭主 FORM - 主 FORM.OnClose -- 其它 FORM 逐个被销毁 -- 主 FORM 被销毁。
 
总结:一定要注意释放顺序。而释放顺序要遵循的原理,则是 TComponent 的子类,释放完接口,对象不会自动释放,必须主动释放对象;但对象被释放以后再释放引用它的接口(比如 MyIntf := nil; )则会因为释放接口会使得执行对象内部引用计数减一,而该对象又已经不存在,去执行一个不存在的对象内的代码,导致 AV 错误!

http://blog.sina.com.cn/s/blog_44fa172f0102wl1o.html

Delphi 接口使用中,对象生命周期管理,如何释放需要注意的问题的更多相关文章

  1. SkylineGlobe 如何实现工程进度管理或者说是对象生命周期管理

    SkylineGlobe 的 TerraExplorer Pro里面,给我们提供了一个Timespan Tags工具,通过这个工具,我们可以设置ProjectTree任务组对象的生命周期: 然后通过调 ...

  2. ASP.NET Core Web API下事件驱动型架构的实现(二):事件处理器中对象生命周期的管理

    在上文中,我介绍了事件驱动型架构的一种简单的实现,并演示了一个完整的事件派发.订阅和处理的流程.这种实现太简单了,百十行代码就展示了一个基本工作原理.然而,要将这样的解决方案运用到实际生产环境,还有很 ...

  3. Tomcat中组件的生命周期管理公共接口Lifecycle

    Tomcat的组件都会实现一个Lifecycle接口,以方便组件的生命周期的统一管理 interface Lifecycle 组件生命周期中主要的几个方法 增加监听器,事件委托机制 public vo ...

  4. ASP.NET Core中的依赖注入(4): 构造函数的选择与服务生命周期管理

    ServiceProvider最终提供的服务实例都是根据对应的ServiceDescriptor创建的,对于一个具体的ServiceDescriptor对象来说,如果它的ImplementationI ...

  5. .Net组件程序设计之对象生命周期

    .Net组件程序设计之对象生命周期 .NET 垃圾回收 IDisposable() Using语句 .NET 垃圾回收 是CLR管理着垃圾回收器,垃圾回收器监控着托管堆,而我们使用的对象以及系统启动是 ...

  6. Castle IOC容器组件生命周期管理

    主要内容 1.生命处理方式 2.自定义生命处理方式 3.生命周期处理 一.生命处理方式 我们通常创建一个组件的实例使用new关键字,这样每次创建出来的都是一个新的实例,如果想要组件只有一个实例,我们会 ...

  7. 依赖注入及AOP简述(十一)——生命周期管理 .

    2.     生命周期管理 各种依赖注入框架提供了替开发者管理各种Scope的便利功能,随之而来的就必然是被管理的依赖对象的生命周期管理的问题.所谓生命周期管理,就是一个对象在它所属的Scope中从被 ...

  8. Akka(2):Actor生命周期管理 - 监控和监视

    在开始讨论Akka中对Actor的生命周期管理前,我们先探讨一下所谓的Actor编程模式.对比起我们习惯的行令式(imperative)编程模式,Actor编程模式更接近现实中的应用场景和功能测试模式 ...

  9. TOMCAT源码分析——生命周期管理

    前言 从server.xml文件解析出来的各个对象都是容器,比如:Server.Service.Connector等.这些容器都具有新建.初始化完成.启动.停止.失败.销毁等状态.tomcat的实现提 ...

随机推荐

  1. NDK 配置及简单项目

    转载请标明出处:http://blog.csdn.net/xx326664162/article/details/50998720 文章出自:薛瑄的博客 你也能够查看我的其它同类文章,也会让你有一定的 ...

  2. 《从零開始学Swift》学习笔记(Day 71)——Swift与C/C++混合编程之数据类型映射

    原创文章.欢迎转载.转载请注明:关东升的博客 posted @ 2017-07-21 13:23 zhchoutai 阅读(...) 评论(...) 编辑 收藏

  3. 【codeforces 757A】Gotta Catch Em' All!

    time limit per test1 second memory limit per test256 megabytes inputstandard input outputstandard ou ...

  4. C++ 与 Java 语言对比

    1 . Java 是完全封装的,而 C++ 的函数是可以定义在 Class 的外部的.从这里就可以看出 C++ 的 OO 思想不够彻底,至少在封装这一点上. 2. C++ 中有拷贝构造函数,可以把一个 ...

  5. erlang中变量作用域

    http://erlangdisplay.iteye.com/blog/315452 _开头(包括_)在erlang可以是表明,这个变量可以存任意东西,就是我们常说的全匹配,_A一般来说就是表明这个东 ...

  6. 谷歌 AI 中国中心成立,人工智能势不可挡?

    昨日,谷歌在上海举办了一年一度的Google中国开发者大会.在本届大会上,谷歌云首席科学家李飞飞宣布了一个重磅消息,即在北京将成立谷歌AI中国中心.对于这个即将成立的AI中心谷歌寄予厚望,希望与中国本 ...

  7. JVM性能调优实践——JVM篇

    前言 在遇到实际性能问题时,除了关注系统性能指标.还要结合应用程序的系统的日志.堆栈信息.GClog.threaddump等数据进行问题分析和定位.关于性能指标分析可以参考前一篇JVM性能调优实践-- ...

  8. Opencv均值漂移pyrMeanShiftFiltering彩色图像分割流程剖析

    meanShfit均值漂移算法是一种通用的聚类算法,它的基本原理是:对于给定的一定数量样本,任选其中一个样本,以该样本为中心点划定一个圆形区域,求取该圆形区域内样本的质心,即密度最大处的点,再以该点为 ...

  9. Static静态变量和非静态变量

    Static静态变量:   不同的对象共享这个变量的存储空间 而不是静态变量   每个对象具有可变的存储器空间 public class StaticDemo { private int count= ...

  10. AndroidStudio封装SDK的那些事

    来自自己简书博客:原文地址:https://www.jianshu.com/p/4d092c915ef1 首先SDK是提供给别人调用的工具.所以常见的SDK都是以jar包,so库,aar包等方式导入A ...