此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有什么不同?
非常感谢答案。总结一下:说“如果你使用接口,它们是引用计数,因此它们可以为你释放,这很容易,但却是错误的。” - 实际上任何实现接口的类都可以破坏这个规则。 (并且不会显示编译器提示或警告。)
答案 0 :(得分:28)
TComponent._Release
不会释放您的实例。 TInterfacedObject._Release
免费您的实例。也许有人可以插话,但我对此的看法是TComponent
并不打算用作我们通常使用界面的方式作为参考计数对象。
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中的引用计数已被删除。