Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/delphi/9.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Delphi 意外隐式接口变量的神秘案例_Delphi_Interface - Fatal编程技术网

Delphi 意外隐式接口变量的神秘案例

Delphi 意外隐式接口变量的神秘案例,delphi,interface,Delphi,Interface,我最近遇到了一些我根本无法解释的行为,与Delphi接口变量有关 本质上,它归结为编译器在Broadcast方法中生成的隐式接口变量 在终止该方法的end语句中,尾声代码包含对IntfClear的两个调用。其中一个我可以解释,它对应于Listener局部变量。另一个我无法解释,它会在对象实例销毁后将您带到TComponent.\u Release(调试DCU)。它不会产生AV,但这很幸运,并且使用完整的FastMM调试,会报告销毁后的实例访问 代码如下: program UnexpectedIm

我最近遇到了一些我根本无法解释的行为,与Delphi接口变量有关

本质上,它归结为编译器在
Broadcast
方法中生成的隐式接口变量

在终止该方法的end语句中,尾声代码包含对
IntfClear
的两个调用。其中一个我可以解释,它对应于
Listener
局部变量。另一个我无法解释,它会在对象实例销毁后将您带到
TComponent.\u 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的电话,非常清楚

那么,谁能看到我所缺少的明显的解释呢


更新

嗯,我们马上就拿到了
FListeners[i]
需要一个临时隐式变量作为其结果变量。我没有看到这一点,因为我分配给了
侦听器
,但这当然是另一个变量

以下变量是编译器为我的原始代码生成的内容的显式表示

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在结束语之前无法清除。

只是猜测,但可能作为IListener的
FListeners[i]使用了一个临时变量作为
FListeners[i]
。毕竟这是函数调用的结果。

如果您进一步查看代码,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\u 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?

是的,就是这样-请参阅更新的问题以获取相应的代码。谢谢,真不敢相信我错过了。谢谢。我想我需要花更多的时间学习如何阅读反汇编代码。顺便说一下:这就是为什么在混合对象和接口引用范例时,我更喜欢从
TInterfacedObject
继承并手动引用count m的原因之一根据需要创建对象引用。
(FListener作为IUnknown)。\u AddRef
,而不是典型的对象样式销毁…
(FListener作为IUnknown)。\u Release
(在大多数情况下可能会导致立即自毁)。