No Delphi, por que passar uma variável de interface às vezes exige que seja um parâmetro cons
Primeira pergunta: Por que a remoção de const emUnregisterNode()
causar falha, mas não emRegisterNode()
.
Agora, estou trabalhando no Delphi XE com Interfaces e encontrei um artefato que me deu uma pausa e cheguei à conclusão de que realmente não entendo o porquê.
Um objeto que é acessado como uma interface não precisa ser liberado explicitamente. Quando a última referência fica fora do escopo, ela é destruída. Isso parece bastante simples. Eu escrevi um caso de teste para mostrar variações que são executadas conforme o esperado e duas que falham. Os seis casos de teste estão limitados a variações no parâmetro Node dos métodos Register e Unregiste
Pressionar o botão solitário no formulário cria o contêiner e três nós. As operações são executadas nelas para demonstrar o procedimento
O programa cria alguns nós simples vinculados a um contêiner simples. O problema ocorreu nos casos 1 e 6. Quando o nó está sendo liberado, ele chama os containersUnregister()
método. O método exclui uma cópia do ponteiro para o nó em uma TList. Ao deixar o método nos dois casos com falha, ele chama @ do Destroy()
método recursivamente iniciando o processo novamente até que ocorra um estouro de pilh
Nos quatro casos que funcionam oDestroy()
método @ é retomado normalmente e o programa continua e sai normalment
Failure # 1 (Caso 1)
procedure RegisterNode(Node:INode);
procedure UnregisterNode(Node:INode);
Chamando oUnregister()
do nóTNode.Destroy()
método @ parece afetar a contagem de referência do INode, causando várias chamadas paraDestroy().
Por que isso acontece eu não entendo. Isso não acontece quando euRegister()
o nó com o mesmo estilo de parâmetro
Failure # 2 (Caso 6)
procedure RegisterNode(const Node:INode);
procedure UnregisterNode(Node:INode);
O mesmo padrão de falha acontece aqui. A adição de const à lista de parâmetros, como no caso 5, impede que as chamadas recursivas paraDestroy()
.
O código
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.