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

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.


[1] The class TNonReferenceCountedObject is assumed to an implementation of the IUnknown methods to prevent reference counting, as illustrated earlier in the article.