现在的背景:我正在使用Delphi XE与Interfaces进行工作,我遇到了一个让我暂停的工件,我得出结论,我不太明白为什么.
作为接口访问的对象不需要显式释放.当最后一个参考超出范围时,它被破坏.这看起来很简单.我已经写了一个测试用例来显示按预期运行的变体,另外两个失败.六个测试用例限于Register和Unregister方法的Node参数的变化.
按下窗体上的单个按钮将创建容器和三个节点.执行操作以演示程序
该程序创建一些链接到简单容器的简单节点. #1和#6的情况发生.当节点被释放时,它调用容器Unregister()方法.该方法删除指向TList中节点的指针的副本.当在两个失败的情况下离开该方法时,它会将节点的Destroy()方法递归地重新开始,直到堆栈溢出发生.
在工作的四种情况下,Destroy()方法恢复正常,程序将继续正常退出.
失败#1(案例1)
procedure RegisterNode(Node:INode); procedure UnregisterNode(Node:INode);
从TNode.Destroy()方法调用Unregister()节点似乎影响引起对Destroy()的多次调用的INode的引用计数.为什么会发生这种情况我不明白当我注册()节点具有相同的参数样式时,不会发生.
失败#2(案例6)
procedure RegisterNode(const Node:INode); procedure UnregisterNode(Node:INode);
同样的故障模式发生在这里.将const添加到参数列表中,如第5种情况,阻止对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,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.
解决方法
这允许编译器对这些参数进行一些优化,特别是在诸如字符串和接口等的引用类型的领域.
具体来说,由于参数被声明为const,在参数的“生命周期”期间传递接口引用的值是不可修改的(因为编译器将拒绝任何尝试修改该值的代码),因此编译器能够消除调用AddRef()和Release(),在另一个方面可以将其作为prolog和epilog生成.
但是请注意,如果将引用分配给其他变量,则引用计数仍然可以改变. const优化简化了对AddRef / Release对的可能需求.
const和非const参数之间的引用计数行为的这种差异显然会在你的代码中产生一些副作用或者与其他复杂性的其他交互,但是现在理解const的效果,你可能可以确定你/你可能出错了别处.
其实我可以告诉你你哪里错了.