|<<>>|236 of 274 Show listMobile Mode

Interfaces in Delphi − Part I

Published by marco on

This article was originally published on the Encodo Blogs. Browse on over to see more!


This is the first of a two-part article on interfaces. part two is available here.

Delphi Pascal, like many other languages that refuse to implement multiple inheritance, regardless of how appropriate the solution often is, added interfaces to the mix several years ago. However, Borland failed, at the same time, to add garbage collection, so they opted instead for a COM-like reference-counting model, which automatically frees an interface when there are no more references to it. Any references to the object behind the interface are on their own.

The Perils of Reference Couting

This is not just a theoretical problem; it’s extraordinarily easy to provoke this situation. The definitions below show a simple interface and a class that uses that interface:

ISomeInterface = interface
  procedure DoSomethingGreat;
end;

TSomeObject = class( TInterfacedObject, ISomeInterface )
  procedure DoSomethingGreat;
end;

Now imagine that an application has a library of functions that accept an interface of type ISomeInterface (like DoSomething in the example below). Given the definition above, if it has an instance of TSomeObject, it can magically profit from this library, even though the library doesn’t know anything about any of the objects in its inheritance chain. ProcessObjects below uses this library function in the simplest and most direct way possible.

procedure DoSomething( aObj: ISomeInterface );
begin
  aObj.DoSomethingGreat;
end;

procedure ProcessObjects;
var
  obj: TSomeObject;
begin
  obj:= TSomeObject.Create;
  try
    DoSomething( obj );
  finally
    obj.Free;
  end;
end;

At first glance, there is nothing wrong with this code. However, executing it results in an access violation (crash). Why? The short answer is that references to the object (as opposed to references to the interface) do not increase the reference count on the object. In order to better illustrate this point, let’s unroll the DoSomething function into ProcessObjects to make the interface assignment explicit. This is shown below, with the reference count of obj shown before each line:

procedure ProcessObjects;
var
  obj: TSomeObject;
  aObj: ISomeInterface;
begin
  obj:= TSomeObject.Create; // (0)
  try
    aObj:= obj; // (1)
    aObj.DoSomethingGreat; 
    aObj:= nil;  // (0) obj is freed automatically!
  finally
    obj.Free;
  end;
end;

With reference-counted objects, as soon as the reference count reaches 0, it is automatically freed. Programming with this kind of pattern is, at best, a touchy affair, so most experienced Delphi programmers have learned one of two things about interfaces:

  1. Don’t touch them, they’re poison
  2. Use only non-reference-counted versions

A non-reference-counted interface implementation overrides the _AddRef and _Release methods to always return 1, so that the object behind the interface is never automatically released. This avoids a lot of crashes, but not all of them. Part two will show how to avoid the dreaded dangling interface.

Continue to part two.