Here is some sample code, it is a standalone console app in Delphi, it creates an object and then creates an object which is a TInterfacedObject
and assigns the Interface reference to a field in a TObject:
program ReferenceCountingProblemProject;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils;
type
ITestInterface = interface
['{A665E2EB-183C-4426-82D4-C81531DBA89B}']
procedure AnAction;
end;
TTestInterfaceImpl = class(TInterfacedObject,ITestInterface)
constructor Create;
destructor Destroy; override;
// implement ITestInterface:
procedure AnAction;
end;
TOwnerObjectTest = class
public
FieldReferencingAnInterfaceType1:ITestInterface;
end;
constructor TTestInterfaceImpl.Create;
begin
WriteLn('TTestInterfaceImpl object created');
end;
destructor TTestInterfaceImpl.Destroy;
begin
WriteLn('TTestInterfaceImpl object destroyed');
end;
procedure TTestInterfaceImpl.AnAction;
begin
WriteLn('TTestInterfaceImpl AnAction');
end;
procedure Test;
var
OwnerObjectTest:TOwnerObjectTest;
begin
OwnerObjectTest := TOwnerObjectTest.Create;
OwnerObjectTest.FieldReferencingAnInterfaceType1 := TTestInterfaceImpl.Create as ITestInterface;
OwnerObjectTest.FieldReferencingAnInterfaceType1.AnAction;
OwnerObjectTest.Free; // This DOES cause the clearing of the interface fields automatically.
ReadLn; // wait for enter.
end;
begin
Test;
end.
I wrote this code because I was not sure if, in trivial examples, Delphi would always clear out my interface pointers. Here is the output when the program runs:
TTestInterfaceImpl object created
TTestInterfaceImpl AnAction
TTestInterfaceImpl object destroyed
This is the output I very much hoped to see. The reason I wrote this program is because I am seeing this "contract between me and Delphi" violated in a large Delphi application that I am working on. I am seeing objects NOT be freed, unless I explicitly zero them out in my destructor like this:
destructor TMyClass.Destroy;
begin
FMyInterfacedField := nil; // work around leak.
end;
My belief is that Delphi is doing its level best to zero these interfaces, and so, when I set a breakpoint on the destructor in the test code above, I get this call stack:
ReferenceCountingProblemProject.TTestInterfaceImpl.Destroy
:00408e5f TInterfacedObject._Release + $1F
:00408d77 @IntfClear + $13
ReferenceCountingProblemProject.ReferenceCountingProblemProject
As you can see a call to @IntfClear
is being generated but the lack of a "Free" in the call stack above is slightly confusing me as it appears that the two are causally linked, but not directly in each other's call paths. This suggests to me that the compiler itself emits an @IntfClear
in my application at some point after the invocation of the destructor TObject.Free
. Am I reading this sign correctly?
My question is: Does Delphi's TObject always guarantee finalization of Fields of Interface types? If not, when will my Interface be cleared for me, and when do I have to manually clear it? Is this finalization of the interface reference implemented as part of TObject, or as part of some general compiler-scope-semantics? What are, in fact, the rules that I should follow as to when to manually zero out an interface, and when to let Delphi do it for me? Imagine I have (as I do have) 200+ classes in my application that store Interfaces as Fields. Do I set them all to Nil in my destructor, or not? How do I decide what to do?
My suspicion is that either (a) TObject provides this guarantee, with the proviso that if you do something stupid, and somehow do not get down to invoking TObject.Destroy on the object that contains the Interface reference Field, you leak both, or (b) that the compiler at a level lower than TObject provides this semantic guarantee, at the level of things going out of scope, and it is this side that leaves me then, scratching my head and unable to explain the complex scenarios I might encounter in the real world.
For trivial cases, like the one where I remove OwnerObjectTest.Free;
from the demo above, and you leak both objects that the demo code creates, I have no problem understanding the behaviour of the language/compiler/runtime, but I wish to be sure that I have fully understood what contract or guarantee, if any, exists with respect to Fields in Objects that are of Interface type.
Update By single stepping and declaring my own destructor, I was able to get a different call stack, which makes more sense:
ReferenceCountingProblemProject.TTestInterfaceImpl.Destroy
:00408e5f TInterfacedObject._Release + $1F
:00408d77 @IntfClear + $13
:00405483 TObject.Free + $B
ReferenceCountingProblemProject.ReferenceCountingProblemProject
This appears to show that @IntfClear
is invoked BY TObject.Free
which is what I very much expected to see.
All fields of an object instance are finalised when the object's destructor is executed. That is guaranteed by the runtime. Indeed, all fields of managed types are finalised upon destruction.
The likely explanations for such reference counted objects not being destroyed are:
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