Interfaces in Delphi − Part II
Published by marco on
This article was originally published on the Encodo Blogs. Browse on over to see more!
This is the second of a two-part article on interfaces. part one is available here.
In part one, we saw how to use non-reference-counted interfaces to prevent objects from magically disappearing when using interfaces in common try…finally…FreeAndNil()
cases. Though this brings the interface problem under control, there is further danger.
Dangling Interfaces
A dangling interface is another problem that arises even when using non-reference-counted interfaces. In this case, the crash happens because an object has been freed, but there are still (often implicit) references to it in interfaces. Anytime a reference to an interface is removed—set to nil—the function _Release
is called on the object behind the interface. If this object has already been freed, there is a rather nasty crash deep in library code.
A nice use of interfaces is as a return type, so that objects from various inheritance hierarchies can be used from common code. To better illustrate this problem, consider the two interfaces below:
IRow = interface
function ValueAtIndex( aIndex: integer ): variant;
end;
ITable = interface
procedure GoToFirst;
procedure GoToNext;
function IsPastEnd: boolean;
function CurrentRow: IRow;
end;
The two interfaces describe a way of generically iterating a table and retrieving values for each column in a row. Now, take a look at a concrete implementation for the table iterator.[1]
TRow = class( TNonReferenceCountedObject, IRow )
protected
Values: array of variant;
public
function ValueAtIndex( aIndex: integer ): variant;
end;
TTable = class( TNonReferenceCountedObject, ITable )
protected
Index: integer;
Rows: TObjectList;
public
procedure GoToFirst;
procedure GoToNext;
function IsPastEnd: boolean;
function CurrentRow: IRow;
end;
The implementation is not shown, but assume that each row allocates a buffer for its values and that the table allocates and frees its rows when destroyed. Assume further the naive implementation for the remaining methods—they are not salient to this discussion.
The example that follows iterates this table in a seemingly innocuous way, but one that causes a crash … sometimes. That’s what makes this class of problem even more difficult—it’s unpredictability. The lines of code that change a row’s reference count are followed by the reference count. This helps see what is happening behind the scenes and explains the ensuing crash.
procedure DoSomething;
rowSet:= CreateRowSet;
try
rowSet.GoToFirst;
while not rowSet.IsPastEnd do begin
val1:= rowSet.CurrentRow.ValueAtIndex( 0 ); // (1)
val2:= rowSet.CurrentRow.ValueAtIndex( 1 ); // (2)
rowSet.GoToNext;
end;
finally
FreeAndNil( rowSet );
end;
end; // (1) CRASH!
The code looks harmless enough; it is not obvious at all that CurrentRow
returns an interface. The two references to an IRow
are left “dangling” in the sense that the code has no references to them. But they exist nonetheless and will be cleared when exiting the function scope—after the objects to which they refer have been freed.
The way to fix this—and to work completely safely with interfaces—is to use only explicit references to interfaces. DoSomething
is rewritten below:
procedure DoSomething;
rowSet:= CreateRowSet;
try
rowSet.GoToFirst;
while not rowSet.IsPastEnd do begin
row:= rowSet.CurrentRow; // (1)
try
val1:= row.ValueAtIndex( 0 );
val2:= row.ValueAtIndex( 1 );
finally
row:= nil; // (0)
end;
rowSet.GoToNext;
end;
finally
FreeAndNil( rowSet );
end;
end;
Interfaces are very useful, but Delphi Pascal’s implementation leaves a lot to be desired. It is possible to write completely safe code for them, but it takes a lot of practice and care. And, as seen in the examples above, interfaces can be easily hidden in with and mixed with objects, so that crashes remain a mystery if the presence of a rogue interface is not detected.
TNonReferenceCountedObject
is assumed to an implementation of the IUnknown
methods to prevent reference counting, as illustrated earlier in the article.↩