Search code examples
delphidelphi-xe7

Why is an interface in the sub class not released


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?


Solution

  • 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