Jump to content

Recommended Posts

I am just investigating on how this works. Having the following code:

program Project3;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  FastMM4,
  system.Classes,
  System.SysUtils;

type
  MyInterface2 = interface
    ['{8E7BC71F-55EE-4345-8A5B-42C433BE9EBA}']
    procedure print;
  end;

  MyInterface1 = interface
    ['{607A66A6-D68C-4980-9FB1-83B325EE9A91}']
    procedure SetInterface2(aInterface: MyInterface2);
  end;

  TMyClass1 = class(TInterfacedObject, MyInterface1)
  private
    RefToInterFace2: MyInterface2;
  public
    procedure SetInterface2(aInterface: MyInterface2);
  end;

  TMyClass2 = class(TInterfacedObject, MyInterface2)
    constructor Create(int: MyInterface1); reintroduce;
    procedure print;
  end;


{ TMyClass1 }

procedure TMyClass2.print;
begin
  Writeln('TMyClass2.print');
  Sleep(1500);
end;

{ TMyClass2 }

procedure TMyClass1.SetInterface2(aInterface: MyInterface2);
begin
  RefToInterFace2 := aInterface;
  RefToInterFace2.print;
end;

constructor TMyClass2.Create(int: MyInterface1);
begin
  inherited Create;
  int.SetInterface2(Self);
end;

var
  aMyClass1: MyInterface1;
  aMyClass2: MyInterface2;

begin
  aMyClass1 := TMyClass1.Create;
  aMyClass2 := TMyClass2.Create(aMyClass1);
end.


How does TMyClass1 know there is RefToInterFace2 to be set to nil in order to prevent an AV error? Where is RefToInterFace2:=nil invoked from?

I mean, TMyClass1 ancestor does not know about RefToInterFace2 and RefToInterFace2 is not set to nil anywhere in TMyClass1 

 

Many thanks

Alberto

Edited by Alberto Paganini

Share this post


Link to post
55 minutes ago, Alberto Paganini said:

Where is RefToInterFace2:=nil invoked from?

That happens when the variable is destroyed, which is when the destructor is executed. 

Edited by David Heffernan
  • Thanks 1

Share this post


Link to post
1 hour ago, Alberto Paganini said:

How does TMyClass1 know there is RefToInterFace2 to be set to nil in order to prevent an AV error?

When a class object instance is destroyed, its managed data members are finalized automatically for you (inside of TObject.CleanupInstance(), which is called automatically by the RTL).  Strings, dynamic arrays, and interfaces are all managed types that use reference counting.  The RTL automatically decrements their reference counts for you when they go out of scope, such as when a TMyClass1 object is destroyed (it will not necessarily set the variables themselves to nil, though, but that doesn't matter during object destruction).

Quote

Where is RefToInterFace2:=nil invoked from?

RefToInterFace2.Release() is invoked by the RTL when the TMyClass1 object is destroyed.

Quote

I mean, TMyClass1 ancestor does not know about RefToInterFace2

Actually, it does.  When the TMyClass1 object is destroyed, the RTL calls TObject.CleanupInstance(), which then loops through TMyClass1's RTTI finalizing all managed class members.  That destruction happens when your aMyClass1 variable goes out of scope on the 'end.' statement, where the RTL finalizes the aMyClass1 variable, decrementing the TMyClass1 object's refcount.  And since its refcount falls to 0, the TMyClass1 destructor is called.  Your aMyClass2 variable will have already gone out of scope and been finalized by that time, so the refcount of the TMyClass2 object will be 1 when the TMyClass1 destructor is entered.  When RefToInterFace2 is finalized by the RTL, the TMyClass2 object's refcount falls to 0, calling the TMyClass2 destructor.

 

Edited by Remy Lebeau
  • Like 1

Share this post


Link to post

But If I store the interface in a local variable in TMyClass2, Ref in my example, I have a leak instead. It seems the RTL cannot have access to it. The only way to release the variable is to use a method and call this method from the main begin..end. Here below the minimum example.

program Project3;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  FastMM4,
  system.Classes,
  System.SysUtils;

type
  MyInterface2 = interface
    ['{8E7BC71F-55EE-4345-8A5B-42C433BE9EBA}']
    procedure print;
  end;

  MyInterface1 = interface
    ['{607A66A6-D68C-4980-9FB1-83B325EE9A91}']
    procedure SetInterface2(aInterface: MyInterface2);
    procedure DoSomethingWithInterface2;
  end;

  TMyClass1 = class(TInterfacedObject, MyInterface1)
  private
    RefToInterFace2: MyInterface2;
  public
    procedure SetInterface2(aInterface: MyInterface2);
    procedure DoSomethingWithInterface2;
    destructor Destroy;override;
  end;

  TMyClass2 = class(TInterfacedObject, MyInterface2)
  private
    Ref: MyInterface1;
  public
    constructor Create(int: MyInterface1); reintroduce;
    procedure print;
  end;


{ TMyClass1 }

procedure TMyClass2.print;
begin
  Writeln('TMyClass2.print');
  Sleep(1500);
end;

{ TMyClass2 }

destructor TMyClass1.Destroy;
begin
  SetInterface2(nil);// this isn not executed
  inherited;
end;

procedure TMyClass1.DoSomethingWithInterface2;
begin
  RefToInterFace2.print;
end;

procedure TMyClass1.SetInterface2(aInterface: MyInterface2);
begin
  RefToInterFace2 := aInterface;
end;

constructor TMyClass2.Create(int: MyInterface1);
begin
  inherited Create;
  Ref := int;
  Ref.SetInterface2(Self);
end;

var
  aMyClass1: MyInterface1;
  aMyClass2: MyInterface2;

begin
  aMyClass1 := TMyClass1.Create;
  aMyClass2 := TMyClass2.Create(aMyClass1);
  aMyClass1.DoSomethingWithInterface2;
  aMyClass1.SetInterface2(nil);// this releases the variable
end.

Is this the only way to release the pointer in the variable or is there a better way? If I add a destructor that is not executed.

 

Many thanks

Alberto

Share this post


Link to post
1 hour ago, Alberto Paganini said:

But If I store the interface in a local variable in TMyClass2, Ref in my example, I have a leak instead.

That is a VERY OLD problem that interface users must always be careful of.  You are creating a strong circular reference between the two classes.  TMyClass1 has a strong reference to TMyClass2, so the refcount of TMyClass2 is incremented.  And TMyClass2 has a strong reference to TMyClass1, so the recount of TMyClass1 is incremented.  In that situation, neither object's refcount can fall to 0 to destroy either object without explicit intervention to release one of the references, otherwise both objects are leaked.

 

So yes, one solution is to explicitly release one of the references to decrement its refcount manually.

 

Another solution is to have one of the objects use a weak reference instead of a strong reference, so that the refcount of the weak-referenced object is not incremented to begin with.  Delphi 10.1 Berlin added support for [weak] and [unsafe] attributes on interface variables.  But prior to 10.1 Berlin, raw Pointers have to be used to accomplish the same thing.

 

For example:

program Project3;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  FastMM4,
  system.Classes,
  System.SysUtils;

type
  MyInterface2 = interface
    ['{8E7BC71F-55EE-4345-8A5B-42C433BE9EBA}']
    procedure print;
  end;

  MyInterface1 = interface
    ['{607A66A6-D68C-4980-9FB1-83B325EE9A91}']
    procedure SetInterface2(aInterface: MyInterface2);
    procedure DoSomethingWithInterface2;
  end;

  TMyClass1 = class(TInterfacedObject, MyInterface1)
  private
    RefToInterFace2: MyInterface2;
  public
    procedure SetInterface2(aInterface: MyInterface2);
    procedure DoSomethingWithInterface2;
  end;

  TMyClass2 = class(TInterfacedObject, MyInterface2)
  private
    // in Delphi 10.1 Berlin and later, use this...
    [weak]{or [unsafe]} Ref: MyInterface1;

    // prior to Delphi 10.1 Berlin, use this...
    // (you will have to typecast Ref every time you want to access MyInterface1)
    // Ref: Pointer{MyInterface1};
  public
    constructor Create(int: MyInterface1); reintroduce;
    procedure print;
  end;


{ TMyClass1 }

procedure TMyClass2.print;
begin
  Writeln('TMyClass2.print');
  Sleep(1500);
end;

{ TMyClass2 }

procedure TMyClass1.DoSomethingWithInterface2;
begin
  if RefToInterFace2 <> nil then
    RefToInterFace2.print;
end;

procedure TMyClass1.SetInterface2(aInterface: MyInterface2);
begin
  RefToInterFace2 := aInterface;
end;

constructor TMyClass2.Create(int: MyInterface1);
begin
  inherited Create;
  Ref := int;
  if int <> nil then
    int.SetInterface2(Self);
end;

var
  aMyClass1: MyInterface1;
  aMyClass2: MyInterface2;

begin
  aMyClass1 := TMyClass1.Create;
  aMyClass2 := TMyClass2.Create(aMyClass1);
  aMyClass1.DoSomethingWithInterface2;
end.

Or:

program Project3;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  FastMM4,
  system.Classes,
  System.SysUtils;

type
  MyInterface2 = interface
    ['{8E7BC71F-55EE-4345-8A5B-42C433BE9EBA}']
    procedure print;
  end;

  MyInterface1 = interface
    ['{607A66A6-D68C-4980-9FB1-83B325EE9A91}']
    procedure SetInterface2(aInterface: MyInterface2);
    procedure DoSomethingWithInterface2;
  end;

  TMyClass1 = class(TInterfacedObject, MyInterface1)
  private
    // in Delphi 10.1 Berlin and later, use this...
    [weak]{or [unsafe]} RefToInterFace2: MyInterface2;

    // prior to Delphi 10.1 Berlin, use this...
    // (you will have to typecast RefToInterFace2 every time you want to access MyInterface2)
    // RefToInterFace2: Pointer{MyInterface2};
  public
    procedure SetInterface2(const aInterface: MyInterface2);
    procedure DoSomethingWithInterface2;
  end;

  TMyClass2 = class(TInterfacedObject, MyInterface2)
  private
    Ref: MyInterface1;
  public
    constructor Create(int: MyInterface1); reintroduce;

    // prior to Delphi 10.1 Berlin, use this...
    // destructor Destroy; override;
    
    procedure print;
  end;


{ TMyClass1 }

procedure TMyClass2.print;
begin
  Writeln('TMyClass2.print');
  Sleep(1500);
end;

{ TMyClass2 }

procedure TMyClass1.DoSomethingWithInterface2;
begin
  if RefToInterFace2 <> nil then
  begin
    // in Delphi 10.1 Berlin and later, use this...
    RefToInterFace2.print;

    // prior to Delphi 10.1 Berlin, use this...
    // MyInterface2(RefToInterFace2).print;
  end;
end;

procedure TMyClass1.SetInterface2(const aInterface: MyInterface2);
begin
  RefToInterFace2 := aInterface;
end;

constructor TMyClass2.Create(int: MyInterface1);
begin
  inherited Create;
  Ref := int;
  if int <> nil then
    int.SetInterface2(Self);
end;

// prior to Delphi 10.1 Berlin, use this...
{destructor TMyClass2.Destroy;
begin
  if Ref <> nil then
    MyInterface1(Ref).SetInterface2(nil);
  inherited Destroy;
end;}

var
  aMyClass1: MyInterface1;
  aMyClass2: MyInterface2;

begin
  aMyClass1 := TMyClass1.Create;
  aMyClass2 := TMyClass2.Create(aMyClass1);
  aMyClass1.DoSomethingWithInterface2;
end.

 

Edited by Remy Lebeau
  • Like 1

Share this post


Link to post

Hi Remi,

 

I had to amend your code with the following one in order to make the example compile by DX2

procedure TMyClass1.SetInterface2(const aInterface: MyInterface2);
begin
  RefToInterFace2 := Pointer(aInterface);
end;

The rest is all spot on!

 

I suppose that the advantage to use the second solution is you can call any method of MyInterface1 throughout TMyClass2 by using Ref while in the first one you can do it only in TMyClass2.Create as MyInterface1 is not stored in a variable.

 

Thank you very much for taking the time.

 

Alberto

Share this post


Link to post

Use Weak<T> from Spring4D in any version before one that supports [weak] if you want proper cleanup of such weak references.

Using the pointer approach will not do and you might run into AVs caused by dangling references.

  • Like 1
  • Thanks 1

Share this post


Link to post

@Stefan Glienke

I have amended the example according to your advice. 

 

program Project3;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  FastMM4,
  system.Classes,
  System.SysUtils,
  Spring;

type
  MyInterface2 = interface
    ['{8E7BC71F-55EE-4345-8A5B-42C433BE9EBA}']
    procedure print;
  end;

  MyInterface1 = interface
    ['{607A66A6-D68C-4980-9FB1-83B325EE9A91}']
    procedure SetInterface2(const aInterface: MyInterface2);
    procedure DoSomethingWithInterface2;
  end;

  TMyClass1 = class(TInterfacedObject, MyInterface1)
  private
    //RefToInterFace2: Pointer{MyInterface2};
    RefToInterFace2: Weak<MyInterface2>;
  public
    procedure SetInterface2(const aInterface: MyInterface2);
    procedure DoSomethingWithInterface2;
  end;

  TMyClass2 = class(TInterfacedObject, MyInterface2)
  private
//    Ref: MyInterface1;
    ref: Weak<MyInterface1>;
  public
    constructor Create(int: MyInterface1); reintroduce;
    destructor Destroy; override; // this can be deleted
    procedure print;
  end;


{ TMyClass1 }

procedure TMyClass2.print;
begin
  Writeln('TMyClass2.print');
  Sleep(1500);
end;

{ TMyClass2 }

procedure TMyClass1.DoSomethingWithInterface2;
begin
  if RefToInterFace2 <> nil then
  begin
//    MyInterface2(RefToInterFace2).print;
    RefToInterFace2.Target.print;
  end;
end;

procedure TMyClass1.SetInterface2(const aInterface: MyInterface2);
begin
//  RefToInterFace2 := Pointer(aInterface);
  RefToInterFace2.Target := aInterface;
end;

constructor TMyClass2.Create(int: MyInterface1);
begin
  inherited Create;
  if int <> nil then
  begin
    Ref.Create(int);
    Ref.Target.SetInterface2(Self);
  end;
end;

destructor TMyClass2.Destroy;
begin
//  if Ref <> nil then
//    MyInterface1(Ref).SetInterface2(nil);
  inherited Destroy;
end;

var
  aMyClass1: MyInterface1;
  aMyClass2: MyInterface2;

begin
  aMyClass1 := TMyClass1.Create;
  aMyClass2 := TMyClass2.Create(aMyClass1);
  aMyClass1.DoSomethingWithInterface2;
end.

 

Is that acceptable?

In which case the pointer approach proposed by Remi may cause AV errors?

 

Many thanks

 

Alberto

Edited by Alberto Paganini

Share this post


Link to post

Thats wrong, only the one that had the [weak] attribute needs to be a Weak<T>. If the reference a weak is pointing to is being destroyed the RTL takes care of making any [weak] marked reference nil. Same does Spring with a Weak<T>. With the pointer that does not happen.

 

So if DoSomethingWithInterface2 happens to be called after TMyClass2  was destroyed (possibly not with this example but a common scenario) with the pointer approach you will run into an AV or other bad things if you dont explicitly set those weak reference back to nil which is why Remy made the dtor which did that - however in real code an object usually does not keep track of all the places it might be weak referenced.

Edited by Stefan Glienke
  • Thanks 1

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

×