Недавно я столкнулся с некоторым поведением, которое я просто не мог и не могу объяснить, связанное с интерфейсными переменными 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.
] Итак, кто может увидеть очевидное объяснение того, что мне не хватает?
ОБНОВЛЕНИЕ
Что ж, Уве сразу понял. 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 нельзя очистить до эпилога.