Delphi's interfaces are rooted in COM interop, but in most cases are used for non COM purposes. Unfortunately, there are some big limitations with Delphi interfaces, and in a critical area where interfaces are the most useful.

Exception, Not the Rule

I rarely use interfaces. I find that in many cases, they add complexity, rather than solve it. However, there are valid cases for interfaces such as IEnumerable , data binding, IList , etc. Interfaces allow much of what is often sought after with multiple inheritance, but without multiple inheritance's problems.

Object Interface Support

The goal of using an interface is to allow a common API among objects that otherwise do not share a common ancestor other than TObject . Interfaces can also be useful in allowing limited exposure of private members.

In Delphi however, not all objects can be used with interfaces. To use an object with interfaces, one must add special code for reference counting, or descend from (directly or indirectly) one of the specialized classes that already supports interfaces. These classes are:

TInterfacedObject

TInterfacedPersistent

TComponent

There may be others as well, but these are the primary players. Likely it was done this way due to concerns of adding additional baggage to the entire object tree. Unfortunately, this causes some problems as well.

They are declared as follows:

TObject TInterfacedObject TPersistent TInterfacedPersistent TComponent



If all of the objects that you wish to use a specified interface on all descend from the same class that adds interface support, all is well. However if you have objects that inherit their interface support from different classes, then there is a big problem. It does not work.

For example, imagine we have an interface ILife and we have 2 classes. One class inherits from TComponent , another from TInterfacedPersistent . Each can implement ILife , but we cannot simply get an ILife interface using the common ancestor TPersistent .

Using TComponent

First, let me show you a simple example where three classes all descend from TComponent .

TComponent TComponentA TComponentB TComponentC



unit UnitA; interface uses System.SysUtils, System.Classes; type ILife = interface [ ' {BDC73295-9F45-4BEC-B726-77DEC9B9EAAC}' ] function GetAnswer: Integer ; end ; TComponentA = class(TComponent, ILife) function GetAnswer: Integer ; end ; TComponentB = class(TComponent, ILife) function GetAnswer: Integer ; end ; TComponentC = class(TComponentB, ILife) end ; procedure TestA; implementation procedure TestIntf(aComp: TComponent); var i: integer ; xILife: ILife; begin xILife := aComp as ILife; i := xILife.GetAnswer; WriteLn( ' Answer: ' + i.ToString); end ; procedure TestA; var xCompA, xCompB, xCompC: TComponent; begin WriteLn( ' TestA' ); xCompA := TComponentA.Create(nil); try TestIntf(xCompA); finally xCompA.Free; end ; xCompB := TComponentB.Create(nil); try TestIntf(xCompB); finally xCompB.Free; end ; xCompC := TComponentC.Create(nil); try TestIntf(xCompC); finally xCompC.Free; end ; WriteLn; end ; function TComponentA.GetAnswer: Integer ; begin Result := 42 ; end ; function TComponentB.GetAnswer: Integer ; begin Result := 22 ; end ; end .

This code works fine and produces the expected output:

TestA Answer: 42 Answer: 22 Answer: 22

The Problem

The problem is that this only works if all of the classes which use ILifeA all descend from TComponent . This limitation largely defeats one of the primary purposes of interfaces. Let's change one of the classes to descend from TInterfacedObject instead. TInterfacedObject is a direct descendant of TObject , and exists only to add interface support.

For example, this will not work:

type ILife = interface [ ' {CED2DFC6-4E8E-42F2-A724-2E3D8539192F}' ] function GetAnswer: Integer ; end ; TObjectB1 = class(TObject, ILifeB) function GetAnswer: Integer ; end ;

If you try to compile this, the following error will occur:

[dcc32 Error] UnitB.pas(14): E2291 Missing implementation of interface method IInterface.QueryInterface [dcc32 Error] UnitB.pas(14): E2291 Missing implementation of interface method IInterface._AddRef [dcc32 Error] UnitB.pas(14): E2291 Missing implementation of interface method IInterface._Release [dcc32 Error] UnitB.pas(27): E2015 Operator not applicable to this operand type [dcc32 Error] UnitB.pas(38): E2034 Too many actual parameters [dcc32 Fatal Error] Project1.dpr(9): F2063 Could not compile used unit ' UnitB.pas'

This is because TObject does not have the necessary scaffolding required for interfaces. We can solve this easily by changing the ancestor to TInterfacedObject instead of TObject :

type ILife = interface [ ' {26621188-5BE3-42B4-A906-2963B480C1F4}' ] function GetAnswer: Integer ; end ; TObjectC1 = class(TInterfacedObject, ILife) private function GetAnswer: Integer ; public function GetFoo: integer ; end ;

All should be good now right? Well, no. There are MORE problems. Look at this code:

procedure TestIntf(aObj: TInterfacedObject); var i: integer ; xILife: ILifeC; begin xILife := aObj as ILife; i := xILife.GetAnswer; WriteLn( ' Answer: ' + i.ToString); end ; procedure TestC_1; var xObjC1: TInterfacedObject; begin WriteLn( ' TestC_1' ); xObjC1 := TObjectC1.Create; try TestIntf(xObjC1); finally xObjC1.Free; end ; WriteLn; end ;

On quick look, one would expect this should work fine. However, it does not because TInterfacedObject changes how objects work when interfaces are actually used, but not when they are not. This code will crash on this statement:

xObjC1.Free;

Why? Because when we use the ILife interface and we are done with it, the compiler uses reference counting and frees the whole object. You can see this in action by adding a dummy destructor to TObjectC1 . Then set a breakpoint and look at the call stack. The destructor will be called after TestIntf is called.

New Problem

Interfaces when used with classes that descend from TComponent do not exhbit this behavior that for Delphi is non standard in non ARC compilers (Windows). So now objects act differently when used with interfaces depending on their ancestor.

So now we have to just think about sometimes destroy sometimes not? Well, it is not that simple either. Now we don't have to free the object, except sometimes! If no interface is used, then we must NOT free it. If an interface is not used, we MUST free it. The problem is, as the object is passed around to other methods, how are we to know if any code takes an interface for it or not?

If an interface is used, anywhere down the line...

xObjC1 := TObjectC1.Create; TestIntf(xObjC1);

Do we free it or not?

xObjC1 := TObjectC1.Create; try i := xObjC1.GetFoo; finally xObjC1.Free; end ;

New hard to find bugs:

xObjC1 := TObjectC1.Create; TestIntf(xObjC1); i := xObjC1.GetFoo;

When your code logic becomes deeper and you add multiple interfaces to a class, the problem gets even worse.

Some of this can be addressed using unsafe and/or weak directives. However, this isn't simply declared on the declaration, and must be used in user code references. A bad solution in my opinion as well.

The Solution?

The supposed solution is to use interface references instead of object references everywhere for such objects. But this defeats much of the reason that interfaces are used and when multiple interfaces on an object are used, the problem only gets worse.

The Tree Problem

A primary use of interfaces is to expose a common interface from disparate objects. Yet, in many cases, this is not workable in Delphi.

type ILife = interface [ ' {7C8E0C18-F8A5-43DF-8999-BF17D6EC961C}' ] function GetAnswer: Integer ; end ; TComponentA = class(TComponent, ILife) function GetAnswer: Integer ; end ; TObjectA = class(TInterfacedObject, ILife) function GetAnswer: Integer ; end ; TPersistentA = class(TInterfacedPersistent, ILife) function GetAnswer: Integer ; end ;

Unfortunately, these are largely unusable to obtain an interface in a generic way. This will not compile:

procedure TestIntf(aObj: TObject); var i: integer ; xILife: ILife; begin xILife := aObj as ILife; i := xILife.GetAnswer; WriteLn( ' Answer: ' + i.ToString); end ;

Now the obvious solution is to pass the interface instead. However, this is not always practical and again eliminates one of the major benefits of interfaces. There are some work arounds, but then we have the problem that if a class descends from:

TComponent - we MUST free it or use Owner to free it.

- we MUST free it or use Owner to free it. TInterfacedPersistent , we MUST free it.

, we MUST free it. TInterfacedObject If any code anywhere used an interface, we must NOT free it. If no code used an interface, we MUST free it.



Seriously? Someone thought this is a good idea?

TInterfacedObject Misnomer

If TInterfacedObject really must work this way, it should have been called TARCObject or something distinctive. TInterfacedPersistent is TPersistent with interface support, yet TInterfacedObject is an TObject with interface support AND non standard lifecycle management? TInterfacedObject documentation references Memory Management of Interface Objects, but this topic only has two short paragraphs and barely a hint at the problems it introduces.

But Just Use Interfaces!

Yeah. I get it. As discussed prior, there are "workarounds" to using TInterfacedObject by using only interface references. But if you are still thinking this is a "solution", you have totally missed the point.

Using only interface references causes complexity in accessing non interface members. There is also a lack of documentation on this issue. And then, we have the life cycle issues should the executed code paths not use an interface, and multiple interfaces require extra code as well.

A Persistent Problem

TInterfacedPersistent does not have the free/maybe free problem that TInterfacedObject does.

TComponent inherits from TPersistent , but not TInterfacedPersistent .

TComponent = class(TPersistent, IInterface, IInterfaceComponentReference)

The VCL declares them like this:

TPersistent TInterfacedPersistent TComponent



This means that if we have:

type ILife = interface [ ' {26621188-5BE3-42B4-A906-2963B480C1F4}' ] function GetAnswer: Integer ; end ; TObjectC1 = class(TInterfacedObject, ILife) private function GetAnswer: Integer ; public function GetFoo: integer ; end ; TPersistentC1 = class(TInterfacedPersistent, ILife) private function GetAnswer: Integer ; public destructor Destroy; override ; end ;

We still cannot use the interface from a TPersistent reference, even though both of them have TPersistent as a base class. If they had declared them this way:

TPersistent TInterfacedPersistent TComponent



At least we could have used interfaces properly between TComponent and TInterfacedPersistent . The interface scaffolding is slightly different between the two though and prevents this. Interfaces and TComponent have issues as well. Although TInterfacedPersistent and TComponent have different interface scaffolding, TComponent could still have been changed to inherit from TInterfacedPersistent , and the interface scaffolding (implemented as methods) could have been overridden. This would have provided an easy solution to this problem.

You can also create your own interface scaffolding on your objects or a base to use, however it doesn't solve the tree problem.

Hacking a Solution

There is a hacky solution - one that should not be needed. This could also be done using RTTI (Runtime reflection for non Delphi developers reading this), but not exactly optimal either. To get an interface crossing different interface scaffolding entry points in the tree, one can do:

function GetALife(aObject: TPersistent): ILife; begin if aObject is TInterfacedPersistent then begin Result := TInterfacedPersistent(aObject) as ILife; end else if aObject is TComponent then begin Result := TComponent(aObject) as ILife; end else begin raise Exception.Create( ' Cannot obtain interface.' ); end ; end ;

This provides a reasonably usable solution, although it should not be necessary in the first place. This method requires a function to be added for each interface. I have avoided implementing TInterfacedObject as I will be avoiding it like the plague, but it can be made to work as well but will of course introduce its lifecycle management problems which will differ from when ILife is returned from TInterfacedPersistent and TComponent .

Conclusion

As if I didn't have enough reasons to avoid interfaces, the way Delphi implements them only adds to the baggage and has made interfaces almost completely useless for me as they add far more code and risk than they help. The net gain for me is strongly negative except in rare cases.

Without adding heavy baggage to TObject , it would have been better to build support into it to allow getting a single interface even if there are separate scaffolding implementations. An interface should be an interface, not dependent on specifics of the class itself to determine compatibility as well as lifecycle management. Further more, the life management of TInterfacedObjects as it is implemented makes them a very dangerous class to use and "proper use" of them severely limits their usefulness.