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>