В Delphi, почему передача переменной Interface иногда требует, чтобы она была параметром const?

Сначала вопрос: Почему удаление const в UnregisterNode() приводит к сбою, а в RegisterNode() - нет.

Теперь предыстория: Я работаю в Delphi XE с интерфейсами и столкнулся с артефактом, который заставил меня задуматься, и я пришел к выводу, что не очень понимаю почему.

Объект, к которому обращаются как к интерфейсу, не нужно явно освобождать. Когда последняя ссылка выходит из области видимости, он уничтожается. Это кажется достаточно простым. Я написал тестовый пример, чтобы показать вариации, которые выполняются, как ожидалось, и две, которые терпят неудачу. Шесть тестовых примеров ограничиваются вариациями параметра Node методов Register и Unregister.

Нажатие одинокой кнопки на форме создает контейнер и три узла. Для демонстрации процедуры над ними выполняются операции

Программа создает несколько простых узлов, которые связываются с простым контейнером. Проблема возникла в случаях №1 и №6. Когда узел освобождается, вызывается метод контейнера Unregister(). Метод удаляет копию указателя на узел в TList. При выходе из метода в двух неудачных случаях он вызывает метод Destroy() узла, рекурсивно начиная процесс заново, пока не произойдет переполнение стека.

В четырех сработавших случаях метод Destroy() возобновляется как обычно, и программа продолжает работу и завершается нормально.

Сбой #1 (Случай 1)

procedure RegisterNode(Node:INode);
procedure UnregisterNode(Node:INode);

Вызов Unregister() узла из метода TNode.Destroy(), похоже, влияет на счетчик ссылок INode, вызывая множественные вызовы Destroy(). Почему это происходит, я не понимаю. Этого не происходит, когда я Регистрирую() узел с тем же стилем параметров.

Сбой #2 (пример 6)

procedure RegisterNode(const Node:INode);
procedure UnregisterNode(Node:INode);

Здесь происходит та же картина сбоя. Добавление const в список параметров, как в примере 5, предотвращает рекурсивный вызов Destroy().

The code:

unit fMain;
{
   Case 1 - Fails when a node is freed, after unregistering,
             TNode.Destroy is called again
   Case 2 - Passes
   case 3 - Passes
   Case 4 - Passes
   Case 5 - Passes
   Case 6 - Fails the same way as case 1
}
{$Define Case1}
{.$Define Case2}
{.$Define Case3}
{.$Define Case4}
{.$Define Case5}
{.$Define Case6}
interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type

  INode = interface;
  TNode = class;

  IContainer = interface
  ['{E8B2290E-AF97-4ECC-9C4D-DEE7BA6A153C}']
{$ifDef Case1}
    procedure RegisterNode(Node:INode);
    procedure UnregisterNode(Node:INode);
{$endIf}
{$ifDef Case2}
    procedure RegisterNode(Node:TNode);
    procedure UnregisterNode(Node:TNode);
{$endIf}
{$ifDef Case3}
    procedure RegisterNode(const Node:INode);
    procedure UnregisterNode(const Node:INode);
{$endIf}
{$ifDef Case4}
    procedure RegisterNode(const Node:TNode);
    procedure UnregisterNode(const Node:TNode);
{$endIf}
{$ifDef Case5}
    procedure RegisterNode(Node:INode);
    procedure UnregisterNode(const Node:INode);
{$endIf}
{$ifDef Case6}
    procedure RegisterNode(const Node:INode);
    procedure UnregisterNode(Node:INode);
{$endIf}
  end;
  INode = interface
  ['{37923052-D6D1-4ED5-9AC0-F7FB0076FED8}']
    procedure SetContainer(const Value:IContainer);
    function GetContainer():IContainer;
    procedure ReReg(const AContainer: IContainer);
    procedure UnReg();
    property Container : IContainer
      read GetContainer write SetContainer;
  end;

  TContainer = class(TInterfacedObject, IContainer)
  protected
    NodeList: TList;
  public
    constructor Create(); virtual;
    destructor Destroy(); override;
{$ifDef Case1}
    procedure RegisterNode(Node:INode); virtual;
    procedure UnregisterNode(Node:INode); virtual;
{$endIf}
{$ifDef Case2}
    procedure RegisterNode(Node:TNode); virtual;
    procedure UnregisterNode(Node:TNode); virtual;
{$endIf}
{$ifDef Case3}
    procedure RegisterNode(const Node:INode); virtual;
    procedure UnregisterNode(const Node:INode); virtual;
{$endIf}
{$ifDef Case4}
    procedure RegisterNode(const Node:TNode); virtual;
    procedure UnregisterNode(const Node:TNode); virtual;
{$endIf}
{$ifDef Case5}
    procedure RegisterNode(Node:INode); virtual;
    procedure UnregisterNode(const Node:INode); virtual;
{$endIf}
{$ifDef Case6}
    procedure RegisterNode(const Node:INode); virtual;
    procedure UnregisterNode(Node:INode); virtual;
{$endIf}
  end;

  TNode = class(TInterfacedObject, INode)
  protected
    FContainer : IContainer;
  public
    constructor Create(const AContainer: IContainer); virtual;
    destructor Destroy(); override;
    procedure SetContainer(const Value:IContainer); virtual;
    function GetContainer():IContainer; virtual;
    procedure ReReg(const AContainer: IContainer); virtual;
    procedure UnReg(); virtual;
    property Container : IContainer
      read GetContainer write SetContainer;
  end;

  TForm1 = class(TForm)
    btnMakeStuff: TButton;
    procedure btnMakeStuffClick(Sender: TObject);
  private
    { Private declarations }
    MyContainer : IContainer;
    MyNode1,
    MyNode2,
    MyNode3     : INode;

  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation
{$R *.dfm}

{ TContainer }

constructor TContainer.Create();
begin
  inherited;
  NodeList := TList.Create();
end;
destructor TContainer.Destroy();
var
  i : integer;
begin
  for i := 0 to Pred(NodeList.Count) do
    INode(NodeList.Items[i]).Container := nil;  //Prevent future Node from contacting container
  NodeList.Free();
  inherited;
end;

{$ifDef Case1}
procedure TContainer.RegisterNode(Node:INode);
{$endIf}
{$ifDef Case2}
procedure TContainer.RegisterNode(Node:TNode);
{$endIf}
{$ifDef Case3}
procedure TContainer.RegisterNode(const Node:INode);
{$endIf}
{$ifDef Case4}
procedure TContainer.RegisterNode(const Node:TNode);
{$endIf}
{$ifDef Case5}
procedure TContainer.RegisterNode(Node:INode);
{$endIf}
{$ifDef Case6}
procedure TContainer.RegisterNode(const Node:INode);
{$endIf}

begin
  NodeList.Add(pointer(Node));
end;

{$ifDef Case1}
procedure TContainer.UnregisterNode(Node:INode);
{$endIf}
{$ifDef Case2}
procedure TContainer.UnregisterNode(Node:TNode);
{$endIf}
{$ifDef Case3}
procedure TContainer.UnregisterNode(const Node:INode);
{$endIf}
{$ifDef Case4}
procedure TContainer.UnregisterNode(const Node:TNode);
{$endIf}
{$ifDef Case5}
procedure TContainer.UnregisterNode(const Node:INode);
{$endIf}
{$ifDef Case6}
procedure TContainer.UnregisterNode(Node:INode);
{$endIf}
var
  i : integer;
begin
  i := NodeList.IndexOf(pointer(Node));
  if i >= 0 then
    NodeList.Delete(i);
end;

{ INode }

constructor TNode.Create(const AContainer: IContainer);
begin
  ReReg(AContainer);
end;

destructor TNode.Destroy();
begin   {When failing, after unregistering, it returns here !!!!}
  if Assigned(FContainer) then begin
    FContainer.UnregisterNode(self);
  end;
  inherited;
end;

function TNode.GetContainer(): IContainer;
begin
  Result := FContainer;
end;

procedure TNode.ReReg(const AContainer: IContainer);
begin
  if Assigned(AContainer) then
    AContainer.RegisterNode(Self);
  FContainer := AContainer;
end;

procedure TNode.SetContainer(const Value: IContainer);
begin
  if Assigned(FContainer) then
    FContainer.UnregisterNode(self);
  FContainer := Value;
  FContainer.RegisterNode(self);
end;

procedure TNode.UnReg();
begin
  if Assigned(FContainer) then
    FContainer.UnregisterNode(self);
  FContainer := nil;
end;

 { TForm1 }

procedure TForm1.btnMakeStuffClick(Sender: TObject);
begin
  MyContainer := TContainer.Create();
  MyNode1 := TNode.Create(MyContainer);
  MyNode2 := TNode.Create(MyContainer);
  MyNode3 := TNode.Create(MyContainer);

  MyNode2.UnReg();  //Breakpoint here
  MyNode2.ReReg(MyContainer);  //Breakpoint here
  MyNode3 := nil;   //Case 1 & 6 cause a stackoverflow
  MyNode2 := nil;

end;

end.
17
задан Rich Shealer 3 October 2011 в 23:23
поделиться