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:
- Don’t touch them, they’re poison
- 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.