Mega Search
23.2 Million


Sign Up

Make a donation  
Weak interface references in earlier versions of Delphi  
News Group: embarcadero.public.delphi.oodesign

Hi,

I am working on component which need to be supported in earlier version of Delphi (starting from Delphi 6) and I have problem with reference counting of interfaces.

Last days I am trying to learn more about this topic, and I have collected this info:

- XE2 (or maybe XE3) include [weak] attribute, but I need to support very old Delphi 6.
- At few places I have read that if I declare variable as pointer, RefCount will not be incremented. There are some traps with this approach (which I already discovered :( ), but it seems that this approach work when I assign value. But, as soon I cast this pointer into required interface - RefCount is increased. Please check bellow in code (marked with <----).
- I have found some more elegant solutions, but unfortunately they are using generics and are for more new Delphi versions.
- I can't use third-party libs since I need to ship code as commercial product.

Maybe there is some other approach which I don't know. I am not sure is it possible at all to cast, but to avoid increment. Maybe I don't see something obvious (or I brake some basic rule), in last days I am only surfing trough Delphi VCL code - all day :)

Please help. Thank you in advance.

{code}
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  IChild = interface;

  IParent = interface
    ['{49945A1E-AEAA-4A74-9301-F630AE0D5BC7}']
    procedure SetChild(AChild: IChild);
    procedure Test;
  end;

  IChild = interface
    ['{D1B60E54-DF55-4F56-8560-D36F3095FB9C}']
    function GetParent: IParent;
    procedure SetParent(AParent: IParent);
    procedure Test;
    property Parent: IParent read GetParent;
  end;

  TParent = class(TInterfacedObject, IParent)
  public
    FChild: IChild;
    procedure SetChild(AChild: IChild);
    procedure Test;
    destructor Destroy; override;
  end;

  PParent = ^IParent;

  TChild = class(TInterfacedObject, IChild)
  private
    FParent: PParent;
    function GetParent: IParent;
  public
    procedure SetParent(AParent: IParent);
    procedure Test;
    destructor Destroy; override;
    property Parent: IParent read GetParent;
  end;

  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

  P: IParent;
  C, D: IChild;

implementation

{$R *.dfm}

{ TParent }

destructor TParent.Destroy;
begin
  ShowMessage('Parent Destroying');
  inherited;
end;

procedure TParent.SetChild(AChild: IChild);
begin
  if FChild <> nil then FChild.Test;
  FChild := AChild;
end;

procedure TParent.Test;
begin
  ShowMessage('Parent: ' + IntToStr(FRefCount));
end;

{ TChild }

destructor TChild.Destroy;
begin
  ShowMessage('Child Destroying');
  inherited;
end;

function TChild.GetParent: IParent;
begin
  Result := IParent(FParent); // <----------------- increase RefCount
end;

procedure TChild.SetParent(AParent: IParent);
begin
  FParent := PParent(AParent);
end;

procedure TChild.Test;
begin
  ShowMessage('Child: ' + IntToStr(FRefCount));
end;

{ TForm1 }

procedure TForm1.Button1Click(Sender: TObject);
begin
  C := TChild.Create;
  P := TParent.Create;

  C.SetParent(P); // Does not increase RefCount

  P.Test;

  C.Parent.Test; // Access to Parent property increase RefCount

  C.Test;
  P.Test;

  P := nil;
  C := nil;
end;

end.
{code}

Vote for best question.
Score: 0  # Vote:  0
Date Posted: 2-Jul-2014, at 1:14 PM EST
From: Bojan Nikolic
 
Re: Weak interface references in earlier versions of Delphi  
News Group: embarcadero.public.delphi.oodesign
Hi Remy,

Thank you for your answer.

More specific, I have problem inside IParent destructor.

Inside destructor IParent RefCount is already 0 (this is why destructor is called) and if I access to the this object again, it goes from 0 to 1, and back to 0 (on method exit) which call Destructor again. Maybe this can be avoided somehow?

Will try to think about all once again, my brain have melted last days :)

Vote for best answer.
Score: 0  # Vote:  0
Date Posted: 2-Jul-2014, at 2:54 PM EST
From: Bojan Nikolic
 
Re: Weak interface references in earlier versions of Delphi  
News Group: embarcadero.public.delphi.oodesign
> {quote:title=Remy Lebeau (TeamB) wrote:}{quote}
> Why are you accessing the parent object from inside its own destructor using 
> an interface instead of its own Self pointer?  What are you actually doing 
> that is causing the reference count to get bumped?  Please show your real 
> code, becaue what you showed earlier does not do that.

Parent is clearing Childs, and in this process Child access to the Parent which causes this Destructor -> 0 -> 1 -> 0 -> Destructor problem.

Code is quite long (starting being complex) and I have created this small project as playground to isolate problem and learn more.

Maybe I need to rewrite this part of code.

Vote for best answer.
Score: 0  # Vote:  0
Date Posted: 2-Jul-2014, at 3:18 PM EST
From: Bojan Nikolic
 
Re: Weak interface references in earlier versions of Delphi  
News Group: embarcadero.public.delphi.oodesign
> {quote:title=Remy Lebeau (TeamB) wrote:}{quote}
> Bojan wrote:
> 
> > Parent is clearing Childs, and in this process Child access to the
> > Parent which causes this Destructor -> 0 -> 1 -> 0 -> Destructor
> > problem.
> 
> Then the child should not be using its Parent property, which bumps the refcount 
> (and you cannot avoid that), but should instead be using its FParent field 
> directly.
> 
> --
> Remy Lebeau (TeamB)

Hi,

It seems that I will end with this :( . Thank you again for your help. I will review my code in next days. Learned something new.

At the end, is there maybe some library or custom implementation of IInterfacedObject which somehow emulate weak references and they can be used in early version of Delphi?

Thanks.

Vote for best answer.
Score: 0  # Vote:  0
Date Posted: 2-Jul-2014, at 3:38 PM EST
From: Bojan Nikolic
 
Re: Weak interface references in earlier versions of Delphi  
News Group: embarcadero.public.delphi.oodesign
> {quote:title=Robert Dawson wrote:}{quote}
>  wrote
> >
> > Maybe there is some other approach which I don't know. I am not sure is it 
> > possible at all to cast, but to avoid increment.
> 
> Rather than going down in to the technical details...
> 
> Exactly how do you want the lifetime semantics to work? There are a variety 
> of different hacks/approaches, including descending from a non-refcounted 
> interfaced object or IFDEF-ing your code by version.
> 
> What's best depends entirely on a complete and consistent definition of what 
> you consider 'correct' (aka desired) behavior, and that being crystal clear 
> in your mind.
> 
> bobD

Hi Robert,

In my project , I only have a IParent (I named it INode) which have a childs of INode. 

I want that parent node hold a strong reference to childs, but childs to hold a weak reference to parent. Notice here that are both of same interface type. 

I have think about creating my own TInterfacedObject, but I am not sure how this dangerous is. Maybe there is some tested code sample already.

Maybe something like, if possible at all:

{code}
var
  ParentNode: INode;
  ChildNode: INode;
begin
  ParentNode.AssignStrong(OtherNod); // AddRef/Release are used now
  ChildNode.AssignWeak(OtherNode); 
{code}

Vote for best answer.
Score: 0  # Vote:  0
Date Posted: 3-Jul-2014, at 5:45 AM EST
From: Bojan Nikolic
 
Re: Weak interface references in earlier versions of Delphi  
News Group: embarcadero.public.delphi.oodesign
PS. If needed, I may post shortly how my component actually work with interfaces. Maybe it will help.

Vote for best answer.
Score: 0  # Vote:  0
Date Posted: 3-Jul-2014, at 6:05 AM EST
From: Bojan Nikolic
 
Re: Weak interface references in earlier versions of Delphi  
News Group: embarcadero.public.delphi.oodesign
> {quote:title=Bojan Nikolic wrote:}{quote}
> 
> I want that parent node hold a strong reference to childs, but childs to hold a weak reference to parent. Notice here that are both of same interface type. 

Okay, given that circumstance, Remy's absolutely right that the standard pattern would be for the parent to hold an interface reference to the child, and the child a plain pointer reference to the parent.

Since the child's lifetime is guaranteed by the parent, is there any way for a child to be destroyed other than by telling the parent to drop it from the collection? I'm also a bit puzzled that the child would have to call/access the parent in its destructor. Might be some logic there that needs moved.

bobD

Vote for best answer.
Score: 0  # Vote:  0
Date Posted: 3-Jul-2014, at 7:44 AM EST
From: Robert Dawson
 
Re: Weak interface references in earlier versions of Delphi  
News Group: embarcadero.public.delphi.oodesign
Hi,

It seems that I will end up with using Pointers.

Maybe you will have some more ideas, if I try to explain how my component work. It is a Grid control with rows, where each rows may have own rows. Also each row contain a list of Cells.

{code}
Grid
  |
Row 0
  |-----Row 0.0
          |-----Cell 0, Cell 1, Cell 2, Cell 3
Row 1
Row 2
....
Row n
{code}

- Grid is pointing to Row
- Row is pointing to child row (RefCount 1)
- Each Cell is pointing back to child row (RefCount 1 + number of cells)

In this pattern, if I want to destroy Row 0.0 , I need to destroy cells _first_, if I want to destroy child row. I will more like to destroy cells in Row's destructor. As you both suggested I will try with Pointer, but if there is some more elegant solution - I will listen.

This my problem is not stopping me to progress with project, but all will be easier if there was [weak] attribute in old Delphi's

Vote for best answer.
Score: 0  # Vote:  0
Date Posted: 3-Jul-2014, at 9:57 AM EST
From: Bojan Nikolic
 
Re: Weak interface references in earlier versions of Delphi  
News Group: embarcadero.public.delphi.oodesign
Thanks again,

What should I do if I need to delete/create object several times in my control. In my example I have a TEdit descendant who need to be created when edit starts and destroyed when user finish with editing.

1) In this case how to I delete actual instance of interface?

Do I need to do something like :

{code}(MyObj as TEdit).Free;{code}

or

{code}MyObj.Instance.Free;{code}

But if Owner is set, I will need to remove instance from Components list of Owner? In my test ComponentCount of Own is increased after every creation and never decreased (except when destroyed).

2) Is it safe to init this object without Owner:

{code}MyObj := TMyObj.Create(nil);{code}

and then not to bother with removing object from Components list of Owner.

I am able to destroy object (without any leaks and AV's) in this way but it don't look very nice:

{code}
procedure TForm3.Button2Click(Sender: TObject);
var
  T: TMyObj;
begin
  FMyObj := TMyObj.Create(nil);

  T := (FMyObj as TMyObj);

  FMyObj := nil;

  T.Free;
  T := nil;
end;
{code}

PS. If someone of you guys want my components for free, please contact me on PM.

Thanks!

Vote for best answer.
Score: 0  # Vote:  0
Date Posted: 20-Jul-2014, at 2:37 PM EST
From: Bojan Nikolic
 
Re: Weak interface references in earlier versions of Delphi  
News Group: embarcadero.public.delphi.oodesign
Bojan wrote:

> Can you please tell me what is a correct way to destroy/release this
> object?

The easiest way is to simply assign an Owner to the object, and let the Owner 
free it for you when the Owner is freed.  You just have to make sure the 
interace is nil'ed first, otherwise it may try to call _Release() on a non-existant 
object.  You can use the form's OnDestroy event for that, eg:

{code}
procedure TForm1.FormCreate(Sender: TObject);
begin
  MyObj := TMyObj.Create(Self);
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  MyObj := nil;
end;
{code}

> Since TComponent always include Owner, is Owner responsive for
> destroying them?

Yes.

> If so, how he do this?

TComponent has an internal list of components that it owns.  When a TComponent 
is freed, it removes itself from its Owner's list, and frees all of the components 
in its own list.

--
Remy Lebeau (TeamB)

Vote for best answer.
Score: 0  # Vote:  0
Date Posted: 20-Jul-2014, at 10:26 AM EST
From: Remy Lebeau (TeamB)