Jump to content
Incus J

Interface Reference counting

Recommended Posts

I'm reading up on interfaces, and trying to understand when a reference counted object will be freed.

var
  MyObject:IMyInterface;
begin
  if TSomeclass.CreateAndReturnAnObject(var MyObject) then begin
    //Do something with MyObject
  end;
end; //MyObject is freed automatically here?

If CreateAndReturnAnObject creates an object of type TMyInterface (which implements IMyInterface) and returns it via the MyObject parameter, will the object still exist immediately after CreateAndReturnAnObject returns - or will it have fallen out of scope?  How about if I omit 'var' from the parameter?

 

Since MyObject starts of as nil, I'm guessing it can't have its reference count auto-incremented until after it is created, so will it cease to exist once CreateAndReturnAnObject completes, in which case I can't 'Do something with MyObject', or does the compiler know to keep it around somehow?

Edited by Incus J

Share this post


Link to post
18 minutes ago, Incus J said:

If CreateAndReturnAnObject creates an object of type TMyInterface (which implements IMyInterface) and returns it via the MyObject parameter, will the object still exist immediately after CreateAndReturnAnObject returns

Yes.

 

18 minutes ago, Incus J said:

How about if I omit 'var' from the parameter?

Then there are no references to the object and it will be destroyed right before CreateAndReturnAnObject returns.

IMO it would be better if you declare CreateAndReturnAnObject a function that returns an interface.

 

20 minutes ago, Incus J said:

Since MyObject starts of as nil, I'm guessing it can't have its reference count auto-incremented...

MyObject is not an object; It's a reference (i.e. a pointer) to an object.

 

22 minutes ago, Incus J said:

I'm guessing it can't have its reference count auto-incremented until after it is created, so will it cease to exist once CreateAndReturnAnObject completes, in which case I can't 'Do something with MyObject', or does the compiler know to keep it around somehow?

First of all, it's important to know that a reference-counted object is destroyed when the reference count is decreased to 0.

 

Now, when the object has been created it has a reference count of 0. When you assign the object to a variable (e.g. Result or "var MyObject") you create a reference and the ref count is increased to 1.

The MyObject value returned from CreateAndReturnAnObject maintains the reference so the ref count isn't decremented.

 

Only when MyObject goes out of scope, at the final "end;" will MyObject be nilled which decrements the ref count so it reached 0 and the object is destroyed.

 

If your object is based on TInterfacedObject then I suggest you place breakpoints in all of the methods of TInterfacedObject and watch what goes on. For instance, it's good to know why the object starts with a ref count of -1 and how Delphi makes that work out in the end.

 

There are some strange corner cases but basic reference counting in Delphi really is very simple and logical once you understand the rules.

  • Thanks 2

Share this post


Link to post

Thank you - that's a good clear explanation.  I'll have a go at your suggestion and step through with the debugger to see what actually happens.

Quote

IMO it would be better if you declare CreateAndReturnAnObject a function that returns an interface.

That does make sense, though I like to have a boolean return value to test success against in a compact way:

if TSomeclass.CreateAndReturnAnObject(var MyObject) then begin

vs

MyObject := TSomeclass.CreateAndReturnAnObject;
if Assigned(MyObject) then begin

...but perhaps there's a better approach?  

Share this post


Link to post
2 hours ago, Incus J said:

If CreateAndReturnAnObject creates an object of type TMyInterface (which implements IMyInterface) and returns it via the MyObject parameter, will the object still exist immediately after CreateAndReturnAnObject returns - or will it have fallen out of scope?

It depends.  You did not show the actual declaration and implementation of CreateAndReturnAnObject(), so it is possible that it could go either way.

 

For instance, if the parameter is an untyped var, then the created object WILL NOT have its reference count incremented when assigned to the parameter, and so the object will die (or worse, be leaked) when CreateAndReturnAnObject() exits:

function TSomeclass.CreateAndReturnAnObject(var MyObject): Boolean;
begin
  MyObject := TMyInterface.Create; // <-- refcount IS NOT incremented!
  Result := True;
end;

To solve that, you would need to use an explicit type-cast:

function TSomeclass.CreateAndReturnAnObject(var MyObject): Boolean;
begin
  IMyInterface(MyObject) := TMyInterface.Create; // <-- refcount IS incremented!
  Result := True;
end;

// Or:

function TSomeclass.CreateAndReturnAnObject(var MyObject): Boolean;
begin
  MyObject := TMyInterface.Create as IMyInterface; // <-- refcount IS incremented!
  Result := True;
end;

Or, call _AddRef() directly:

function TSomeclass.CreateAndReturnAnObject(var MyObject): Boolean;
var
  Intf: IMyInterface;
begin
  Intf := TMyInterface.Create; // <-- refcount IS incremented here
  MyObject := Intf;
  Intf._AddRef; // <-- refcount is incremented again
  Result := True;
end; // <-- refcount is decremented here

On the other hand, if the parameter is typed as a var reference to IMyInterface, then the reference count WILL be incremented as expected:

function TSomeclass.CreateAndReturnAnObject(var MyObject: IMyInterface): Boolean;
begin
  MyObject := TMyInterface.Create; // <-- refcount IS incremented!
  Result := True;
end;

The calling code you have shown would compile in both cases (once you drop the var at the call site, see below), so it is important to know what the parameter is actually typed as to begin with.

2 hours ago, Incus J said:

How about if I omit 'var' from the parameter?

Specifying var when passing a variable to a function parameter is not valid syntax to begin with.  You can use var only when declaring a variable/parameter, but not when passing it around.  Pascal is not C#, or other languages, that require you to be explicit about how a variable is passed to a function parameter.

2 hours ago, Incus J said:

Since MyObject starts of as nil, I'm guessing it can't have its reference count auto-incremented until after it is created, so will it cease to exist once CreateAndReturnAnObject completes, in which case I can't 'Do something with MyObject', or does the compiler know to keep it around somehow?

It WILL be auto-incremented when it is assigned to a variable/parameter of a valid interface type.

  • Thanks 1

Share this post


Link to post

So much to learn - and so many opportunities to make mistakes that will still compile while unwittingly shooting myself in the foot 🙂

 

I didn't even know an untyped var parameter was permitted - in my specific case it would be typed, as below, but I can see myself doing something like accidentally specifying a type of TMyInterface instead of IMyInterface due to a momentary distraction, tiredness or loss of focus.

Quote

Specifying var when passing a variable to a function parameter is not valid syntax

Yes, sorry - that's a typo on my part, should have been:

if SomeclassInstance.CreateAndReturnAnObject(MyObject) then begin

My understanding now is that the first version of the declaration below (with var) would successfully auto-reference count and retain MyObject on return, whereas the second version would end up returning (a pointer to?) an instance of MyObject that had just been freed:

function TSomeclass.CreateAndReturnAnObject(var MyObject:IMyInterface):boolean;
begin
  MyObject:=TMyInterface.Create;
  result:=true;
end; //MyObject retained.

vs

function TSomeclass.CreateAndReturnAnObject(MyObject:IMyInterface):boolean;
begin
  MyObject:=TMyInterface.Create;
  result:=true;
end; //MyObject vapourises here.

When an interfaced object (e.g. MyObject) goes out of scope and is automatically freed, does it get nil assigned to it, or does it behave the same as non-interfaced objects which keep a dangling pointer to the freed instance after SomeObject.Free (unless SomeObject.FreeAndNil is used to free it)?

Edited by Incus J

Share this post


Link to post
5 hours ago, Incus J said:

perhaps there's a better approach?

Yes: Don't be lazy. This is the better approach:

5 hours ago, Incus J said:

MyObject := TSomeclass.CreateAndReturnAnObject;
if (MyObject <> nil) then
begin
  ...

 

 

Don't use Assigned() unless you have a reason to. Assigned() just adds another layer that your brain has to parse. Testing against nil makes it immediately clear what's going on.

 

 

51 minutes ago, Incus J said:

My understanding now is that the first version of the declaration below (with var) would successfully auto-reference count and retain MyObject on return

Yes.

 

52 minutes ago, Incus J said:

whereas the second version would end up returning (a pointer to?) an instance of MyObject that had just been freed:

Yes and no. Since the MyObject parameter is a value parameter you're not returning anything (besides the boolean). And since you're not returning a reference to the interface the ref count will become 0 when the method returns and the object will be destroyed. The debugger would have shown you this if you'd tried it.

  • Like 1

Share this post


Link to post
4 hours ago, Incus J said:

I didn't even know an untyped var parameter was permitted

https://docwiki.embarcadero.com/RADStudio/en/Parameters_(Delphi)#Untyped_Parameters

4 hours ago, Incus J said:

My understanding now is that the first version of the declaration below (with var) would successfully auto-reference count and retain MyObject on return

Yes.

4 hours ago, Incus J said:

whereas the second version would end up returning (a pointer to?) an instance of MyObject that had just been freed

No.  It would not return anything at all, because the parameter is being passed in by value, not by var reference.  So, it is essentially just a local variable that is a copy of the caller's variable.  As such, you are just assigning the object to a local variable, and the caller's variable is left untouched.  The object's refcount will be incremented when the local variable is assigned to, and then be decremented when the local variable goes out of scope.  The caller will never see the object at all.

4 hours ago, Incus J said:

When an interfaced object (e.g. MyObject) goes out of scope and is automatically freed, does it get nil assigned to it

It is not the object itself that goes out of scope, just the interface variable that refers to the object.  But no, the variable is not assigned nil, it is already out of scope, its value doesn't matter anymore.  But, when the variable does go out of scope, if it has a non-nil value then the refcount of the object it is referring to will be decremented.  The object is automatically destroyed only when its refcount falls to 0.

4 hours ago, Incus J said:

or does it behave the same as non-interfaced objects which keep a dangling pointer to the freed instance after SomeObject.Free (unless SomeObject.FreeAndNil is used to free it)?

Never call Free() on an interfaced object.  Use reference counting instead.  And, there is no FreeAndNil() member method, only a standalone function.  So SomeObject.FreeAndNil() is not valid, only FreeAndNil(SomeObject) is valid.  And, it is only valid on an object variable, not an interface variable.

 

In any case, if you need an interface variable, that has not gone out of scope yet, to be set to nil automatically when the object it refers to is destroyed, you need to declare the interface variable as [weak].

Share this post


Link to post

as you want "have" a obj-reference in your function( var XXXX ):boolean.... you should use function( out XXXX):boolean;

the scope should be the same then "var definition" on caller procedure!!! if "the var is LOCAL (into proceudre) then the life is local", else if the var is "unit-GLOBAL", then, the obj is alive while unit is alive.

unit uMyInterfX;

interface

type
  IMyInterfX = interface
    ['{AE871A32-4B6A-4E4B-B825-92B762493D3F}']
    function Hello(AValue: string = ''): string;
    function HelloObj(out MyObjOUT: IMyInterfX): boolean;
  end;

  TMyClassX = class(TInterfacedObject, IMyInterfX)
  private

  public
    function Hello(AValue: string = ''): string;
    function HelloObj(out MyObjOUT: IMyInterfX): boolean;
  end;

implementation

uses
  System.SysUtils;

{ TMyClassX }

function TMyClassX.Hello(AValue: string = ''): string;
begin
  result := 'Hello World: ' + AValue + '  ref-Counting: (' + Self.RefCount.ToString + ') ';
end;

function TMyClassX.HelloObj(out MyObjOUT: IMyInterfX): boolean;
begin
  result := false;
  //
  try
    MyObjOUT := TMyClassX.Create; // created locally, but will be refereced in "caller Obj"-scope!!
    result   := true;
  except
    // ?
  end;
end;

end.

 

var
  Form1: TForm1;

implementation

{$R *.dfm}

uses
  uMyInterfX;

var
  LMyInterfXGLOBALForUnit: IMyInterfX;
  LObjGlobal             : IMyInterfX;

procedure MyOtherProc(const AObj: IMyInterfX);
begin
  if (AObj <> nil) then
    ShowMessage(AObj.Hello(' AObj from MyOtherProc'));
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  LMyInterfXLOCALForProcedure: IMyInterfX;
  LObj                       : IMyInterfX; // local obj interfaced but will created into "TMyClass interfaced" ... it is gone too!
  LObj2                      : IMyInterfX;
begin
  LMyInterfXLOCALForProcedure := TMyClassX.Create;
  // ...try
  ShowMessage(LMyInterfXLOCALForProcedure.Hello + ' LMyInterfXLOCALForUnit');
  // finally
  // ...LMyInterfXLOCALForProcedure.FREE;  // does not works in Interfaced object!!!
  // ...end;
  //
  if LMyInterfXLOCALForProcedure.HelloObj(LObj) and (LObj <> nil) then
    ShowMessage(LObj.Hello(' from LObj (Local)'));
  //
  MyOtherProc(LObj);
  LObj2 := LObj;
  MyOtherProc(LObj);
  //
  //
  LMyInterfXGLOBALForUnit := TMyClassX.Create;
  //
  ShowMessage(LMyInterfXLOCALForProcedure.Hello + ' LMyInterfXGLOBALForUnit');
  //
  if LMyInterfXGLOBALForUnit.HelloObj(LObjGlobal) and (LObjGlobal <> nil) then
    ShowMessage(LObjGlobal.Hello(' from LObj (Global)'));
  //
  // summary:
  // LMyInterfXLOCALForProcedure is done when this procedure out-of-scope
  // LMyInterfXGLOBALForUnit still alive same that this procedure is out-of-scope... until unit is gone!
end;

procedure TForm1.Button2Click(Sender: TObject);
var
  LObj2: IMyInterfX;
begin
  // ShowMessage(LMyInterfXLOCALForProcedure.Hello + ' LMyInterfXLOCALForUnit'); // not works because it's a LOCAL definition
  //
  if (LMyInterfXGLOBALForUnit <> nil) then
    begin
      ShowMessage(LMyInterfXGLOBALForUnit.Hello + ' LMyInterfXGLOBALForUnit is alive');
      //
      if (LObjGlobal <> nil) then
        ShowMessage(LObjGlobal.Hello(' Obj Global its alive '))
      else
        ShowMessage('Obj Global is nil');
    end
  else
    ShowMessage('LMyInterfXGLOBALForUnit is nil');
  //
  MyOtherProc(LObjGlobal);
  LObj2 := LObjGlobal;
  MyOtherProc(LObjGlobal);
end;

initialization

ReportMemoryLeaksOnShutdown := true;

end.

image.thumb.png.6fcf92e6ee7f68840d02eebdaf688476.png

Edited by programmerdelphi2k
  • Like 1

Share this post


Link to post
implementation

uses
  System.SysUtils,
  Vcl.Dialogs;

{ TMyClassX }

destructor TMyClassX.Destroy;
begin
  ShowMessage('calling TMyClass.Destroy...');  /// how many times is called?
  //
  inherited;
end;

// verifying pointer
function TMyClassX.Hello(AValue: string = ''): string;
begin
  result := 'Pointer: ' + integer(Self).ToString + '  ' + 'Hello World: ' + AValue + '  ref-Counting: (' + Self.RefCount.ToString + ') ';
end;

 

Edited by programmerdelphi2k

Share this post


Link to post
Quote

  4 hours ago, Incus J said:

whereas the second version would end up returning (a pointer to?) an instance of MyObject that had just been freed

No.  It would not return anything at all, because the parameter is being passed in by value, not by var reference.

Ah - so that is a key difference to, for example, passing in an parameter of type TStringlist - even without 'var' I think an object such as TStringlist gets passed by reference (?)

 

 

Share this post


Link to post
12 minutes ago, Incus J said:

Ah - so that is a key difference to, for example, passing in an parameter of type TStringlist - even without 'var' I think an object such as TStringlist gets passed by reference (?)

Yes, because objects are reference types. You are passing in a pointer to a TStringList object, so it doesn't matter whether the pointer itself is passed by value or by reference, you still have to dereference the pointer either way to access the object.

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×