I'm facing the following situation and I'm wondering this code is leaking memory. Let say I have the following interfaces and implementations:
type
ITools = interface
function HelloWorld : String;
end;
IDatabase = interface
function Query(AQuery : String) : String;
end;
IManager = interface
procedure Execute;
end;
TDatabase = class(TInterfacedObject, IDatabase)
strict private
FTools : ITools;
public
constructor Create;
destructor Destroy; override;
function Query(AQuery : String) : String;
end;
TTools = class(TInterfacedObject, ITools)
strict private
FDatabase : IDatabase;
public
constructor Create(ADatabase : IDatabase);
destructor Destroy; override;
function HelloWorld : String;
end;
TManager = class(TInterfacedObject, IManager)
strict private
FDatabase : IDatabase;
public
constructor Create;
procedure Execute;
end;
Now, if you create for example this:
procedure Example;
var
lExample : IManager;
begin
lExample := TManager.Create;
lExample.Execute;
lExampe := nil; // Should not be necessary
end;
Where FDatabase
from TManager
is created as TDatabase
and passed in to the constructor of TTools
, so it has the same (?) object / interface in TTools as in TManager.
Then lExample
leaks memory, because of the interfaces / objects in the sub class (IDatabase
). Why isn't it the interface released? Or what do I don't understand of the Delphi fundamentals?
Why isn't it the interface released?
You have a circular reference.
TDatabase contains a reference to TTools and TTools contains a reference to TDatabase.
Because Delphi does not have a garbage collector it is not able to resolve these circular references without help.
If you are using the Mobile NexGen compiler, or D10.1 Berlin the solution is declare TDatabase as:
TDatabase = class(TInterfacedObject, IDatabase)
strict private
[weak] <<--
FTools : ITools;
public
constructor Create;
destructor Destroy; override;
function Query(AQuery : String) : String;
end;
The [weak]
attribute will trigger Delphi to generate different code when FTools
is assigned. And the runtime will keep some bookkeeping so that even if the reference count of the interface goes to zero the object will not get destroyed if the weak reference happens to not be involved in a circular reference.
Marco Cantù writes about it here: http://blog.marcocantu.com/blog/2016-april-weak-unsafe-interface-references.html
He also writes about the [unsafe]
attribute. Don't use that one, unless you know exactly what it means, it's not what you need.
You should only mark one of the circular references as [weak]
! If you mark both mishaps will occur.
What to do if the compiler does not support [weak]
?
If you are using an older Delphi for the Windows or OSX targets the solution is as follows.
Use this hack as described by: http://blog.dummzeuch.de/2014/06/19/weak-references-or-why-you-should-enable-reportmemoryleaksonshutdown/
procedure SetWeak(_InterfaceField: PIInterface; const _Value: IInterface); begin PPointer(_InterfaceField)^ := Pointer(_Value); end; type TChild = class(TInterfacedObject, IChild) private FParent: IParent; // This must be a weak reference! public constructor Create(Parent: IParent); destructor Destroy; override; end; constructor TChild.Create(Parent: IParent); begin inherited Create; SetWeak(@FParent, Parent); end; destructor TChild.Destroy; begin SetWeak(@FParent, Nil); inherited; end;
What this does is make a reference in an underhanded way so that the reference count does not go up permanently.
Not that this hack does not have the full protection against mishaps that the [weak]
attribute gives.
If your weak reference happens to not be involved in a circular reference then you might have premature destruction of FParent.
Arnaud Bouchez has a more detailed write-up in his blog; I recommend you read it: http://blog.synopse.info/post/2012/06/18/Circular-reference-and-zeroing-weak-pointers