Caves Travel Diving Graphics Mizar Texts Cuisine Lemkov Contact Map RSS Polski
Trybiks' Dive Texts Delphi Delphi interfaces... again YAC Software
  Back

List

Charsets

Charts

DBExpress

Delphi

HTML

Intraweb

MSTest

PHP

Programming

R

Rhino Mocks

Software

Testing

UI Testing

VB.NET

VCL

WPF

Delphi interfaces... again
After being away from Delphi for a while (and quite happily programming in VB.NET), I returned to one of my older projects (YAC Text Recoder) to implement filters (basically, simple expressions using comparison and arithmetic operators on numeric and text values, that are then used to select a subset of the data - think of WHERE clauses in SQL).

To check that the expression is being parsed correctly, I wanted to add a method that displays the parse tree one element per line, with indenting showing the depth of the tree at that point, plus any error or warning messages and positions (so that, in unit tests, I can compare this generated output against reference output for large numbers of manually and pseudo-randomly generated expressions).

Anyway, just having come back from VB.NET (where programming with interfaces is really second nature) and forgetting that Delphi has basically botched the whole approach to interfaces, I got stung, yet again, by the idiotically conflicting memory management between standard objects and interface based objects (or, more precisely, by the absolutely stupid decision to default to reference counted memory management for all interface based objects).

Here, TNode is the ancestor class for parse tree nodes. And an extract of the original code that implements the visitor pattern to iterate over all nodes of the expression tree to build the resulting parse tree string:
  INodeVisitor = interface;
  
  TNode = class
    procedure Accept(AVisitor: INodeVisitor); virtual; abstract;
  end;
  
  TNodeValueString = class(TNode)
    FValue: String;
    procedure Accept(AVisitor: INodeVisitor); override;
  end;
  
  INodeVisitor = interface
    procedure Visit(ANode: TNodeValueString); overload;
    . . .
  end;
  
  TToStringVisitor = class(TInterfacedObject, INodeVisitor)
    FText: String;
    procedure Visit(ANode: TNodeValueNumeric); overload;
    . . .
    function ToString(ARoot: TNode): string;
  end;
A pretty standard set up of the pattern, I should think. With the obvious implementations (I dropped the indenting part as it's not really relevant here):
  { TNodeValueString }
  
  procedure TNodeValueString.Accept(AVisitor: INodeVisitor);
  begin
    AVisitor.Visit(self);
  end;
  
  { TToStringVisitor }
  
  procedure TToStringVisitor.Visit(ANode: TNodeValueString);
  begin
    FText := FText + '"' + ANode.FValue + '"';
  end;
  
  function TToStringVisitor.ToString(ARoot: TNode): string;
  begin
    FText := '';
    ARoot.Accept(self);
    Result := FText;
  end;
To my surprise (and please remember, for the last three years I've been having a lot of fun programming with normally behaving interfaces) FText was correct up to the last instruction:
  Result := FText;
But before that assignment, FText was, all of a sudden, being reset to an empty string...

WTF!?!

Fortunately, I do remember some of the problems I had with using interfaces in Delphi. Here, it turned out that the absolutely idiotic implementation of interfaces sets self's reference count to 0 at the start of the method, increments it on the call to ARoot.Accept(self) and decrements it right after the call. So, then, happily decides to destroy the object (self) before reaching the end of the method (actually, it shouldn't destroy the object even then)...

And this isn't even a problem between mixing object and interface references - the well known issue in Delphi...

Multiple solutions are possible here, of course, but first, you have to understand the problem. But why would you want to waste time on such stupidity when there are much more interesting things to do?

For instance, this looks very intuitive, no?
  function TToStringVisitor.ToString(ARoot: TNode): string;
  var
    LSelf: INodeVisitor;
  begin
    FText := '';
    LSelf := self;
    ARoot.Accept(LSelf);
    Result := FText;
  end;
Nice, eh? Though I am kind of joking here - another option is to turn off reference counting for the TToStringVisitor class entirely:
  TToStringVisitor = class(TInterfacedObject, INodeVisitor)
    . . .
    function _AddRef: Integer; stdcall;
    function _Release: Integer; stdcall;
  end;
  
  . . .
  
  function TToStringVisitor._AddRef: Integer;
  begin
    Result := -1;
  end;
  
  function TToStringVisitor._Release: Integer;
  begin
    Result := -1;
  end;
But then, why default to reference counted memory management in the first place? Why not just have interfaces as just contracts that have nothing to do with object deallocations? Why should I manually turn off reference counting just because I'm calling a method and passing self as the parameter?

Man, at every single satisfaction survey I had asked Borladero to add non-reference counted interfaces to Delphi. This is the one most important reason why I'm not so keen on coming back to the language (even considering recent developments)...

Anyway, enough of this rant... Hope others don't stumble too often on similar problems - enough time wasted already.

Top

Comments
Alas!
No comments yet...

Top

Add a comment (fields with an asterisk are required)
Name / nick *
Mail (will remain hidden) *
Your website
Comment (no tags) *
Enter the text displayed below *
 

Top

Tags

Delphi


Related pages

Checking "Dangling" Event Handlers in Delphi Forms

Drag-n-drop files onto the application window

Intraweb and MaxConnections

A Case for FreeAndNIL

Intraweb as an Apache DSO module

"Device not supported" in Intraweb

Automated GUI Testing

Rounding and precision on the 8087 FPU

SessionTimeout in Intraweb

Using TChart with Intraweb

Unknown driver: MySQL

TIdMessage's CharSet

Software Guarantees

Automated Testing of Window Forms

TChart - Missing Labels in Axes

Memory Leaks and Connection Explosions in DBExpress

Controlling Conditional Defines and Compilation Switches

Detecting Memory Leaks with DUnit

last_insert_id() and DBExpress

Registering Extensions

DBExpress and Thread Safety

Forms as Frames

Checking Dangling Pointers vs. the New Memory Manager

Accessing Protected Members

Objects, interfaces, and memory management in Delphi