I asked a similar question about implicit interface variables not so long ago.
The source of this question was a bug in my code due to me not being aware of the existence of an implicit interface variable created by the compiler. This variable was finalized when the procedure that owned it finished. This in turn caused a bug due to the lifetime of the variable being longer than I had anticipated.
Now, I have a simple project to illustrate some interesting behaviour from the compiler:
program ImplicitInterfaceLocals;
{$APPTYPE CONSOLE}
uses
Classes;
function Create: IInterface;
begin
Result := TInterfacedObject.Create;
end;
procedure StoreToLocal;
var
I: IInterface;
begin
I := Create;
end;
procedure StoreViaPointerToLocal;
var
I: IInterface;
P: ^IInterface;
begin
P := @I;
P^ := Create;
end;
begin
StoreToLocal;
StoreViaPointerToLocal;
end.
StoreToLocal
is compiled just as you would imagine. The local variable I
, the function's result, is passed as an implicit var
parameter to Create
. The tidy up for StoreToLocal
results in a single call to IntfClear
. No surprises there.
However, StoreViaPointerToLocal
is treated differently. The compiler creates an implicit local variable which it passes to Create
. When Create
returns, the assignment to P^
is performed. This leaves the routine with two local variables holding references to the interface. The tidy up for StoreViaPointerToLocal
results in two calls to IntfClear
.
The compiled code for StoreViaPointerToLocal
is like this:
ImplicitInterfaceLocals.dpr.24: begin
00435C50 55 push ebp
00435C51 8BEC mov ebp,esp
00435C53 6A00 push $00
00435C55 6A00 push $00
00435C57 6A00 push $00
00435C59 33C0 xor eax,eax
00435C5B 55 push ebp
00435C5C 689E5C4300 push $00435c9e
00435C61 64FF30 push dword ptr fs:[eax]
00435C64 648920 mov fs:[eax],esp
ImplicitInterfaceLocals.dpr.25: P := @I;
00435C67 8D45FC lea eax,[ebp-$04]
00435C6A 8945F8 mov [ebp-$08],eax
ImplicitInterfaceLocals.dpr.26: P^ := Create;
00435C6D 8D45F4 lea eax,[ebp-$0c]
00435C70 E873FFFFFF call Create
00435C75 8B55F4 mov edx,[ebp-$0c]
00435C78 8B45F8 mov eax,[ebp-$08]
00435C7B E81032FDFF call @IntfCopy
ImplicitInterfaceLocals.dpr.27: end;
00435C80 33C0 xor eax,eax
00435C82 5A pop edx
00435C83 59 pop ecx
00435C84 59 pop ecx
00435C85 648910 mov fs:[eax],edx
00435C88 68A55C4300 push $00435ca5
00435C8D 8D45F4 lea eax,[ebp-$0c]
00435C90 E8E331FDFF call @IntfClear
00435C95 8D45FC lea eax,[ebp-$04]
00435C98 E8DB31FDFF call @IntfClear
00435C9D C3 ret
I can guess as to why the compiler is doing this. When it can prove that assigning to the result variable will not raise an exception (i.e. if the variable is a local) then it uses the result variable directly. Otherwise it uses an implicit local and copies the interface once the function has returned thus ensuring that we don't leak the reference in case of an exception.
But I cannot find any statement of this in the documentation. It matters because interface lifetime is important and as a programmer you need to be able to influence it on occasion.
So, does anybody know if there is any documentation of this behaviour? If not does anyone have any more knowledge of it? How are instance fields handled, I have not checked that yet. Of course I could try it all out for myself but I'm looking for a more formal statement and always prefer to avoid relying on implementation detail worked out by trial and error.
Update 1
To answer Remy's question, it mattered to me when I needed to finalize the object behind the interface before carrying out another finalization.
begin
AcquirePythonGIL;
try
PyObject := CreatePythonObject;
try
//do stuff with PyObject
finally
Finalize(PyObject);
end;
finally
ReleasePythonGIL;
end;
end;
As written like this it is fine. But in the real code I had a second implicit local which was finalized after the GIL was released and that bombed. I solved the problem by extracting the code inside the Acquire/Release GIL into a separate method and thus narrowed the scope of the interface variable.
See Question&Answers more detail:
os