为啥基于 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 连接?