TObject提供了什么保证在对象销毁时清除接口字段?

War*_* P 6 delphi interface delphi-xe2

下面是一些示例代码,它是Delphi中的一个独立控制台应用程序,它创建一个对象,然后创建一个对象,TInterfacedObject并将接口引用分配给TObject中的字段:

program ReferenceCountingProblemProject;
{$APPTYPE CONSOLE}
{$R *.res}
uses
  System.SysUtils;
type
  ITestInterface = interface
     ['{A665E2EB-183C-4426-82D4-C81531DBA89B}']
     procedure AnAction;
  end;
  TTestInterfaceImpl = class(TInterfacedObject,ITestInterface)
     constructor Create;
     destructor Destroy; override;

     // implement ITestInterface:
         procedure AnAction;
  end;

  TOwnerObjectTest = class
     public
         FieldReferencingAnInterfaceType1:ITestInterface;
   end;
   constructor TTestInterfaceImpl.Create;
   begin
     WriteLn('TTestInterfaceImpl object created');
   end;
   destructor TTestInterfaceImpl.Destroy;
   begin
     WriteLn('TTestInterfaceImpl object destroyed');
   end;
   procedure TTestInterfaceImpl.AnAction;
   begin
       WriteLn('TTestInterfaceImpl AnAction');
   end;
procedure Test;
var
  OwnerObjectTest:TOwnerObjectTest;
begin
  OwnerObjectTest := TOwnerObjectTest.Create;
  OwnerObjectTest.FieldReferencingAnInterfaceType1 := TTestInterfaceImpl.Create as ITestInterface;
  OwnerObjectTest.FieldReferencingAnInterfaceType1.AnAction;
  OwnerObjectTest.Free;      // This DOES cause the clearing of the interface fields automatically.
  ReadLn; // wait for enter.
end;
begin
   Test;
end.
Run Code Online (Sandbox Code Playgroud)

我编写这段代码是因为我不确定在简单的例子中,Delphi是否总能清除我的接口指针.这是程序运行时的输出:

TTestInterfaceImpl object created
TTestInterfaceImpl AnAction
TTestInterfaceImpl object destroyed
Run Code Online (Sandbox Code Playgroud)

这是我非常希望看到的输出.我编写这个程序的原因是因为我看到这个"我和Delphi之间的契约"在我正在研究的大型Delphi应用程序中被违反了.我看到对象没有被释放,除非我在析构函数中明确地将它们归零,如下所示:

 destructor TMyClass.Destroy;
 begin
        FMyInterfacedField := nil; // work around leak.
 end;
Run Code Online (Sandbox Code Playgroud)

我相信Delphi正在尽最大努力将这些接口归零,因此,当我在上面的测试代码中的析构函数上设置断点时,我得到了这个调用堆栈:

ReferenceCountingProblemProject.TTestInterfaceImpl.Destroy
:00408e5f TInterfacedObject._Release + $1F
:00408d77 @IntfClear + $13
ReferenceCountingProblemProject.ReferenceCountingProblemProject
Run Code Online (Sandbox Code Playgroud)

正如您所看到的那样,@IntfClear正在生成一个调用,但上面的调用堆栈中缺少"Free"会让我感到困惑,因为看起来这两者是因果关系,而不是直接在彼此的调用路径中.这告诉我,@IntfClear在调用析构函数之后,编译器本身会在我的应用程序中发出一个TObject.Free.我正确地读了这个标志吗?

我的问题是:Delphi的TObject是否始终保证最终确定接口类型的字段?如果没有,我的界面何时会被清除,何时我必须手动清除它?这个接口引用的最终化是作为TObject的一部分实现的,还是作为一些通用编译器范围语义的一部分实现的?事实上,关于什么时候手动将接口清零,以及何时让Delphi为我做这些规则,我应遵循哪些规则?想象一下,我的应用程序中有200多个类(我已经拥有)将Interfaces存储为Fields.我是否在析构函数中将它们全部设置为Nil,或者不是?我该如何决定做什么?

我怀疑是(a)TObject提供了这种保证,附带条件是如果你做了一些愚蠢的事情,并且不知何故不打算在包含接口引用字段的对象上调用TObject.Destroy,你就会泄漏两者,或者( b)低于TObject的编译器提供了这种语义保证,在超出范围的范围内提供了这种语义保证,而这一方面让我感到茫然,无法解释我可能遇到的复杂场景.现实中.

对于琐碎的情况,比如我OwnerObjectTest.Free;从上面的演示中删除的那个,并且你泄漏了演示代码创建的两个对象,我没有问题理解语言/编译器/运行时的行为,但我希望确保我有完全理解对于接口类型的对象中的字段存在什么合同或保证(如果有).

更新通过单步执行并声明我自己的析构函数,我能够得到一个不同的调用堆栈,这更有意义:

ReferenceCountingProblemProject.TTestInterfaceImpl.Destroy
:00408e5f TInterfacedObject._Release + $1F
:00408d77 @IntfClear + $13
:00405483 TObject.Free + $B
ReferenceCountingProblemProject.ReferenceCountingProblemProject
Run Code Online (Sandbox Code Playgroud)

这似乎表明@IntfClear调用了BY TObject.Free,这是我非常期待看到的.

Dav*_*nan 1

当执行对象的析构函数时,对象实例的所有字段都将被最终确定。这是由运行时保证的。事实上,托管类型的所有字段都在销毁时最终确定。

对于此类引用计数对象没有被销毁的可能解释是:

  1. 析构函数没有被执行,或者
  2. 其他东西持有对该对象的引用。