为什么基于TComponent泄漏内存的接口实现?

时间:2010-02-02 08:10:14

标签: delphi interface memory-leaks delphi-2009

此Delphi代码将显示TMyImplementation实例的内存泄漏:

program LeakTest;

uses
  Classes;

type
  MyInterface = interface
  end;

  TMyImplementation = class(TComponent, MyInterface)
  end;

  TMyContainer = class(TObject)
  private
    FInt: MyInterface;
  public
    property Impl: MyInterface read FInt write FInt;
  end;

var
  C: TMyContainer;
begin
  ReportMemoryLeaksOnShutdown := True;

  C := TMyContainer.Create;
  C.Impl := TMyImplementation.Create(nil);
  C.Free;
end.

如果TComponent被TInterfacedObject替换并且构造函数更改为Create(),则泄漏消失。与TComponent有什么不同?

非常感谢答案。总结一下:说“如果你使用接口,它们是引用计数,因此它们可以为你释放,这很容易,但却是错误的。” - 实际上任何实现接口的类都可以破坏这个规则。 (并且不会显示编译器提示或警告。)

3 个答案:

答案 0 :(得分:28)

实施方面的差异

  • TComponent._Release 不会释放您的实例。
  • TInterfacedObject._Release 免费您的实例。

也许有人可以插话,但我对此的看法是TComponent并不打算用作我们通常使用界面的方式作为参考计数对象。

TComponent._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;

答案 1 :(得分:20)

TComponent没有像TInterfacedObject那样实现其_AddRef和_Release方法。它将其引用计数推迟到其VCLComObject属性,该属性应该是一些其他接口对象。由于TComponent不计算引用,因此无法检测其引用计数何时达到零,因此它不会自行释放。

VCLComObject属性包含一个接口引用,该引用应该实现IVCLComObject。如果组件的关联VCLComObject对象已被告知它拥有该组件,那么当该接口的引用计数达到零时,它将销毁其关联的组件。它通过调用FreeOnRelease方法告诉它拥有该组件。

所有这些都旨在使VCL组件更容易包装到COM对象中。如果那不是你的目标,那么你可能会在此过程中与其他几个意想不到的设计方面作斗争,所以你可能希望重新评估你的动机,让你的组件首先实现接口。

答案 2 :(得分:6)

组件应该由其他东西(通常是表单)拥有和销毁。在该场景中,不使用引用计数。如果将组件作为接口引用传递,那么在方法返回时它被销毁将是非常不幸的。

因此,TComponent中的引用计数已被删除。