【问题标题】:The mysterious case of the unexpected implicit interface variable意外隐式接口变量的神秘案例
【发布时间】: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(在大多数情况下可能会导致立即自毁)。

标签: delphi interface


【解决方案1】:

只是一个猜测,但也许FListeners[i] as IListener 使用了一个临时变量来表示FListeners[i]。毕竟是函数调用的结果。

【讨论】:

  • 是的,就是这样 - 查看相应代码的更新问题。谢谢,真不敢相信我错过了。
【解决方案2】:

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

您可以看到 FListeners[i] 调用的结果如何放置在 [ebp-$20] 中,然后在其上调用 procedure _IntfCast(var Dest: IInterface; const Source: IInterface; const IID: TGUID);(eax 是目标,[ebp-$10],edx 源,[ ebp-$20],并 ecx 可以找到适当 guid 的地址。

您可以通过将广播方法更改为:

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?

【讨论】:

  • 谢谢。我想我需要花更多时间学习如何阅读反汇编代码。
猜你喜欢
  • 2014-06-05
  • 2011-09-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-04-13
  • 1970-01-01
  • 2011-07-05
  • 1970-01-01
相关资源
最近更新 更多