Alberto Paganini 3 Posted July 8, 2020 (edited) 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 July 8, 2020 by Alberto Paganini Share this post Link to post
David Heffernan 2345 Posted July 8, 2020 (edited) 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 July 8, 2020 by David Heffernan 1 Share this post Link to post
Remy Lebeau 1397 Posted July 8, 2020 (edited) 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 July 8, 2020 by Remy Lebeau 1 Share this post Link to post
Alberto Paganini 3 Posted July 9, 2020 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
Remy Lebeau 1397 Posted July 9, 2020 (edited) 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 July 9, 2020 by Remy Lebeau 1 Share this post Link to post
Alberto Paganini 3 Posted July 10, 2020 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
Stefan Glienke 2004 Posted July 10, 2020 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. 1 1 Share this post Link to post
Alberto Paganini 3 Posted July 11, 2020 (edited) @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 July 11, 2020 by Alberto Paganini Share this post Link to post
Stefan Glienke 2004 Posted July 11, 2020 (edited) 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 July 11, 2020 by Stefan Glienke 1 Share this post Link to post