Загадочный случай неожиданной неявной интерфейсной переменной - PullRequest
8 голосов
/ 18 марта 2011

Недавно я столкнулся с поведением, которое я просто не мог и не мог объяснить, связанным с переменными интерфейса 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.

А вот разборка эпилога:

enter image description here

Там, как день, два вызова IntfClear.

Итак, кто может видеть очевидное объяснение, которое я пропускаю?


UPDATE

Ну, Уве сразу понял. 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 не может быть очищен до эпилога.

Ответы [ 2 ]

12 голосов
/ 18 марта 2011

Просто предположение, но, возможно, FListeners[i] as IListener использует временную переменную для FListeners[i].Ведь это результат вызова функции.

11 голосов
/ 18 марта 2011

Уве Раабе прав, если вы посмотрите на код дальше:

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], а затем *Для этого вызывается 1004 * (eax - цель, [ebp- $ 10], edx источник, [ebp- $ 20] и выведите адрес, по которому можно найти соответствующий гид.

Вы можете исправитькод, изменив метод 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?
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...