VSoft Technologies Blogs

rss

VSoft Technologies Blogs - posts about our products and software development.


WeakRefence in Delphi - solving circular interface references


Using interfaces and reference counting in Delphi works great for the most part. Its a feature I use a lot, I'm a big fan of using interfaces to tightly control what parts of a class a consumer has access to. But, there is one big achillies heel with reference counting in Delphi, you cannot keep circular references, at least not easily, without causing memory leaks.

Consider this trivial example :

 
IChild = interface;
IParent = interface
['{62DC70E1-8D82-4012-BF01-452EB0F7F45A}']
  procedure AddChild(const AChild : IChild);
end;

IChild = interface
['{E1DB1DA0-55D6-408E-8143-072CA433412D}']
end;

TParent = class( TInterfacedObject, IParent )
private
  FChild : IChild;
  procedure AddChild(const AChild : IChild);
public
  destructor Destroy; override;
end;

TChild = class( TInterfacedObject, IChild )
private
  FParent : IParent;
public
  constructor Create( AParent : IParent );
  destructor Destroy; override;
end;

implementation
	
constructor TChild.Create(AParent: IParent);
begin
  inherited Create;
  FParent := AParent;
  AParent.AddChild(Self);
end;

destructor TChild.Destroy;
begin
  FParent := nil;
  inherited;
end;
	
procedure TParent.AddChild(const AChild: IChild);
begin
  FChild := AChild;
end;

destructor TParent.Destroy;
begin
  if Assigned( FChild ) then
    FChild := nil;
  inherited;
end;

procedure Test;
var 
  MyParent : IParent;
  MyChild : IChild;
begin
  MyParent := TParent.Create;
  MyChild := TChild.Create(MyParent);
  MyChild := nil;
  MyParent := nil;
end;

Both parent and child are now orphaned and we have no reference to them and no way to free them! Ideally, the parent would control the life of the child, but the child would not control the life parent.

So how can we get around this? Well a technique that I have used a lot in the past is to not hold a reference to the parent in the child, but rather just a pointer to the parent.

 
TChild = class(TInterfacedObject,IChild) 
private 
  FParent : Pointer; 
  ... 
end;

constructor TChild.Create(AParent : IParent); 
begin 
	FParent := Pointer(AParent); 
end; 
function TChild.GetParent : IParent; 
begin 
  result := IParent(FParent); // if the parent has been released the we are passing out a bad reference! 
                              // a nil reference would be preferable as it's easy to check. 
end; 

This works well for the most part, but it does have the potential for access voilations if you do not understand or at least know how the child is referencing the parent.

For example :

 
var child : IChild; 
parent : IParent 
begin 
  parent := TParent.Create; 
  child := TChild.Create(parent): 
  parent := nil; //parent will now be freed, since nothing has a reference to it.
  ....... 
  parent := child.GetParent; //kaboom 
end; 

One of my collegues kindly pointed out that C# doesn't suffer from this problem and he uses circular references all the time without even thinking about it. While discussing this, he mentioned the WeakReference class in .NET. It basically allows you to hold a reference to a object without affecting it's lifecycle (ie, not influencing when it will be garbage collected). I figured there must be a way to do this in Delphi, and so set about creating a WeakReference class for Delphi.

I wasn't able to find a reliable way to do this with any old TInterfacedObject descendant, however by creating a TWeakReferencedObject class and the use of generics on Delphi 2010 I did manage to implement something that works well and is not too cumbersome. Lets take a look at our Child/Parent example using a weak reference.

The important part in this is the use of the WeakReference to the parent in the Child class. So instead of declaring

FParent : IParent;

we have

FParent : IWeakReference<IParent>;

We create it using

FParent := TWeakReference<IParent>.Create(parent); //value is an IParent instance

This is how our TChild.GetParent /SetParent methods look now :

 
function TChild.GetParent: IParent; 
begin 
  if FParent <> nil then 
    result := FParent.Data as IParent 
  else 
    result := nil; 
end; 

procedure TChild.SetParent(const value: IParent); 
begin 
  if (FParent <> nil) and FParent.IsAlive then 
    FParent.Data.RemoveChild(Self); 
  FParent := nil; 
  if value <> nil then 
    FParent := TWeakReference<IParent>.Create(value); 
end; 

Note the use of the IsAlive property on our weak reference, this tells us whether the referenced object is still available, and provides a safe way to get a concrete reference to the parent.

I still think this is something that could be solved in a better way by the delphi compiler/vcl guys n girls.

Hopefully someone will find this useful, the code is available for download here - Updated Sunday 28/3/2010

Feedback welcolme, I'm about to start making extensive use of this code, so if you see any holes then please do let me know!





Comments are closed.
Showing 4 Comments
Avatar  Vincent Parrett 7 years ago

I have created a D2007 (or earlier version) of this, not quite as neat but still quite useful : &lt;a href=&quot;http://www.finalbuilder.com/downloads/delphi/WeakRefD2007.zip&quot; rel=&quot;nofollow&quot;&gt;www.finalbuilder.com/downloads/delphi/WeakRefD2007.zip&lt;/a&gt; &lt;br&gt;&lt;br&gt;This version is not thread safe as there is no locking (the D2010 version uses the new TMonitor class for locking).

Avatar  Vincent Parrett 7 years ago

If the file still appears corrupted try a different browser. I found that firefox was caching it and didn't actually download the file again, IE8 downloaded it fine.

Avatar  Vincent Parrett 7 years ago

Hi Salvador&lt;br&gt;&lt;br&gt;I have re-uploaded the zip file and it appears to be ok now, thanks for letting me know.&lt;br&gt;

Avatar  salvador 7 years ago

Hello:&lt;br&gt;&lt;br&gt;I have tried to download the file but it's corrupted.&lt;br&gt;I can not unzip the code.&lt;br&gt;&lt;br&gt;Yours sincerelly.&lt;br&gt;&lt;br&gt;&lt;br&gt;