I recently came across some behaviour that I simply could not and cannot explain, related to Delphi interface variables.
Essentially, it boils down to an implicit interface variable that the compiler generates in the Broadcast
method.
At the end statement that terminates the method, the epilogue code contains two calls to IntfClear
. One of which I can explain, it corresponds to the Listener
local variable. The other one I cannot explain and it takes you to TComponent._Release
(Debug DCUs) after the object instance has been destroyed. It doesn't result in an AV, but that's just lucky, and with full FastMM debug a post-destruction instance access is reported.
Here's the code:
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.
And here's the disassembly of the epilogue:
There, clear as day, are the two calls to IntfClear.
So, who can see the obvious explanation that I am missing?
UPDATE
Well, Uwe got it straight away. FListeners[i]
needs a temporary implicit variable for its result variable. I didn't see that since I was assigning to Listener
, but of course that's a different variable.
The following variant is an explicit representation of what the compiler is generating for my original code.
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;
When written this way it is obvious that Intf cannot be cleared until the epilogue.
TInterfacedObject
and manually reference count my object references as needed.(FListener as IUnknown)._AddRef
and instead of typical object style destruction...(FListener as IUnknown)._Release
(which probably in most cases results in immediate self-destruction). – Degradable