【发布时间】:2011-07-18 07:15:57
【问题描述】:
我最近遇到了一些与 Delphi 接口变量有关的行为,我根本无法也无法解释。
本质上,它归结为编译器在Broadcast 方法中生成的隐式接口变量。
在终止方法的 end 语句中,结尾代码包含对 IntfClear 的两次调用。我可以解释其中之一,它对应于Listener 局部变量。另一个我无法解释,它会在对象实例被销毁后将您带到TComponent._Release(调试 DCU)。它不会产生 AV,但这只是幸运,并且通过完整的 FastMM 调试报告了破坏后实例访问。
代码如下:
program UnexpectedImplicitInterfaceVariable;
{$APPTYPE CONSOLE}
uses
SysUtils, Classes;
type
IListener = interface
['{6D905909-98F6-442A-974F-9BF5D381108E}']
procedure HandleMessage(Msg: Integer);
end;
TListener = class(TComponent, IListener)
//TComponent._AddRef and TComponent_Release return -1
private
procedure HandleMessage(Msg: Integer);
end;
{ TListener }
procedure TListener.HandleMessage(Msg: Integer);
begin
end;
type
TBroadcaster = class
private
FListeners: IInterfaceList;
FListener: TListener;
public
constructor Create;
procedure Broadcast(Msg: Integer);
end;
constructor TBroadcaster.Create;
begin
inherited;
FListeners := TInterfaceList.Create;
FListener := TListener.Create(nil);
FListeners.Add(FListener);
end;
procedure TBroadcaster.Broadcast(Msg: Integer);
var
i: Integer;
Listener: IListener;
begin
for i := 0 to FListeners.Count-1 do
begin
Listener := FListeners[i] as IListener;
Listener.HandleMessage(Msg);
end;
Listener := nil;
FListeners.Clear;
FreeAndNil(FListener);
end;//method epilogue: why is there a call to IntfClear and then TComponent._Release?
begin
with TBroadcaster.Create do
begin
Broadcast(42);
Free;
end;
end.
这是对尾声的反汇编:
对 IntfClear 的两次调用清晰可见。
那么,谁能看到我遗漏的明显解释?
更新
嗯,Uwe 马上就明白了。 FListeners[i] 需要一个临时隐式变量作为其结果变量。我没有看到,因为我分配给Listener,但当然这是一个不同的变量。
以下变体是编译器为我的原始代码生成的内容的明确表示。
procedure TBroadcaster.Broadcast(Msg: Integer);
var
i: Integer;
Intf: IInterface;
Listener: IListener;
begin
for i := 0 to FListeners.Count-1 do
begin
Intf := FListeners[i];
Listener := Intf as IListener;
Listener.HandleMessage(Msg);
end;
Listener := nil;
FListeners.Clear;
FreeAndNil(FListener);
end;
这样写很明显 Intf 直到结尾才能被清除。
【问题讨论】:
-
顺便说一句:这就是为什么在混合对象和接口引用范例时我更喜欢从
TInterfacedObject继承并根据需要手动引用计数我的对象引用的原因之一。(FListener as IUnknown)._AddRef而不是典型的对象样式破坏...(FListener as IUnknown)._Release(在大多数情况下可能会导致立即自毁)。