Почему этот интерфейс не был правильно выпущен при выходе из метода?

Мой текущий код выглядит следующим образом:

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 ) Отладочный вывод : TCatalog ._ AddRef Process Project1 . ехе ( 3456 ) Debug Output : TSharedPtr < Project1 . TForm1 > ._ AddRef Process Project1 . ехе ( 3456 ) Debug Output : TSharedPtr < Project1 . TForm1> ._ AddRef Process Project1 . ехе ( 3456 ) Debug Output : TSharedPtr < Project1 . TForm1 > ._ AddRef Process Project1 . exe ( 3456 ) Отладочный вывод : TTree ._ AddRef Process Project1 . ехе ( 3456 ) Debug Output : TSharedPtr < Project1 . TForm1 > ._ Процесс выпуска Project1 . ехе ( 3456 ) Debug Output : TSharedPtr < Project1 . TForm1 > ._ Процесс выпуска Project1 . exe ( 3456 ) Debug Output : TCatalog ._ Процесс выпуска Project1 . ехе ( 3456 ) Debug Output : TSharedPtr < Project1 . TForm1 > ._ Процесс выпуска Project1 . ехе ( 3456 ) Источник Breakpoint на $ 0047F675 : C : Пользователи Администратор Documents Embarcadero Студия Проекты ViewFail Проект1 . dpr line 168. Process Project1 . Пример exe ( 3456 ) выглядит следующим образом:

TForm1

Так:

  • События _AddRefed один раз и _Released один раз, что хорошо.
  • Каталог _AddRefed один раз и никогда _Released, что не то, что я ожидал.
  • TForm1.View - _AddRefed четыре раза и _Released только три раза, что также не то, что я ожидал.

Почему это происходит? Есть ли где-то ссылочный цикл, который мне не хватает?

delphi,reference-counting,delphi-10.2-tokyo,

2

Ответов: 1


5 принят

Да, в вашем коде есть ссылочный цикл. Он создается с помощью анонимного метода захвата переменной метода.

Анонимные методы поддерживаются ссылочным подсчетом, сгенерированным компилятором. Любые переменные, захваченные анонимным методом, сохраняются как поля в одном классе. Компилятор создает экземпляр этого класса и сохраняет его в памяти, пока анонимный метод находится в области видимости.

Теперь для создания цикла было бы недостаточно данных. Но тот же экземпляр (тот же класс) будет использоваться для резервного копирования всех анонимных методов в рамках некоторой процедуры.

Переведено на ваш код:

  • Tree держит TForm1
  • Tree держит Catalog
  • Catalog держит Tree

Никаких циклов там - Mainне ссылается Catalog, ни CatalogссылкиMain

Но, когда вы смотрите на свою Treeпроцедуру, все меняется.

Анонимные методы Eventбудут поддерживаться экземпляром скрытого объекта, поэтому давайте посмотрим, что будет там:

  • Первый анонимный метод
  • Второй анонимный метод
  • Events - захвачен вторым анонимным методом

Все еще нет видимых циклов, но затем вы добавляете первый анонимный метод Eventв Treeсписок, который удерживается . Чтобы сохранить этот метод в живых, весь поддерживающий объект также будет сохранен.Anonymous method object -> Event -> Tree -> Events -> Event -> Anonymous method object

Tree

Чтобы разорвать этот цикл, вам нужно очистить некоторые ссылки. Например установить , Treeчтобы nilгде - нибудь в Main.

Дельфы, подсчет ссылок, Дельфы-10.2-Токио,
Похожие вопросы