gpt4 book ai didi

delphi - 在 Delphi 中,为什么传递接口(interface)变量有时需要它是 const 参数?

转载 作者:行者123 更新时间:2023-12-03 14:35:21 24 4
gpt4 key购买 nike

首先是问题:为什么要删除 UnregisterNode() 中的 const导致失败,但不在 RegisterNode() 中.

现在背景:我在 Delphi XE 中使用 Interfaces 工作,我遇到了一个让我暂停的工件,我得出的结论是我真的不明白为什么。

作为接口(interface)访问的对象不需要显式释放。当最后一个引用超出范围时,它会被销毁。这似乎很简单。我编写了一个测试用例来显示按预期运行的变体和两个失败的变体。六个测试用例仅限于 Register 和 Unregister 方法的 Node 参数的变化。

按下表单上的唯一按钮会创建容器和三个节点。对它们进行操作以演示程序

该程序创建了一些链接到一个简单容器的简单节点。问题发生在案例#1 和#6。当节点被释放时,它会调用容器 Unregister()方法。该方法删除指向 TList 中节点的指针的副本。在两个失败的情况下离开方法时,它调用节点的 Destroy()方法递归地重新开始该过程,直到发生堆栈溢出。

Destroy() 有效的四种情况下方法恢复正常,程序将继续正常退出。

失败 #1(案例 1)

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

调用 Unregister()来自 TNode.Destroy() 的节点方法似乎会影响 INode 的引用计数,从而导致对 Destroy(). 的多次调用 为什么会发生这种情况我不明白。 当我 Register() 时不会发生具有相同参数样式的节点。

失败 #2(案例 6)
procedure RegisterNode(const Node:INode);
procedure UnregisterNode(Node:INode);

同样的失败模式也发生在这里。如案例 5 所示,将 const 添加到参数列表可防止递归调用 Destroy() .

编码:
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.

最佳答案

接口(interface)的引用计数

您的原始问题和对此答案的评论中的后续操作都取决于 Delphi 的接口(interface)引用计数机制。

编译器发出代码来安排对接口(interface)的所有引用都被计算在内。每当您获取新的引用时,计数就会增加。每当释放引用(设置为 nil 、超出范围等)时,计数就会减少。当计数达到零时,接口(interface)被释放,在您的情况下,这就是所谓的 Free在你的物体上。

您的问题是您通过将接口(interface)引用放入和取出 TList 来欺骗引用计数。通过转换为 Pointer然后回来。在某个地方,引用文献被误算了。我确信可以解释您的代码的行为(即堆栈溢出),但我不愿意尝试这样做,因为代码使用了这些明显不正确的结构。

简而言之,您永远不应该将接口(interface)转换为像 Pointer 这样的非托管类型。 .每当您这样做时,您还需要控制丢失的引用计数代码。我可以向你保证,这是你不想承担的!

您应该使用正确的类型安全容器,例如 TList<INode>甚至是动态数组,然后引用计数将被正确处理。对代码进行此更改可以解决您在问题中描述的问题。

循环引用

但是,正如您自己发现并在评论中详细说明的那样,仍然存在一个大问题。

一旦遵循引用计数规则,就会面临循环引用的问题。在这种情况下,节点持有对容器的引用,而容器又持有对节点的引用。像这样的循环引用不能被标准引用计数机制破坏,您必须自己破坏它们。一旦你打破了构成循环引用的两个单独引用之一,框架就可以完成其余的工作。

对于您当前的设计,您必须通过显式调用 UnReg 来打破循环引用。在每个 INode你创造的。

代码的另一个问题是您正在使用表单的数据字段来保存 MyContainer , MyNode等等因为你从来没有设置MyContainernil那么你的事件处理程序的两次执行将导致泄漏。

对您的代码进行了以下更改,以证明它可以在不泄漏的情况下运行:

TContainer = class(TInterfacedObject, IContainer)
protected
NodeList: TList<INode>;//switch to type-safe list

...

procedure TContainer.RegisterNode(Node:INode);
begin
//must ensure we don't add the node twice
if NodeList.IndexOf(Node) = -1 then
NodeList.Add(Node);
end;

...

procedure TForm1.btnMakeStuffClick(Sender: TObject);
//make the interfaces local variables although in production
//code they would likely be fields and construction would happen
//in the constructor of the owning object
var
MyContainer: IContainer;
MyNode1, MyNode2, MyNode3: INode;
begin
MyContainer := TContainer.Create;
MyNode1 := TNode.Create(MyContainer);
MyNode2 := TNode.Create(MyContainer);
MyNode3 := TNode.Create(MyContainer);

MyNode1.UnReg;
MyNode1.ReReg(MyContainer);
MyNode2.UnReg;
MyNode3.UnReg;
MyNode2.ReReg(MyContainer);
MyNode1.UnReg;
MyNode2.UnReg;
end;

通过这些更改,代码在没有内存泄漏的情况下运行 – 设置 ReportMemoryLeaksOnShutdown := True在 .dpr 文件的开头进行检查。

必须调用 UnReg 将是一种束缚。在每个节点上,所以我建议您只需添加一个方法到 IContainer要做到这一点。一旦您安排容器能够删除其引用,那么您将拥有一个更易于管理的系统。

您将无法让引用计数为您完成所有工作。您需要调用 IContainer.UnRegAllItems明确地。

您可以像这样实现这个新方法:
procedure TContainer.UnRegAllItems;
begin
while NodeList.Count>0 do
NodeList[0].UnReg;
end;

引用计数错误

尽管 Delphi 引用计数机制总体上实现得非常好,但据我所知,存在一个长期存在且非常知名的错误。
procedure Foo(const I: IInterface);
begin
I.DoSomething;
end;
...
Foo(TInterfacedObject.Create);

Foo以这种方式调用不会生成任何代码来添加对接口(interface)的引用。因此,该接口(interface)在创建后立即释放, Foo作用于无效接口(interface)。

因为 Foo接收参数为 const , Foo不引用接口(interface)。该错误位于调用 Foo 的代码生成器中。它错误地没有引用接口(interface)。

我解决这个特定问题的首选方法是这样的:
var
I: IInterface;
...
I := TInterfacedObject.Create;
Foo(I);

这成功了,因为我们明确地引用了一个引用。

请注意,我已经对此进行了解释以供将来引用——您当前的代码不会遇到这个问题。

关于delphi - 在 Delphi 中,为什么传递接口(interface)变量有时需要它是 const 参数?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/7640841/

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