【发布时间】:2018-07-06 15:33:05
【问题描述】:
我当前的代码如下所示:
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.
我在应用程序的最后一个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 一次,这很好。
- 树被 _AddRefed 一次,从不 _Released,这不是我所期望的。
- Catalog.View 被 _AddRefed 4 次和 _Released 3 次,这也不符合我的预期。
为什么会这样?我在某处缺少参考循环吗?
【问题讨论】:
-
在单个 dpr 文件中是否不可能将其缩减为最小的、没有 UI 的东西。
-
@DavidHeffernan 我试过了,但如果我删除更多代码,问题就会消失。
-
主要问题是您的表单有两个所有者 - Application 和 SharedPtr。由于匿名方法变量捕获,应用程序释放表单并留下悬空引用的 SharedPtr。本质上,您的表单被发布了两次。
-
现在,您可以修复该代码中的直接问题,但不能。那个代码太可怕了。相信我,你不想在生产中使用类似的东西;)
-
另一个提示。调试时,不要调试引用计数——这绝对不会告诉你什么。将您的日志信息放入析构函数中,您会立即清楚哪些内容被多次发布或以错误的顺序发布。
标签: delphi reference-counting delphi-10.2-tokyo