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.
Just a guess, but perhaps the FListeners[i] as IListener
uses a temporary variable for FListeners[i]
. After all it is the result of a function call.
Uwe Raabe is correct, if you look at the code further up:
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
You can see how the result of the FListeners[i] call is placed in [ebp-$20] and then procedure _IntfCast(var Dest: IInterface; const Source: IInterface; const IID: TGUID);
is called on that (eax being the target, [ebp-$10], edx the source, [ebp-$20], and ecx the address where the appropriate guid can be found.
You can fix your code by changing the Broadcast method to:
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?
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With