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