gpt4 book ai didi

delphi - 为什么方法退出时没有正确释放这个接口(interface)?

转载 作者:行者123 更新时间:2023-12-02 15:49:25 25 4
gpt4 key购买 nike

我当前的代码如下所示:

program Project1;

{$APPTYPE CONSOLE}
{$R *.res}

uses
Winapi.Windows,
System.Generics.Collections,
System.SysUtils;

type
TForm1 = class
public
Events: TList<TProc>;
constructor Create;
destructor Destroy; override;
end;

TTracingInterfacedObject = class(TInterfacedObject)
public
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;
end;

ISharedPtr<T> = interface
['{CC9EE6C5-F07B-40E5-B05D-2DFDBD3404A1}']
function Get: T;
function GetRefCount: Integer;
end;

ICatalog = interface
['{F421BBA8-8DA3-47EE-ADB9-DED26747472E}']
function GetView: ISharedPtr<TForm1>;
property View: ISharedPtr<TForm1> read GetView;
end;

ITree = interface
['{A1E2F71B-124B-48DB-B038-5F90AC5BE94B}']
function GetId: TGUID;
property Id: TGUID read GetId;
end;

TSharedPtr<T: class> = class(TTracingInterfacedObject, ISharedPtr<T>)
private
FObject: T;
public
constructor Create(const AObject: T);
destructor Destroy; override;
function GetRefCount: Integer;
function Get: T;
end;

TCatalog = class(TTracingInterfacedObject, ICatalog)
private
FView: ISharedPtr<TForm1>;
public
constructor Create;
function GetView: ISharedPtr<TForm1>;
end;

TTree = class(TTracingInterfacedObject, ITree)
private
FView: ISharedPtr<TForm1>;
public
constructor Create(const AView: ISharedPtr<TForm1>);
function GetId: TGUID;
end;

function TTracingInterfacedObject._AddRef: Integer;
begin
OutputDebugString(PChar(ClassName + '._AddRef'));
Result := inherited _AddRef;
end;

function TTracingInterfacedObject._Release: Integer;
begin
OutputDebugString(PChar(ClassName + '._Release'));
Result := inherited _Release;
end;

constructor TForm1.Create;
begin
inherited;
Events := TList<TProc>.Create;
end;

destructor TForm1.Destroy;
begin
Events.Free;
inherited;
end;

constructor TSharedPtr<T>.Create(const AObject: T);
begin
inherited Create;
FObject := AObject;
end;

destructor TSharedPtr<T>.Destroy;
begin
FObject.Free;
inherited;
end;

function TSharedPtr<T>.Get: T;
begin
Result := FObject;
end;

function TSharedPtr<T>.GetRefCount: Integer;
begin
Result := FRefCount;
end;

constructor TCatalog.Create;
begin
inherited Create;
FView := TSharedPtr<TForm1>.Create(TForm1.Create) as ISharedPtr<TForm1>;
end;

function TCatalog.GetView: ISharedPtr<TForm1>;
begin
Result := FView;
end;

constructor TTree.Create(const AView: ISharedPtr<TForm1>);
begin
inherited Create;
FView := AView;
end;

function TTree.GetId: TGUID;
begin
Result := TGUID.Empty;
end;

procedure Main;
var
Catalog: ICatalog;
Tree: ITree;
Func: TFunc<TGUID>;
Events: TList<TProc>;
Event: TProc;
begin
Catalog := TCatalog.Create as ICatalog;

Events := Catalog.View.Get.Events;

Event := procedure
begin
end;

Events.Add(Event);

Tree := TTree.Create(Catalog.View) as ITree;

Func := function: TGUID
begin
Result := Tree.Id;
end;
end;

begin
Main;

end.

我在应用程序的最后结束处设置了一个断点。

当时的事件日志如下所示:

Debug Output: TSharedPtr<Project1.TForm1>._AddRef Process Project1.exe (3456)
Debug Output: TCatalog._AddRef Process Project1.exe (3456)
Debug Output: TSharedPtr<Project1.TForm1>._AddRef Process Project1.exe (3456)
Debug Output: TSharedPtr<Project1.TForm1>._AddRef Process Project1.exe (3456)
Debug Output: TSharedPtr<Project1.TForm1>._AddRef Process Project1.exe (3456)
Debug Output: TTree._AddRef Process Project1.exe (3456)
Debug Output: TSharedPtr<Project1.TForm1>._Release Process Project1.exe (3456)
Debug Output: TSharedPtr<Project1.TForm1>._Release Process Project1.exe (3456)
Debug Output: TCatalog._Release Process Project1.exe (3456)
Debug Output: TSharedPtr<Project1.TForm1>._Release Process Project1.exe (3456)
Source Breakpoint at $0047F675: C:\Users\Admin\Documents\Embarcadero\Studio\Projects\ViewFail\Project1.dpr line 168. Process Project1.exe (3456)

所以:

  • 目录_AddRefed 一次并_Released 一次,这很好。
  • Tree _AddRefed 一次,从未_Released,这不是我所期望的。
  • Catalog.View _AddRefed 四次,_Released 仅 3 次,这也不是我所期望的。

为什么会发生这种情况?我是否缺少某个引用循环?

最佳答案

是的,您的代码中有一个引用循环。它是通过匿名方法变量捕获机制创建的。

匿名方法由引用计数、编译器生成的类支持。匿名方法捕获的任何变量都作为字段存储在同一类中。只要匿名方法在范围内,编译器就会实例化该类的实例并使其保持事件状态。

现在,上述事实不足以形成循环。但是相同的实例(相同的类)将用于备份某个例程中的所有匿名方法。

转换为您的代码:

  • TForm1 保存事件
  • Catalog 包含 TForm1
  • Tree 包含 TForm1

那里没有循环 - Tree 没有引用 CatalogCatalog 也没有引用 Tree

但是,当您查看 Main 过程时,情况会发生变化。

Main 中的匿名方法将由隐藏对象实例支持 - 所以让我们看看那里会有什么:

  • 第一个匿名方法
  • 第二种匿名方法
  • Tree - 由第二个匿名方法捕获

仍然没有可见的循环 - 但是,您将第一个匿名方法 Event 添加到 Tree 保存的 Events 列表中。为了使该方法保持事件状态,整个支持对象也将保持事件状态。

Anonymous method object -> Event 
-> Tree -> Events -> Event -> Anonymous method object

要打破这个循环,您必须清除一些引用。例如,在 Main 中的某处将 Tree 设置为 nil

关于delphi - 为什么方法退出时没有正确释放这个接口(interface)?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51213638/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com