Delphi reference counted interfaces

By: Jan Machacek

Abstract: This article covers interfaces in unmanaged Windows and Linux code. It focuses on practical issues developers have to face when they use interfaces in their code.

Why Interfaces?

Interfaces enable us to write code that is implementation-independent. This is very useful when writing more complex applications and becomes even more important when we decide to split the application into packages. With appropriate use of interfaces we can change the implementation of a class in a package, recompile the package and still use it with the original application. Interfaces also allow us to write more loosely coupled class structures resulting in a more flexible and easily upgradeable application.

Interfaces history

The first version of Delphi to support interfaces was Delphi 3. But there was a way to use and develop COM interfaces even in Delphi 2. How was that possible? The answer is simple. If you ignore the fact that a class can implement more than one interface, you can think of an interface as a pure abstract class.


type
  IIntf1 = class
  public
    function Test; virtual; abstract;
  end;

  IIntf2 = interface
  public
    function Test;
  end;

Obviously, IIntf1 has many limitations, but this was the way to write COM interfaces in Delphi 2. The reason why the two constructs are comparable is the structure of the Virtual Method Table. You can think of an interface as a VMT definition. Delphi 3 introduced native interface support, making constructs like IIntf1 obsolete. It also added the biggest improvement to Object Pascal: multiple interface implementations.


type
  IIntf1 = interface ... end;
  IIntf2 = interface ... end;
  TImplementation = class(TAncestor, IIntf1, IIntf2) ... end;  // 1

Construct on line // 1 would be illegal if IIntf1 and IIntf2 were declared as astract classes.

Interface implementation

Now that we have decided to use interfaces in our application we have to overcome a few difficulties in declaring and implementing the application.

GUIDs

The most important difference between an abstract class and an interface is that an interface should have a GUID. GUID is a 128bit constant that Delphi uses to uniquely identify an interface. You may have encountered GUIDs in COM, and Delphi uses the same principles as COM to get access to an interface.


type
  ISimpleInterface = interface
    ['{BCDDF1B6-73CC-406C-912F-7148095F1F4C}'] // 1
  end;

GUID is shown on the line // 1. As you can see the GUID on line // 1 is not a 128bit integer, it is a string. Delphi compiler, however, recognizes the format of the string and converts it into GUID structure.


type
  TGUID = packed record
    D1: LongWord;
    D2: Word;
    D3: Word;
    D4: array[0..7] of Byte;
  end;

The same string to 128bit Integer also applies when defining a GUID constant:


type
  IID_ISimpleInterface: TGUID = '{BCDDF1B6-73CC-406C-912F-7148095F1F4C}';

Why are GUIDs important?

Why does an interface need to be uniquely identifiable? The answer is simple: because Delphi classes can implement multiple interfaces. When an application is running, there has to be a mechanism that will get pointer to an appropriate interface from an implementation. The only way to find out if an object implements an interface and to get a pointer to implementation of that interface is through GUIDs.

Interface core methods

Why does an interface need to be uniquely identifiable? The answer is simple: because Delphi classes can implement multiple interfaces. When an application is running, there has to be a mechanism that will get pointer to an appropriate interface from an implementation. The only way to find out if an object implements an interface and to get a pointer to implementation of that interface is through GUIDs.


function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;
function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;

Let us start with the simple ones; _AddRef and _Release. As you can probably guess from their names, _AddRef increases a reference counter by one and _Release decreases the counter. The behaviour of _Release depends on the class used in implementation. The pivotal method of interface management is QueryInterface. It takes GUID of an interface to get and returns a pointer to its implementation in Obj. For COM-compatibility, the method returns OLE HResult result values.

QueryInterface, as operator and assignment operator

How is QueryInterface related to as and assignment operators? The answer is simple: QueryInterface is used to get a pointer to an interface from the implementing class. Let us consider this code snippet.


type
  TCls = class(TInterfacedObject, IIntf1, IIntf2)
  protected
    // implementation of interfaces.
  end;

var
  C: TCls;
  I1: Intf1;
  I2: Intf2;
begin
  C := TCls.Create;
  I1 := C; // 1
  I2 := C; // 2

  // call methods of I1 and I2

  I1 := nil;
  I2 := nil;
end;

The code on lines //1 and //2 is compiled as call to _IntfCast procedure. This procedure calls QueryInterface, which returns a pointer to an interface in an implementation instance. It also releases previous value of destination.


procedure _IntfCast(var Dest: IInterface; const Source: IInterface; const IID: TGUID);
var
  Temp: IInterface;
begin
  if Source = nil then
    Dest := nil
  else
  begin
    Temp := nil;
    if Source.QueryInterface(IID, Temp) <> 0 then
      Error(reIntfCastError)
    else
      Dest := Temp;
  end;
end;

Exactly the same code will be produced if we use as construct:


  I1 := Impl as ISimpleInterface;

The as and := operators raise EIntfCastError if QueryInterface returns nil pointer. If you want to avoid using exception handling, use QueryInterface instead:


  Impl.QueryInterface(IAnotherInterface, A);

QueryInterface is one of the pivotal methods of interfaces in Delphi. The other two methods, _AddRef and _Release are used in controlling lifetime of an interface.

Interface creation and destruction

An interface is created by calling implementations constructor. Then the RTL copies a pointer to the interface from the created implementation instance to the interface variable. You may have already guessed that copying of an interface is firstly a simple pointer assignment and then increase of the reference counter. To increase the reference counter, RTL calls _AddRef method provided by the implementations base class. Let us have a look at Delphi pseudo-code for lines //1 to //3:


// line1
begin
  var C: TSimpleImplementation := TSimpleImplementacion.Create;
  if (C = nil) then Exit;
  var CVMT := C - VMTOffset;
  _IntfCopy(Intf, CVMT);
end;

The code on line 1 constructs an instance of the implementation class, get pointer to its VMT and then call _IntfCopy function. The most important piece of code is _IntfCopy.


procedure _IntfCopy(var Dest: IInterface; const Source: IInterface);
var
  OldDest: Pointer;
begin
  OldDest := Dest;                     // 1
  if Source <> nil then
    Source._AddRef;                    // 2
  Dest := Source;
  if OldDest <> nil then
    IInterface(OldDest)._Release;      // 3
end;

In most cases, the interface assignment means assigning non-nil pointer to existing interface to a nil pointer. If a destination interface is not nil -- that means it already references an existing interface -- it must be released after successful assignment of the new interface. This is why code on line // 1 copies old destination to a temporary variable. Then procedure then increases reference counter for source. It is important to increase the reference counter before the actual assignment. If the procedure did not do this, another thread might _Release an interface before _IntfCopy could finish executing. This would result in assigning a freed instance, which would result in an Access violation exception. Hence, line // 2 increases reference counter in the source interface before copying its value to the destination. Finally, if Dest was assigned to another interface, the interface is _Released. Once the interface is created, reference counter increased and destination is assigned with the newly created interface, we can safely call its methods.


// line 2:
begin
  var ImplVMT = Intf + VMTOffset;
  (ImplVMT + MethodOffset)(); // 2
end;

Bearing in mind that an interface is simply a VMT template a method call must be a call to a method that is looked up in implementations VMT. In our simple example, Test is the only virtual method if the implementation, MethodOffset is going to be 0, and VMTOffset is going to be $0c. The actual compiled code looks like this:


// set eax to the address of the first local variable 
mov    eax, [ebp - $04]
// edx := @eax
mov    edx, [eax]
// call to ((procedure of object)(edx + VMTOffset + MethodOffset))() 
call   dword ptr [edx + $0c]

The code actually calls Test method of the implementation class. The code is not too different from the call to a regular virtual method.
Line 3 in the original listing is as important as line 1, because it controls destruction of the interface. It is important to remember that when an interfaces reference counter reaches zero, the implementation class is destroyed. The danger is that the pointer to the implementation may remain the same, thus an if-not-nil test for the implementation does no guarantee that an implementation still exists.


// line 3:
begin
  _IntfClear(Intf);
end;

As you can tell, the most important code is hidden in _IntfClear method. This method must _Release the interface, and (if appropriate, free the implementation).


function _IntfClear(var Dest: IInterface): Pointer;
var
  P: Pointer;
begin
  Result := @Dest;
  if Dest <> nil then
  begin
    P := Pointer(Dest);
    Pointer(Dest) := nil;                // 1
    IInterface(P)._Release;              // 2
  end;
end;

The line //1 sets the destination pointer to nil, and line //2 releases the interface. _Release method must call implementations destructor when the reference counter reaches 0. Let us have a look at the compiled code of our testing example:


// load effective address of the first local variable
lea    eax, [ebp - $04]
// in _IntfClear:
// edx := @eax
mov    edx, [eax]
// if (edx = nil) then goto $0e (end);
test   edx, edx
jz     $0e
// eax^ := 0;
mov    [eax], 0
// push original value of eax
push   eax
// push Self parameter
push   edx
// eax := @edx
mov    eax, [edx]
// call _Release.
call   dword ptr [eax + $08]
// restore eax
pop    eax

The most important thing to realize is that after line //3 in the original listing, the interface is nil and the implementation is destroyed. The danger in this may be more obvious from this code snippet:


var
  Impl: TSimpleImplementation;
  Intf: ISimpleInterface;
begin
  Impl := TSimpleImplementation.Create;
  Intf := Impl;
  Intf.Test;
  Intf := nil;
  if (Impl <> nil) then Impl.Free; // 1
end;

The danger is on line // 1: after an interfaces reference counter has reached zero, implementations destructor is called; however, the value of the pointer to the instance of the implementation still remains not nil. Line // 1 will result in a call to a destructor of already destructed instance, which -- in most cases -- will cause an access violation.

Implications of automatic implementation destruction

What are the implications of the destruction mechanism? Perhaps the most important one is that if you want to keep your code easily maintainable you should never have variable for both implementation and interface. Another issue is that you have to do some extra coding if you want to use your implementation alive. Lets consider this situation: an method of a class returns an interface, but you do not want to instantiate an implementation class every time a call is made to the method.


type
  TCls = class
  public
    function GetInterface: ISimpleInterface;
  end;

It is easy to forget the destruction rules and write this code:


type
  TCls = class
  private
    FImpl: TSimpleImplementation;
  public
    constructor Create;
    destructor Destroy; override;
    function GetInterface: ISimpleInterface;
  end;

constructor TCls.Create;
begin
  inherited Create;
  FImpl := TSimpleImplementation.Create;
end;

destructor TCls.Destroy;
begin
  if (FImpl <> nil) then FImp.Free;
  inherited;
end;

function TCls.GetInterface: ISimpleInterface;
begin
  Result := FImpl;
end;

The first error is to use instance of implementation instead of interface. The problems (access violations, to be more specific) that you will encounter are the result of misunderstood implementation destruction.
The only instance when this class will function correctly is when GetInterface method is not called. If GetInterface is called once an error will occur in TClss destructor, if it is called more than once, an error will occur when you try to call ISimpleInterfaces Test method. The way out of this mess is to use the correct base implementation class: Delphis System unit provides three base implementation classes: TInterfacedObject, TAggregatedObject and TContainedObject. These three classes provide thread-safe implementation of interfaces.

TInterfacedObject

This is the simplest class for interface implementation. The requirement for thread-safe implementation has interesting implications. First of all, TInterfacedObject has to make sure that an interface is not released before it is completely constructed. This situation can easily happen in a multi-threaded application. Consider a case where thread constructs an instance of interface implementation class to get access to the interface. Before the instance is fully constructed, thread 2 releases previously acquired interface of the same type. This will trigger release mechanism and if the situation had not been thought of this could result in premature release of the constructed interface. The following code is taken directly from Delphis System.pas unit:


procedure TInterfacedObject.AfterConstruction;
begin
// Release the constructor's implicit refcount. Thread-safe increase is 
// achieved using Win API call to InterlockedDecrement in place of Dec
  InterlockedDecrement(FRefCount);
end;

procedure TInterfacedObject.BeforeDestruction;
begin
  if RefCount <> 0 then
    Error(reInvalidPtr);
end;

// Set an implicit refcount so that refcounting
// during construction won't destroy the object.
class function TInterfacedObject.NewInstance: TObject;
begin
  Result := inherited NewInstance;
  TInterfacedObject(Result).FRefCount := 1;
end;

function TInterfacedObject.QueryInterface(const IID: TGUID; out Obj): HResult;
begin
  if GetInterface(IID, Obj) then
    Result := 0
  else
    Result := E_NOINTERFACE;
end;

function TInterfacedObject._AddRef: Integer;
begin
  Result := InterlockedIncrement(FRefCount);
end;

function TInterfacedObject._Release: Integer;
begin
// _Release thread-safely decreases the reference count, and
  Result := InterlockedDecrement(FRefCount);
// if the reference count is 0, frees itself.
  if Result = 0 then
    Destroy;
end;

This is the most important code in interface support. It is important to understand the rules of interface and implementation creation and destruction. Lets now move on to other base implementation classes.

TContainedObject and TAggregatedObject

These two classes should be used when using implements syntax on interface property. Both classes keep a weak reference to the controller that implements the interfaces.


type
  TCls2 = class(T[Contained|Aggregated]Object, ISimpleInterface)
  private
    function GetSimple: ISimpleInterface;
  public
    property Simple: ISimpleInterface read GetSimple
      implements ISimpleInterface;
  end;

function TCls2.GetSimple: ISimpleInterface;
begin
  Result := Controller as ISimpleInterface;
end;

var
  C: TCls2;
begin
  C := TCls2.Create(TSimpleImplementation.Create); // 1
  // Call interface methods
  C.Free;                                          // 2
end;

Lines // 1 and // 2 show differences between TInterfacedObject an TContainedObject. Firstly, because of implements clause you do not have to implement methods of ISimpleInterface in TCls2. Instead, TCls2 must provide a property and a selector method to get a pointer to ISimpleInterface. The implementation of the selector method for the Simple property gets interface from the controller. An instance of controller is passed as a parameter of the constructor method. Perhaps the most important difference between TContainedObject and TInterfacedObject is the destruction mechanism. You must manually free an instance of TContainedObject. There is no automatic destructor calling, however, the automatic destructor calls for the container class are still in place.

TAggregatedObject

TAggregatedObject and TContainedObject are suitable base classes for interfaced objects intended to be aggregated or contained in an outer controlling object. When using the "implements" syntax on an interface property in an outer object class declaration, use these types to implement the inner object. Interfaces implemented by aggregated objects on behalf of the controller should not be distinguishable from other interfaces provided by the controller. Aggregated objects must not maintain their own reference count - they must have the same lifetime as their controller. To achieve this, aggregated objects reflect the reference count methods to the controller. TAggregatedObject simply reflects QueryInterface calls to its controller. From such an aggregated object, one can obtain any interface that the controller supports, and only interfaces that the controller supports. This is useful for implementing a controller class that uses one or more internal objects to implement the interfaces declared on the controller class. Aggregation promotes implementation sharing across the object hierarchy. TAggregatedObject is what most aggregate objects should inherit from, especially when used in conjunction with the "implements" syntax.
Let TCls2 be descendant of TAggregatedObject: in that case we can write this code:


var
  C: TCls2;
begin
  C := TCls2.Create(TSimpleImplementation.Create);
  C.Simple.Test;                          // 1
  (C as ISimpleInterface).Test;           // 2
  C.Free;
end;

The line // 1 is legal; it simply gets a pointer to ISimpleInterface using GetSimple selector method, which gets the appropriate interface from the controller. Line // 2 is not legal, because TAggregatedObject can use only the controller to return the appropriate interface.

TContainedObject

The purpose of TContainedObject is to isolate QueryInterface method on the aggregate from the controller. Classes derived from this class will only return interfaces that the class itself implements, not the controller. This class should be used for implementing interfaces that have the same lifetime as the controller. This design pattern is known as forced encapsulation. Let TCls2 be descendant of TContainedObject:


var
  C: TCls2;
begin
  C := TCls2.Create(TSimpleImplementation.Create);
  C.Simple.Test;                          // 1
  (C as ISimpleInterface).Test;           // 2
  C.Free;
end;

Unlike the previous case, we can now use both statements C.Simple.Test as well as (C as ISimpleInterface).Test.

Conclusion

Interfaces are very powerful tool for writing flexible and extensible applications. Just like every powerful tool, they can be very dangerous to use if you do not know what you want to write and how the compiler is going to interpret the code. In the next article I will focus on .NET interfaces and Delphi .NET compiler issues.


Server Response from: ETNASC04