11

When I am using Delphi Berlin 10.1 [weak] (and [unsafe]) reference, the "Supports" function and "QueryInterface" both are incrementing the reference count when given an interface variable marked with the "weak" attribute (same behavior with "unsafe" attribute).

program WeakReferences;

{$APPTYPE CONSOLE}

{$R *.res}

uses System.SysUtils;

type
   IAnInterfacedObject = interface
   ['{351DFDA3-42CA-4A1D-8488-494CA454FD9C}']
   end;

   TAnInterfacedObject = class(TInterfacedObject, IAnInterfacedObject)
     protected

      function  GetTheReferenceCount : integer;

     public
      constructor Create;
      destructor  Destroy; override;
      property    TheReferenceCount : integer read GetTheReferenceCount;
   end;

   constructor TAnInterfacedObject.Create;

   begin
      inherited Create;
      writeln('(create AIO instance)');
   end;

   destructor TAnInterfacedObject.Destroy;

   begin
      writeln('(destroy AIO instance)');

      inherited Destroy;
   end;

   function TAnInterfacedObject.GetTheReferenceCount : integer;

   begin
      Result := FRefCount;
   end;

   procedure WithoutSupports;

   var
      AIOinstance : TAnInterfacedObject;

      AIOinterfaced : IAnInterfacedObject;

      [Weak]
      WeakAIOinterfaced : IAnInterfacedObject;

   begin
      AIOinstance := TAnInterfacedObject.Create;
      writeln('created AIO instance; refcount: '+AIOinstance.TheReferenceCount.ToString);

      AIOinterfaced := AIOinstance;
      writeln('create AIO interfaced; refcount: '+AIOinstance.TheReferenceCount.ToString);

      WeakAIOinterfaced := AIOinstance;
      writeln('create WEAK AIO interfaced; refcount: '+AIOinstance.TheReferenceCount.ToString);
   end;

   procedure WithSupports_Weak;

   var
      AIOinstance : TAnInterfacedObject;

      AIOinterfaced : IAnInterfacedObject;

      [Weak]
      WeakAIOinterfaced : IAnInterfacedObject;

   begin
      AIOinstance := TAnInterfacedObject.Create;
      writeln('created AIO instance; refcount: '+AIOinstance.TheReferenceCount.ToString);

      AIOinterfaced := AIOinstance;
      writeln('create AIO interfaced; refcount: '+AIOinstance.TheReferenceCount.ToString);

      Supports(AIOinstance, IAnInterfacedObject, WeakAIOinterfaced);
      writeln('create WEAK AIO interfaced with SUPPORTS; refcount: '+AIOinstance.TheReferenceCount.ToString);
   end;

   procedure WithSupports_Unsafe;

   var
      AIOinstance : TAnInterfacedObject;

      AIOinterfaced : IAnInterfacedObject;

      [Unsafe]
      UnsafeAIOinterfaced : IAnInterfacedObject;

   begin
      AIOinstance := TAnInterfacedObject.Create;
      writeln('created AIO instance; refcount: '+AIOinstance.TheReferenceCount.ToString);

      AIOinterfaced := AIOinstance;
      writeln('create AIO interfaced; refcount: '+AIOinstance.TheReferenceCount.ToString);

      Supports(AIOinstance, IAnInterfacedObject, UnsafeAIOinterfaced);
      writeln('create UNSAFE AIO interfaced with SUPPORTS; refcount: '+AIOinstance.TheReferenceCount.ToString);
   end;

   procedure WithQueryInterface_Weak;

   var
      AIOinstance : TAnInterfacedObject;

      AIOinterfaced : IAnInterfacedObject;

      [Weak]
      WeakAIOinterfaced : IAnInterfacedObject;

   begin
      AIOinstance := TAnInterfacedObject.Create;
      writeln('created AIO instance; refcount: '+AIOinstance.TheReferenceCount.ToString);

      AIOinterfaced := AIOinstance;
      writeln('create AIO interfaced; refcount: '+AIOinstance.TheReferenceCount.ToString);

      AIOinterfaced.QueryInterface(IAnInterfacedObject, WeakAIOinterfaced);
      writeln('create WEAK AIO interfaced with QUERYINTERFACE; refcount: '+AIOinstance.TheReferenceCount.ToString);
   end;

begin
  try
     writeln('--Without "Supports"-------------------');
     WithoutSupports;
     writeln;
     writeln('--With "Supports" - weak-------------------');
     WithSupports_Weak;
     writeln;
     writeln('--With "Supports" - unsafe-------------------');
     WithSupports_Unsafe;
     writeln;
     writeln('--With "QueryInterface" - weak-------------------');
     WithQueryInterface_Weak;

  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;

  readln;
end.

What have I missed here? Is there a "WeakSupports" function? Is this a bug or just a shortcoming of the new "weak" interfaces feature?

Dave Olson
  • 1,435
  • 1
  • 9
  • 16

1 Answers1

9

Delphi Supports (one of overloads - but all other are the same considering out parameter) function is declared as

function Supports(const Instance: IInterface; const IID: TGUID; out Intf): Boolean;

and QueryInterface as

function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;

Both out parameters are not marked as [weak]. That means that you cannot pass [weak] or [unsafe] interface reference to them. You can only pass strong reference to such parameter to keep reference counting in order.

From documentation Weak References:

Note: You can only pass a [Weak] variable to a var or out parameter that is also marked as [Weak]. You cannot pass a regular strong reference to a [Weak] var or out parameter.

Note: You can only pass an [Unsafe] variable to a var or out parameter that is also marked as [Unsafe]. You cannot pass a regular strong reference to an [Unsafe] var or out parameter.


Additionally your test case has another issue. Unless you are using full-ARC Delphi compilers (currently Android and iOS) you cannot store reference counted object to object reference or you will mess up reference counting. To be more precise, object references under non-ARC compilers do not contribute to reference counting, and you can only use them as temporary access points to perform some operations on object not accessible through declared interfaces. You should always have at least one interface reference to that object instance to keep reference counting in order, and prevent premature object destruction or memory leaks.

Instead of

 var
   AIOinstance : TAnInterfacedObject;
   ...
   AIOinstance := TAnInterfacedObject.Create;

you should always use

var
  AIOinstance : IAnInterfacedObject;
  ...
  AIOinstance := TAnInterfacedObject.Create;
Dalija Prasnikar
  • 27,212
  • 44
  • 82
  • 159
  • This is why I find ARC difficult to mix with non-ARC code... so why I never switched to ARC for business code, and still doubt I will ever use the Delphi linux compiler. – Arnaud Bouchez Feb 17 '17 at 20:16
  • 1
    @ArnaudBouchez I am not sure what you mean. Mixing object references and interface references issues is exactly the thing ARC compiler solves. Of course, for cross-platform code you still have to follow non-ARC compiler rules and never mix those two. – Dalija Prasnikar Feb 17 '17 at 20:21
  • 1
    Dalija, there are valid reasons to create an object with a local object instance and then assign it to an interface later. We often create and initialize an object and only then assign it to an interface. – Graymatter Feb 17 '17 at 21:44
  • @Graymatter If class is reference counted, like TinterfacedObject is, storing it's instances into object reference breaks reference counting. Don't do that. You are asking for trouble. If you don't need reference counting then disable reference counting, like TComponent class does. Then you can mix the two. – Dalija Prasnikar Feb 17 '17 at 22:47
  • 1
    I am not saying keep the object, I am saying use it to create and setup the object. For example, `begin MyObj := TMyObject.Create; MyObj.Init(xxxx); Result := MyObj; end;` where the function returns IMyObject - Your comment at the end "you should always use" is not necessarily practical. Of course, you could create an interface to setup your object but that's unnecessary. – Graymatter Feb 17 '17 at 22:51
  • That code is broken on different level. Even if you get pass potential premature destruction of object, which you most likely will if you only call Init like in your example, you will still end up with wrong reference count when your function returns. That object instance will not get released because it will have reference count of 2 instead of 1. – Dalija Prasnikar Feb 17 '17 at 23:52
  • 2
    I am missing something. How will you get an incorrect reference count? This is a very common pattern with factories. http://pastebin.com/nYWgdfSe – Graymatter Feb 18 '17 at 01:24
  • Use the constructor to initialize, which is also the more idiomatic way to do it on OOP – Agustin Ortu Feb 18 '17 at 03:05
  • @Graymatter I guess it is one of those cases where you should not test things on global level because compiler behaves differently than on local level. Your factory works on non-global variables :( – Dalija Prasnikar Feb 18 '17 at 10:02
  • 1
    In fact, I agree with @Graymatter that sometimes, I initialize a class instance, then call some tuning methods, then assign the class into an interface, for abstract use. This is pretty common when working with SOLID code. – Arnaud Bouchez Feb 18 '17 at 18:19
  • 1
    @DalijaPrasnikar This non consistent behavior between ARC and non ARC makes it very difficult to work with, in practice, if you have to maintain also some non-ARC code. And the Free/DisposeOf trick. And the need of explicit [weak] references, including for function parameters. I find it finally more verbose and difficult to debug than Delphi explicit memory management, via Free, or TComponent ownership, or interfaces reference counting (including the smartpointer trick for short-living class instances on stack). But it is very personal. – Arnaud Bouchez Feb 18 '17 at 18:32
  • @ArnaudBouchez writing cross-platform code is a bit messy, that we agree. But this question covers non-ARC compiler and all the mess here comes within that compiler itself. – Dalija Prasnikar Feb 18 '17 at 18:53
  • @Graymatter there is semantic difference here. In your initialization examples mixing works because you are actually using object reference only temporary. That is no different than retrieving object from interface reference to do some immediate work. Only thing I would change in mentioned initialization pattern is assigning object to its interface reference immediately after creating it. That will prevent any possible premature destruction. – Dalija Prasnikar Feb 18 '17 at 19:00
  • In general terms mixing interface and object references considers that you are actually using object reference for permanent storage of object instance and that is wrong. – Dalija Prasnikar Feb 18 '17 at 19:01
  • I understand and agree which is why I upvoted your answer. My concern was just with the last comment "you should always use" which readers could confuse as something that should be done all of the time. I was trying to clarify that it's not always the case. – Graymatter Feb 19 '17 at 07:25
  • @AgustinOrtu Creating a new constructor is not always ideal in Delphi. How your objects are constructed (e.g. plugins) is important. – Graymatter Feb 19 '17 at 07:27