Stefan Glienke recently added his contribution to the various solutions for implementing weak interface references in Delphi.
He quite neatly and comprehensively covers the need for such things, but his approach requires generics support and so is limited to later versions of the compiler (just how late really depends on how confident you are in the robustness of the generics implementation in each iteration of the Delphi compiler since their introduction).
So I thought I would share my own approach that I have been using for some time and which is usable in all versions of Delphi from 3 (where-in interfaces were introduced).
First, an example of the approach in action using Stefan’s own example as the basis for my own:
type TParent = class(TInterfacedObject, IParent) private fChildren: TInterfaceList; procedure AddChild(const aChild: IChild); public destructor Destroy; override; end; TChild = class(TInterfacedObject, IChild) private fParentRef: TWeakInterface; function get_Parent: IParent; public constructor Create(const aParent: IParent); destructor Destroy; override; property Parent: IParent read get_Parent; end; // The implementation of TParent adding a child to it's list // should be sufficiently obvious as to not need illustration // so I shall show only the use of TWeakInterface in TChild constructor TChild.Create(const aParent: IParent); begin inherited Create; fParentRef := TWeakInterface.Create(aParent); end; destructor TChild.Destroy; begin fParentRef.Free; inherited; end; function TChild.get_Parent: IParent; begin result := fParentRef as IParent; end;
Since the parent is maintaining strong references to the children (ensuring that as long as the parent survives then it’s children do also), what we need to take care of (and the whole point of this and Stefan’s post) is that the children themselves do not hold strong references back to the parent.
I achieve this using a TWeakInterface.
TWeakInterface is essentially an encapsulation of the technique found quite commonly in the VCL itself (as mentioned by Stefano in the comments on Stefan’s post) of holding an interface reference as a plain pointer value. Stefan rightly points out some dangers of this approach, and the advantage of using an encapsulation such as TWeakInterface in this way is that it enables us to address some of these.
With a plain pointer value, the interface it stores is retrieved by hard-casting back to the original interface type:
// Taking a weak reference from a strong one: parentRef := Pointer(someIParent); // Obtaining a strong reference from a weak one: parent := IParent(someIParent);
The potential for mistakes here should be obvious.
When casting the strong reference from the pointer you have to be sure to use the correct interface reference. Use the wrong one (even, for example, an interface that is derived – i.e. inherits – from the actual interface reference that is held) and all sorts of things could potentially go wrong.
So one thing that TWeakInterface does is treat the encapsulated reference it is provided with as IUnknown (or IInterface, if you prefer; Potayto / Potahto).
It also does not expose the “captured” reference at all.
To get the interface reference back out from TWeakInterface you have to ask for it.
Nicely. 🙂
TWeakInterface is itself an interfaced object, but it delegates the entire implementation of IUnknown to the captured interface reference so that when you write:
result := fParentRef as IParent;
It is the captured interface reference that is asked for the requested interface (ref.QueryInterface), rather than a hard type-cast being performed.
Thus if you ask for an interface that the weakly referenced object does not support you will get the exception that you so richly deserve.
It also means that you can even ask for an interface that is different from the one you captured, and as long as the referenced object also implements that interface then that also will be fine.
What this approach does not take care of is the situation when the weakly referenced object is destroyed, leaving a “dangling” weak reference to itself.
As Stefano points out in his reply to this observation, this situation should not occur when using weak references as the very fact that you *have* this potential means that you should have mechanisms in place to deal with it.
e.g. if a child’s parent can be destroyed from out above it’s head, then the child will almost certainly need to know when that happens. NIL‘ing the reference to it’s parent is probably the least significant thing it will need to take into account in that situation.
In my own code I have a multi-cast mechanism available for precisely this “notify destruction” purpose, and I could easily have incorporated this into my TWeakInterface implementation.
That is, in the constructor for TWeakInterface it could query the passed interface for IOn_Destroy support, and if present add a listener which would NIL the captured reference when necessary. TWeakInterface would then also have to implement IOn_Destroy, but that is also a trivial matter.
However, just as Stefano points out, in every instance that this would have been useful, the class incorporating a weak reference to some other object already implements (in my code) an On_Destroy mechanism for much broader purposes, assuming no other, more direct mechanism exists.
So I decided against incorporating this in my weak interface reference encapsulation itself, keeping it as simple as possible.
In Summary, The Complete Class
TWeakInterface = class(TObject) private fRef: Pointer; function get_Ref: IUnknown; protected // IUnknown property Ref: IUnknown read get_Ref implements IUnknown; public constructor Create(const aRef: IUnknown); end; { TWeakInterface --------------------------------------------------------------------------------- } { - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - } constructor TWeakInterface.Create(const aRef: IUnknown); begin inherited Create; fRef := Pointer(aRef); end; { - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - } function TWeakInterface.get_Ref: IUnknown; begin result := IUnknown(fRef) as IUnknown; end;
I like VERY much the “property Ref: IUnknown read get_Ref implements IUnknown” trick.
This is the main interest of this implementation.
I like the definition from this web page:
I’m still looking into the other potential solution I wrote about as comment in the original blog article. But perhaps not worth it. Only benefit would be to have direct storage of the interface: no get_parent method needed, and no fParentRef.Free do add in the destructor. Just use the SetWeak() procedure instead of a simple assignment.
Nice summary,
your implementation would be refactored using generics and record with methods for newer compiler versions:
to keep a pointer you use another object and the cost may be too much if you have to handle a lot references.
I have the following situation in one of my projects.
In a graph where links have weak references to nodes and links and nodes are roads and intersection the memory requirements increase a lot if you use your implementation as the density of the graph increases.
Regards,
Stefano
@Stephano
AFAIK your DSharp.Core.Weak.pas is using generics and a record, but will also allocate one class instance per reference instance: only if the same instance is reused, there will be the memory benefit of using a record. Therefore, if an item is used only once, it will use even more memory than the TWeakInterface trick. Of course, if the density of the graph increase, memory will also increase.
TWeakInterface could also reuse existing pointers, just by adding a global dictionary table. No need of generics here.
Nothing is perfect.
If only such weak references were incorporated within the Delphi compiler, just as Apple did with their ARC implementation!
Careful.
Having recently got to the point of beginning to have to understand ARC, the one thing that ARC and the OS X approach teaches us Delphi users is that introducing different garbage collection models into an environment and existing code base where no such garbage collection AND different garbage collection already exists and has to CO-exist with the new approach, is a recipe for headaches and dizzy spells.
And as I have mentioned before, I find it interesting that the most recent innovation in OS X is – essentially – deterministic lifetime management via reference counting. i.e. COM-style reference counts, i.e. what we already have in Delphi and can choose to use in our projects if we wish, even if we aren’t using COM itself.
The only thing missing is language support for weak references. But unless mindsets change, we can’t expect anything like that anytime soon.
Having “delivered” generics, we are now expected to cobble together a multitude of ugly solutions to such simple problems using those, rather than have elegant in-language features provide a consistent approach that everyone can benefit from more easily.
Brilliant, and completely agree with you on your insights into Automatic Reference Counting.
I believe that this is the way to go for lifetime automation. Just as string memory is automatically reference counted, objects in delphi could be automanaged.
A set of compiler improvements to make this easier would be welcome.
W
My deepest sympathy if you are still stuck with something before XE 😉
I knew there were other implementations for older Delphi versions.
But the combination of records, operator overloading, interfaces (wrapped into a record) and generics allows defining new kind of language constructs like Weak(T), Event(T), or Lazy(T) just to name some.
What do you get at the end:
You can use this as you would use a normal interface reference (with the small exception of having to write .Target when directly accessing members) – assigning and passing to other methods “just works”.
No extra managing of the weak reference itself because its just a record.
With addition of the FreeInstance hack you also get the IsValid check and not more possible spooky EInvalidPointer or AVs.
I know your antipathy to the new language features and I agree that often they are more broken than working and instead of dealing with the classic problems (as mentioned before) you are facing ICEs or even worse wrong codegen. But you should really give generics a chance 😉
Stefan, yes an aspect of it is that I find Generics (as implemented in Delphi) to be completely un-Pascal-like, so limited in utility beyond the usual “collections” application as to be next to useless and, yes, just downright unpleasant.
Not to mention that their stability and robustness has been open to question right from the start. Maybe in XE5… ?
But the far bigger reason for avoiding them like the plague is that no matter what version of Delphi *I* may be using, the vast majority of people (according to just about every survey and poll that is undertaken) are still using versions of Delphi that pre-date these things.
When creating general purpose code that has broad utility it simply doesn’t make sense to alienate the vast majority of people that might find it useful.
Also, bear in mind that this code was originally written before generics. It works. It is simple. It is understood.
Re-writing it simply to “take advantage” of new language features and then having to re-factor all existing code using the existing, understood, working perfectly “old” approach would be – literally – a waste of time. The alternative – having a mix of old and new approaches – is never a good recipe for effective code maintenance, where consistency is key in my experience.
Your mileage may vary. 🙂
Since DSharp targets Delphi 2010 and higher I don’t care at all about older Delphi versions.
Since there are solutions out there for these older versions, good.