这是一些示例代码,它是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.
我编写了这段代码,因为我不确定在简单的例子中,Delphi是否总能清除我的接口指针。这是程序运行时的输出:
TTestInterfaceImpl object created
TTestInterfaceImpl AnAction
TTestInterfaceImpl object destroyed
这是我非常希望看到的输出。我编写这个程序的原因是因为我看到这个“我和Delphi之间的契约”在我正在研究的大型Delphi应用程序中被违反了。我看到对象没有被释放,除非我在我的析构函数中明确地将它们归零:
destructor TMyClass.Destroy;
begin
FMyInterfacedField := nil; // work around leak.
end;
我的信念是Delphi正在尽最大努力将这些接口归零,因此,当我在上面的测试代码中的析构函数上设置断点时,我得到了这个调用堆栈:
ReferenceCountingProblemProject.TTestInterfaceImpl.Destroy
:00408e5f TInterfacedObject._Release + $1F
:00408d77 @IntfClear + $13
ReferenceCountingProblemProject.ReferenceCountingProblemProject
正如您所看到的,正在生成对@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
这似乎表明@IntfClear
是由TObject.Free
调用的,这是我非常期待看到的。
答案 0 :(得分:1)
执行对象的析构函数时,最终确定对象实例的所有字段。这是由运行时保证的。实际上,托管类型的所有领域都是在销毁时最终确定的。
此类引用的可能解释是计算未被销毁的对象: