Weak and Unsafe Interface References in Delphi 10.1 Berlin

Posted by on in Tutorial

Delphi 10.1 Berlin adds a couple of features to the Delphi language, bridging gaps between platforms and extending the Win32/Win64 interface reference model. This is not the only change to the language, but clearly the most relevant one. Another, focused on mobile, is the introduction of the native UTF8String type on all platforms, mobile compilers included.

But let me get back to the way references to interfaces work on the desktop compilers. By default, all references to interfaces use reference counting. As you assign a variable to the interface the reference count is increased, as the variable is set to nil or goes out of scope, the reference count is decreased. When the reference count gets to zero the object is deleted from memory. That is not actually always true, as the actual behavior is implemented by each class, so you can write a class that implements an interface and ignores the reference counting mechanism.

Anyway, getting back to the common scenarios in which reference count is active, you can have code like the following, which relies on reference counting to dispose the temporary object:

procedure TForm3.Button2Click(Sender: TObject); 
var
one: ISimpleInterface;
begin
one := TObjectOne.Create;
one.DoSomething;
end;

What if the object has a standard reference count implementation and you want to create an interface reference that is kept out of the total count of references? You can now achieve this by adding the [unsafe] attribute to the interface variable declaration, changing the code above to:

procedure TForm3.Button2Click(Sender: TObject); 
var
[unsafe] one: ISimpleInterface;
begin
one := TObjectOne.Create;
one.DoSomething;
end;

Not that this is a good idea, as the code above would cause a memory leak. By disabling the reference counting, when the variable goes out of scope nothing happens. There are some scenarios in which this is beneficial, as you can still use interfaces and not trigger the extra reference. In other words, an unsafe reference is treated just like... a pointer, with no extra compiler support.

Now before you consider using the unsafe attribute for having a reference without increasing the count, consider that in most cases there is another better option, that is the use of weak references. Weak references also avoid increasing the reference count, but they are managed. This means that the system keeps track of weak references, and in case the actual object gets deleted, it will set the weak reference to nil. With an unsafe reference, instead, you have no way to know the status of the target object (a scenario called dangling reference).

In which scenarios are weak reference useful? A classic case is that of two object with cross-references. In such a case, in fact, the object would artificially inflate the reference count of the other objects, and they'll basically remain in memory forever (with reference count set to 1), even when they become unreachable.

As an example consider the following interface, accepting a reference to another interface of the same type, and a class implementing it with an internal reference:

type ISimpleInterface = interface 
procedure DoSomething;
procedure AddObjectRef (simple: ISimpleInterface);
end;

TObjectOne = class (TInterfacedObject, ISimpleInterface)
private
anotherObj: ISimpleInterface;
public
procedure DoSomething;
procedure AddObjectRef (simple: ISimpleInterface);
end;

If you create two objects and cross-reference them, you end up with a memory leak:

var 
one, two: ISimpleInterface;
begin
one := TObjectOne.Create;
two := TObjectOne.Create;
one.AddObjectRef (two);
two.AddObjectRef (one);

Now the solution available in Delphi 10.1 Berlin is to mark the private field anotherObj as weak:

private 
[weak] anotherObj: ISimpleInterface;

Now the reference count is not modified when you pass the object as parameter to the AddObjectRef call, it stays at 1, and it goes back to zero when the variables go out of scope, freeing the objects from memory.

Now there are many other cases in which this feature becomes handy, and there is some real complexity in the underlying implementation. It is great feature, but one that takes some effort to fully master. Also, it does have some runtime cost, as weak references are managed (while unsafe ones are not).



About
Gold User, Rank: 7, Points: 457
Delphi and RAD Studio Product Manager at Embarcadero.

Comments

  • Marco Cantu, RAD PM
    Marco Cantu, RAD PM Wednesday, 21 September 2016

    Jasper, using [weak] in COM libraries is not a supported scenario. COM memory management is distributed and fairly complex, and doesn't support language or compiler specific changes. As Allen mentioned in the StackOverflow discussion, please don't do it.

  • Jasper S20892
    Jasper S20892 Tuesday, 20 September 2016

    I am able to use [weak] in com libraries. But when I place the two class implementations in separate libraries I get "invalid class typecast". For more information you can see http://stackoverflow.com/questions/39595842/delphi-weak-reference-attribute-produces-invalid-class-typecast-when-impleme

  • Absa Lootly
    Absa Lootly Monday, 9 May 2016

    OK, thanks for clarifying. I thought that maybe there was an advancement that I did not know about, based on that line I quoted.

    I am trying to write my code in a manner that will allow me to do the vast majority of my testing and debugging on Windows while being intended for Android. This means that I have to be very careful of the differences in memory handling. I mention that only to let you know why I wanted the clarification.

  • Absa Lootly
    Absa Lootly Monday, 9 May 2016

    "By default, all references to interfaces use reference counting"

    I thought that the automatic reference counting was implemented by objects that descend from TInterfacedObject only. I just wanted to get that clarified.

  • Marco Cantu, RAD PM
    Marco Cantu, RAD PM Monday, 9 May 2016

    Yes, Brian, you are correct. As written two lines below, "That is not actually always true, as the actual behavior is implemented by each class, so you can write a class that implements an interface and ignores the reference counting mechanism."
    The reference counting mechanism (automatically calling AddRef and Release) is required, but the actual implementation can vary. Now also the actual mechanism can vary, depending on the attributes discussed here.

  • Please login first in order for you to submit comments
  • Page :
  • 1

Check out more tips and tricks in this development video: