为啥基于 TComponent 的接口实现会泄漏内存?

Posted

技术标签:

【中文标题】为啥基于 TComponent 的接口实现会泄漏内存?【英文标题】:Why do interface implementations based on TComponent leak memory?为什么基于 TComponent 的接口实现会泄漏内存? 【发布时间】:2011-01-12 01:28:49 【问题描述】:

此 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 有什么不同?

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

【问题讨论】:

【参考方案1】:

实现上的差异

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;

【讨论】:

正确! TComponent 的生命周期通常由其所有者控制。 换句话说,选择一个语义规则集并遵循它。如果您从 TComponent 继承,则您的对象将告诉全世界,以及使用它的开发人员,“我是 TComponent”。如果你从 TComponent 继承然后你修复 _Release 让它使用接口对象内存语义,你会混淆每个人。如果有人将您的组件添加到表单中,那将是一团糟。 @Warren:有关类似组件的示例实现(在本例中为 TInterfacedDataModule),请阅读我在 2009 年发布的这篇博客文章:wiert.wordpress.com/2009/08/10/…【参考方案2】:

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

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

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

【讨论】:

【参考方案3】:

一个组件应该被其他东西拥有和销毁,通常是一个表单。在这种情况下,不使用引用计数。如果您将组件作为接口引用传递,那么如果在方法返回时它被销毁,那将是非常不幸的。

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

【讨论】:

以上是关于为啥基于 TComponent 的接口实现会泄漏内存?的主要内容,如果未能解决你的问题,请参考以下文章

为啥 TcpListener 会泄漏 ESTABLISHED 连接?

为啥苹果提供的 SimpleFTPSample 会泄漏内存?

为啥存档时我的 NSURLResponse 会泄漏内存?

为啥 `-[UILabel setText:]` 会泄漏?

为啥使用“新”会导致内存泄漏?

为啥 QApplication 会出现内存泄漏?