This page shows the source for this entry, with WebCore formatting language tags and attributes highlighted.
Title
Interfaces in Delphi - Part II
Description
<n>This article was originally published on the <a href="http://blogs.encodo.ch/news/view_article.php?id=6"><b>Encodo Blogs</b></a>. Browse on over to see more!</n>
<hr>
<n>This is the second of a two-part article on interfaces. <a href="{app}view_article.php?id=1408">part one</a> is available here.</n>
In part one, we saw how to use non-reference-counted interfaces to prevent objects from magically disappearing when using interfaces in common <c>try...finally...FreeAndNil()</c> cases. Though this brings the interface problem under control, there is further danger.
<h>Dangling Interfaces</h>
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 <c>_Release</c> 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:
<code>
IRow = <b>interface</b>
<b>function</b> ValueAtIndex( aIndex: integer ): variant;
<b>end</b>;
ITable = <b>interface</b>
<b>procedure</b> GoToFirst;
<b>procedure</b> GoToNext;
<b>function</b> IsPastEnd: boolean;
<b>function</b> CurrentRow: IRow;
<b>end</b>;
</code>
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.<fn>
<code>
TRow = <b>class</b>( TNonReferenceCountedObject, IRow )
<b>protected</b>
Values: array of variant;
<b>public</b>
<b>function</b> ValueAtIndex( aIndex: integer ): variant;
<b>end</b>;
TTable = <b>class</b>( TNonReferenceCountedObject, ITable )
<b>protected</b>
Index: integer;
Rows: TObjectList;
<b>public</b>
<b>procedure</b> GoToFirst;
<b>procedure</b> GoToNext;
<b>function</b> IsPastEnd: boolean;
<b>function</b> CurrentRow: IRow;
<b>end</b>;
</code>
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.
<code>
<b>procedure</b> DoSomething;
rowSet:= CreateRowSet;
<b>try</b>
rowSet.GoToFirst;
<b>while not</b> rowSet.IsPastEnd <b>do begin</b>
val1:= rowSet.CurrentRow.ValueAtIndex( 0 ); <span style="color: green">// (1)</span>
val2:= rowSet.CurrentRow.ValueAtIndex( 1 ); <span style="color: green">// (2)</span>
rowSet.GoToNext;
<b>end</b>;
<b>finally</b>
FreeAndNil( rowSet );
<b>end</b>;
<b>end</b>; <span style="color: green">// (1) CRASH!</span>
</code>
The code looks harmless enough; it is not obvious at all that <c>CurrentRow</c> returns an interface. The two references to an <c>IRow</c> 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 <i>explicit</i> references to interfaces. <c>DoSomething</c> is rewritten below:
<code>
<b>procedure</b> DoSomething;
rowSet:= CreateRowSet;
<b>try</b>
rowSet.GoToFirst;
<b>while not</b> rowSet.IsPastEnd <b>do begin</b>
row:= rowSet.CurrentRow; <span style="color: green">// (1)</span>
<b>try</b>
val1:= row.ValueAtIndex( 0 );
val2:= row.ValueAtIndex( 1 );
<b>finally</b>
row:= nil; <span style="color: green">// (0)</span>
<b>end</b>;
rowSet.GoToNext;
<b>end</b>;
<b>finally</b>
FreeAndNil( rowSet );
<b>end</b>;
<b>end</b>;
</code>
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.
<hr>
<ft>The class <c>TNonReferenceCountedObject</c> is assumed to an implementation of the <c>IUnknown</c> methods to prevent reference counting, as illustrated earlier in the article.</ft>