我最近遇到了一些我无法解释的行为,与Delphi接口变量有关.
从本质上讲,它归结为编译器在Broadcast方法中生成的隐式接口变量.
在终止方法的结束语句中,结尾代码包含两个调用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.
Run Code Online (Sandbox Code Playgroud)
以下是结语的反汇编:

那天,明确的是对IntfClear的两次调用.
那么,谁能看到我遗漏的明显解释?
UPDATE
好吧,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;
Run Code Online (Sandbox Code Playgroud)
以这种方式编写时,显然Intf不能在结尾之前被清除.
Tho*_*ler 11
如果你进一步查看代码,Uwe Raabe是正确的:
Project4.dpr.51: Listener := FListeners[i] as IListener;
00441C16 8D4DE0 lea ecx,[ebp-$20]
00441C19 8B55F4 mov edx,[ebp-$0c]
00441C1C 8B45FC mov eax,[ebp-$04]
00441C1F 8B4004 mov eax,[eax+$04]
00441C22 8B18 mov ebx,[eax]
00441C24 FF530C call dword ptr [ebx+$0c]
00441C27 8B55E0 mov edx,[ebp-$20]
00441C2A 8D45F0 lea eax,[ebp-$10]
00441C2D B9A81C4400 mov ecx,$00441ca8
00441C32 E8A573FCFF call @IntfCast
Run Code Online (Sandbox Code Playgroud)
您可以看到FListeners [i]调用的结果如何放在[ebp- $ 20]中然后procedure _IntfCast(var Dest: IInterface; const Source: IInterface; const IID: TGUID);被调用(eax是目标,[ebp- $ 10],edx源,[ebp- $ 20],以及ecx可以找到适当guid的地址.
您可以通过将Broadcast方法更改为:来修复代码:
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];
if Supports(Intf, IListener, Listener) then
Listener.HandleMessage(Msg);
end;
Listener := nil;
Intf := nil;
FListeners.Clear;
FreeAndNil(FListener);
end;//method epilogue: why is there a call to IntfClear and then TComponent._Release?
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
1467 次 |
| 最近记录: |