View Issue Details

IDProjectCategoryView StatusLast Update
0008951FPCCompilerpublic2012-01-04 15:53
ReporterMario R. Carro Assigned ToIvo Steinmann  
PrioritynormalSeveritymajorReproducibilityalways
Status assignedResolutionopen 
Target Version2.4.0 
Summary0008951: FPC doesn't allow implements properties be of class types
DescriptionDelphi allows implements properties to be of class types derived from TAggregatedObject, but FPC requires them to be of interface types.

For example this line (from the JclSchedule.pas unit of the JCL):

    property DailyFreq: TDailyFreq read FDailyFreq implements IJclScheduleDayFrequency;

fails to compile with the message:

JclSchedule.pas(1327,87) Error: Implements property must have interface type
Tagscom, jcl
Fixed in Revision
FPCOldBugId0
FPCTarget-
Attached Files

Relationships

parent of 0016531 new Partial interface delegation to class 
related to 0004842 assignedIvo Steinmann implements keyword in properties 
related to 0012778 closedIvo Steinmann GetInterface returns wrong results when using delegated interfaces 
related to 0016365 resolvedIvo Steinmann Interface delegation by class type property 
Not all the children of this issue are yet resolved or closed.

Activities

Jonas Maebe

2007-05-26 16:09

manager   ~0012814

Please provide a compilable sample program.

2007-06-05 00:54

 

test.pas (440 bytes)   
program test;

type
  IIntf = interface(IUnknown)
    ['{6CF37F0D-56F4-4AE6-BBCA-7B9DFE60F50D}']
    function F: Cardinal;
  end;

  TImplClass = class(TAggregatedObject)
  public
    function F: Cardinal;
  end;

  TClass = class(TInterfacedObject, IIntf)
  protected
    FImplClass: TImplClass;
    property Impl: TImplClass read FImplClass implements IIntf;
  end;

function TImplClass.F: Cardinal;
begin
  Result := 0;
end;

begin
end.
test.pas (440 bytes)   

Mario R. Carro

2007-06-05 00:57

reporter   ~0012961

Hi. The program I uploaded compiles without errors with D7, but fails with FPC r7578:

Free Pascal Compiler version 2.3.1 [2007/06/04] for i386
Copyright (c) 1993-2007 by Florian Klaempfl
Target OS: Linux for i386
Compiling test.pas
test.pas(17,63) Error: Implements property must have interface type
test.pas(18,6) Error: No matching implementation for interface method "IIntf.F:DWord" found
test.pas(27) Fatal: There were 2 errors compiling module, stopping
Fatal: Compilation aborted
Error: /usr/local/bin/ppc386 returned an error exitcode (normal if you did not specify a source file to be compiled)

Marco van de Voort

2007-10-28 14:00

manager   ~0015797

Also ran into this. Lazarus is preparing to support JVCL that depends on JCL, so it would be nice if it was fixed.

Marco van de Voort

2008-02-03 21:41

manager   ~0017592

Found something:


http://www.geocities.com/svi37/cyber/delphi9/iimplementation.html

2009-04-25 12:15

 

implements.html (69,497 bytes)   
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
	"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>Delphi #9</title>
<meta name="KEYWORD"   content="Delphi Interface">
<meta http-equiv="Content-Type" content="text/html; charset=iso8859-1">
<link rel="stylesheet" type="text/css" href="d.css">
<link rel="alternate stylesheet" title="ErgoStyle" href="e.css">
<link rel="Top" href="../../index.html">
<link rel="Up" href="../index.html">
<link rel="Contents" href="index.html#toc">
<link rel="First" href="index.html">
<link rel="Last" href="istar.html">
<link rel="Prev" href="imisunderstood.html">
<link rel="Next" href="ipragmatic.html">
</head>

<body>
<!-- following code added by server. PLEASE REMOVE -->
<!-- preceding code added by server. PLEASE REMOVE -->
<h1><a name="top"></a>IImplementation</h1>
<hr>
<div class=Fuq><h3>A Piece of &quot;Emit&quot; Art ...</h3></div>
<p>Sometimes asking for an&nbsp; explanation on what interfaces are you get a
laconic answer that sounds like this:
<p>&nbsp;&nbsp;&nbsp; an interface is a sort of &quot;contract&quot;
<p>&nbsp;&nbsp;&nbsp; an interface value is a &quot;pointer&quot;
<p>&nbsp;&nbsp;&nbsp; an interface type is a GUID (for example: {00000000-0000-0000-C000-000000000046}
)
<p>&nbsp;&nbsp;&nbsp; an interface implementation is a VMT (vtable)
<p>I feel this not very satisfactory, but now i have understood why&nbsp; the
answers are so concise ...&nbsp;
<p>A in depth description of interface implementation would take one long
chapter in Developer Guide !&nbsp;<p>I'm surely not able to write this kind of
things, but anyway let's try ...<p>mm.. where to start ?<p>maybe the better
place is before TObject.Create ...<p>When a new Delphi object is requested by
calling a class constructor, the first thing to do is to allocate a piece of
memory for instance data.&nbsp;<p> This task is performed by TObject.NewInstance (class)
method, that calls GetMem with TObject.InstanceSize.<p>So what should be the
size of an object ?
<div class=Code>
<tt>
<pre class="Code">
type

   IAInterface = interface(IUnknown)
   ['{713252E1-4636-11D5-B572-00AA00ACFD08}']
      procedure AMethod;
   end;

   IBInterface = interface(IAInterface)
   ['{713252E2-4636-11D5-B572-00AA00ACFD08}']
      procedure BMethod;
   end;

   ICInterface = interface(IAInterface)
   ['{713252E3-4636-11D5-B572-00AA00ACFD08}']
      procedure BMethod;
      procedure CMethod;
   end;

   IZInterface = interface(IUnknown)
   ['{713252E4-4636-11D5-B572-00AA00ACFD08}']
   end;

type
   TFoo = class(TObject,IBInterface,IAInterface,IZInterface)
   private
      FDummy: Char;
   protected
    function QueryInterface(const IID: TGUID; out Obj): HResult; virtual; stdcall;
    function _AddRef: Integer; stdcall;
    function _Release: Integer; stdcall;
   private
      procedure BMethod;
   public
      procedure AMethod;
      property  Dummy: Char read FDummy;
   end;

   TBar = class(TFoo,ICInterface,IBInterface)
   private
      procedure IBInterface.AMethod = ADifferentMethod;
      procedure IBInterface.BMethod = BSecondMethod;
      procedure ICInterface.BMethod = BMethod;
      procedure ADifferentMethod;
      procedure BSecondMethod;
   public
      procedure CMethod;
   end;

</pre>
</tt>
</div>


<p>&nbsp;
<p>The size of &quot;TFoo&quot; is 16 bytes and that of &quot;TBar&quot; is 24, why ?
<p>First of all consider instance data as a non &quot;packed&quot; record,
aligned to 32 bit boundary per faster memory access (a single char can takes 4
byte).<p>Then consider that (at offset 0) every object contains a pointer to the
class VMT (4 bytes)<p>The remaning space is for interface &quot;hidden&quot;
pointers, and the memory layout could be something like this<p>
<div class=Code>
<tt>
<pre class="Code">
type
   _TFooInstance = {not packed} record  //SizeOf(_TFooInstance) = 16
      FooClassVMT: Pointer;             //(offset:0)  Pointer to TFoo Class VMT
      FDummy: Char;                     //(offset:4)  fields are 32 bit aligned
      IZInterfaceVMT: Pointer;          //(offset:8)  Pointer to TFoo.IZVTBL
      IBInterfaceVMT: Pointer;          //(offset:12) Pointer to TFoo.IBVTBL 
                                        // (can point to TFoo.IAVTBL too)
   end;   
    
   _TBarInstance = {not packed} record  //SizeOf(_TBarInstance) = 24
      BarClassVMT: Pointer;             //(offset:0)  Pointer to TBar Class VMT
      FDummy: Char;                     //(offset:4)  fields are 32 bit aligned
      IZInterfaceVMT: Pointer;          //(offset:8)  Pointer to TFoo.IZVTBL
     _IBInterfaceVMT: Pointer;          //(offset:12) Hidden Pointer to TFoo.IBVTBL
      IBInterfaceVMT: Pointer;          //(offset:16) Pointer to TBar.IBVTBL 
                                        // (can point to TBar.IAVTBL too)
      ICInterfaceVMT: Pointer;          //(offset:20) Pointer to TFoo.ICVTBL
   end;   
</pre>
</tt>
</div>

<p>but where this pointers points to and when are they filled ?<p>the class
pointer points to the class (quite obvious), precisely to the start of the class
Virtual Method Table used to implement polymorphism.<p>at negative fixed
offset&nbsp; respect to the VMT, there's a lot of class type data, and for
interfaces three fields are important:<p>&nbsp;
<ol>
  <li>class self pointer (at offset vmtSelfPtr = -76)&nbsp; that points to the
    start of the VMT of a class<br>
  </li>
  <li>parent class pointer (at offset vmtParent = -36) that points to the class
    self pointer of the parent class<br>
 (single inheritance = just one pointer)<br>
  </li>
  <li>interface table pointer (at offset vmtIntfTable = -72) that points to an
    array TInterfaceEntry record&nbsp;<br>
 (see System.pas)</li>
</ol>
<p>every entry in an interface record contains 4 fields:&nbsp;<br>
<ol>
  <li> a GUID (for example: {00000000-0000-0000-C000-000000000046}
)<br>
  </li>
  <li> a
Pointer to an Interface VTable (described later)<br>
  </li>
  <li>an Integer Offset<br>
  </li>
  <li> a strange thing called
ImplementationGetter</li>
</ol>
<p>Ignoring ImplementationGetter for now, for the classes listed above we should have:
<p>&nbsp;

<div class=Code>
<tt>
<pre class="Code Tiny">


                         +-----&gt;  TObjectVMT
                         |
                         +--- -76 TObject.SelfPtr
                                     ^
                                     |
                         +-----------+
                         |
                         |    -72  TFoo.IntfTable -&gt; {713252E4-4636-11D5-B572-00AA00ACFD08},^TFoo.IZVTBL,8,0
                         +--- -36  TFoo.Parent       {713252E1-4636-11D5-B572-00AA00ACFD08},^TFoo.IAVTBL,12,0
                                                     {713252E2-4636-11D5-B572-00AA00ACFD08},^TFoo.IBVTBL,12,0
AFoo --&gt; ^FooClassVMT ---+-------&gt; TFoo.VMT 
                         |       
                         +--- -76 TFoo.SelfPtr
                                     ^
                                     |
                         +-----------+
                         |
                         |    -72  TBar.IntfTable -&gt; {713252E2-4636-11D5-B572-00AA00ACFD08},^TBar.IBVTBL,16,0
                         +--- -36  TBar.Parent       {713252E3-4636-11D5-B572-00AA00ACFD08},^TBar.ICVTBL,20,0
                              
ABar --&gt; ^BarClassVMT -----------&gt; TBar.VMT 
                     


</pre>
</tt>
</div>

<p>Returning to the constructor of our object, for a new TBar, the first thing
to to is to allocate (with GetMem) 24 bytes of memory in NewInstance. This is a
strange function because does not return, but instead jump into InitInstance
class function.<p>It's here where the hidden pointers gets filled in this way:<p>&nbsp;
<ol>
  <li>
the allocated memory is cleared (set to zero)<br>
  </li>
  <li> the interface table of the class is scan, and
for every entry with a not nil VTable address,<br>
 the vtable address
is moved in the hidden pointer<br>
    <br>
 but where is the hidden
pointer located ?<br>
 starting from the address of the new
chunk of memory,&nbsp;<br>
 you have to offset the number of bytes you
find in the interface entry table&nbsp;<br>
  </li>
  <li> the parent class is located via Parent pointer<br>
    and the
previous operation is repeated for all the interfaces of the parent,<br>
 until you
reach TObject class that has no parent (nil)<br>
  </li>
  <li> the address of the allocated
memory (the object Self) is returned as new object to the caller<br>
  </li>
</ol>
<p>the effect of TObject.NewInstance (+TObject.InitInstance) is similar to this</p>
<p>(but it works with an interface loop inside a class
loop...)&nbsp;&nbsp;&nbsp; </p>
<div class=Code>
<tt>
<pre class="Code"> 
type PPointer = ^Pointer;
var o: Pointer;
begin
   GetMem(o,SizeOf(_TBarInstance));
   // o^ := 0 clear memory (see New,Initialize ...)
   
   //o^.BarClassVMT := @TBar.VMT
   
   PPointer(o)^ := @TBar.VMT                                               


   // o^.IBInterfaceVMT := @TBar.IBVTBL   
   
   PPointer(Interger(o) + (TBar.IntfTable^)[1].IOffSet)^ := (TBar.IntfTable^)[1].VTable   
   
   // o^.ICInterfaceVMT := @TBar.ICVTBL   
   
   PPointer(Interger(o) + (TBar.IntfTable^)[2].IOffSet)^ := (TBar.IntfTable^)[2].VTable   
   
   // o^._IZInterfaceVMT := @TFoo.IZVTBL
   
   PPointer(Interger(o) + (TFoo.IntfTable^)[1].IOffSet)^ := (TFoo.IntfTable^)[1].VTable   

   // o^.IBInterfaceVMT := @TFoo.IBVTBL    ( = @TFoo.IAVTBL )
   
   PPointer(Interger(o) + (TFoo.IntfTable^)[2].IOffSet)^ := (TFoo.IntfTable^)[2].VTable   
   
   result := TBar(o);
   
end;   
</pre>
</tt>
</div>

<p>
<br>
<br>
<br>
Now we have a TBar object, so we can assign it to an interface variable in this way ...
<br>

<div class=Code>
<tt>
<pre class="Code Tiny"> 
var
{
 _ABar: ^_TBarInstance;
 _AFoo: ^_TFooInstance;
}
  ABar: TBar;
  AFoo: TFoo;
  AIAInterface: IAInterface;
  AIBInterface: IBInterface;
  AICInterface: ICInterface;
  AIZInterface: IZInterface;
  AIUnknown: IUnknown;
begin

   ABar := TBar.Create;     //a TBar is a kind of TFoo ...
   AFoo := ABar;            //AFoo := TFoo(^_TFooInstance(^_TBarInstance(ABar)))

{
  _ABar := ^_TBarInstance(ABar);    //object are (de)-references ...
  _AFoo := ^_TFooInstance(AFoo);
}

   AICInterface := ABar;    //assign @(_ABar^.ICInterfaceVMT)  (^^TBar.ICInterfaceVTable)
   AIBInterface := ABar;    //assign @(_ABar^.IBInterfaceVMT)  (^^TBar.IBInterfaceVTable)
   AIAInterface := ABar;    //assign @(_ABar^._IBInterfaceVMT) (^^TFoo.IBInterfaceVTable)
   AIZInterface := ABar;    //assign @(_ABar^.IZInterfaceVMT)  (^^TFoo.IZInterfaceVTable)
// AIUnknown    := ABar;    //illegal: nothing to assign from a _TBarInstance

// AICInterface := AFoo;    //illegal: no ICInterfaceVMT field in a _TFooInstance
   AIBInterface := AFoo;    //assign @(_AFoo^._IBInterfaceVMT) (^^TFoo.IBInterfaceVTable)
   AIAInterface := AFoo;    //assign @(_AFoo^._IBInterfaceVMT) (^^TFoo.IBInterfaceVTable)
   AIZInterface := AFoo;    //assign @(_AFoo^.IZInterfaceVMT)  (^^TFoo.IZInterfaceVTable)
// AIUnknown    := AFoo;    //illegal: nothing to assign from a _TFooInstance

   AIUnknown    := AICInterface;   //legal: cast ^^TBar.IBInterfaceVTable to a ^^IUnknownVTable

   AIAInterface := ABar;
   AIAInterface.AMethod;         //calls TFoo.AMethod;

   AIBInterface := ABar;
   AIAInterface := AIBInterface; //legal: cast ^^TBar.IBInterfaceVTable to a ^^IAInterfaceVTable
   AIAInterface.AMethod;         //calls TBar.ADifferentMethod;

   AIBInterface := ABar;
   AIBInterface.BMethod;         //calls TBar.BSecondMethod;

   AIBInterface := AFoo;
   AIBInterface.BMethod;         //calls TFoo.BMethod;

   ABar.Free;

end;
</pre>
</tt>
</div>

<p>
<br>
<br>
<br>
So an Interface variable store a kind of pointer, but a question raises at this point ...

<p>
if this pointer contains a different value from object Self pointer, how can the implementation obtain the Self back ?

<p>
and what is an Interface VTable ?

<p>
a (messy) picture worths a thousand (messy) words ...
<br>

 

<div class=Code>
<tt>
<pre class="Code Tiny"> 
                                                     
ABar --------------&gt; _TBarInstance
                :           ClassPtr
                :             ...
            offset=20         ...
                :             ...
                :             ...
AICinterface  ------&gt;  ICInterfaceVMT  ------+
                                             |   
                                             +---&gt; TBar.ICInterfaceVTable
 +------------------------------------------------------------ @TBar.ICInterfaceQueryInterfaceThunk
 | +---------------------------------------------------------- @TBar.ICInterface_AddRefThunk
 | | +-------------------------------------------------------- @TBar.ICInterface_ReleaseThunk
 | | | +------------------------------------------------------ @TBar.ICInterfaceAMethodThunk
 | | | | +---------------------------------------------------- @TBar.ICInterfaceBMethodThunk
 | | | | | +-------------------------------------------------- @TBar.ICInterfaceCMethodThunk
 | | | | | |
 | | | | | |         
 | | | | | |  TBar.ICInterfaceThunkTable
 | | | | | |    
 | | | | | +--&gt;  TBar.ICInterfaceCMethodThunk
 | | | | |         (first parameter = AICInterface Ptr)                                                CODE of TBar
 | | | | |         Self := first parm - 20 //offset                                                       :
 | | | | |         jmp  ------------------------------------------&gt;  TBar.CMethod entry point             :                                   
 | | | | |                                                                 Self as first parameter        :  
 | | | | +----&gt;  TBar.ICInterfaceBMethodThunk                              :                              :  
 | | | |           (first parameter = AICInterface Ptr)                    ret (return)                   : 
 | | | |           Self := first parm - 20 //offset
 | | | |           jmp  ------------------------------------------&gt;  TFoo.BMethod entry point          CODE of TFoo
 | | | |                                                                   Self as first parameter        :  
 | | | +------&gt;  TBar.ICInterfaceAMethodThunk                              :                              : 
 | | |             (first parameter = AICInterface Ptr)                    ret (return)                   : 
 | | |             Self := first parm - 20 //offset                                                       : 
 | | |             jmp  ------------------------------------------&gt;  TFoo.AMethod entry point             :
 | | |                                                                     Self as first parameter        :   
 | | +--------&gt;  TBar.ICInterface_ReleaseThunk                             :                              : 
 | |               (first parameter = AICInterface Ptr)                    ret (return)                   : 
 | |               Self := first parm - 20 //offset                                                       : 
 | |               jmp  ------------------------------------------&gt;  TFoo._Release entry point            :
 | |                                                                       Self as first parameter        :
 | +----------&gt;  TBar.ICInterface_AddRefThunk                              :                              :
 |                 (first parameter = AICInterface Ptr)                    ret (return)                   : 
 |                 Self := first parm - 20 //offset                                                       :
 |                 jmp  ------------------------------------------&gt;  TFoo._AddRef entry point             :
 |                                                                         Self as first parameter        : 
 +------------&gt;  TBar.ICInterfaceQueryInterfaceThunk                       :                              :
                   (first parameter = AICInterface Ptr)                    ret (return)                   :
                   Self := first parm - 20 //offset                                                       :
                   jmp  ------------------------------------------&gt;  TFoo.QueryInterface entry point      : 
                                                                           Self as first parameter        : 
                                                                              :                           :
                                                                           ret (return)                   :



</pre>
</tt>
</div>
<p>
<br>
<br>
<br>
Trying to describe 
<p>A Class that implements an interface carry some code table with it (for a
Class not for an Object instance)</p>
<ol>
  <li>the InterfaceEntry array described before<br>
  </li>
  <li>a VTable for every &lt;Class, Interface&gt; couple, composed by an array
    of (Code) pointers <br>
    for the whole (with inheritance) interface<br>
  </li>
  <li>a &quot;Thunk&quot; code table containing small chunk of code used to
    de-offset Self pointer <br>
    and branching into the implementing class method</li>
</ol>
<p>(I' m not sure if Delphi does some optimization, recycling thunk code and
VTables for similar interfaces in inheritance ?!?)</p>
<p>Now we can compare an object virtual method call with an Interface method
call and found how that they are similar</p>
<p>&nbsp;</p>
<p>&nbsp;</p>


<div class=Code>
<tt>
<pre class="Code Tiny"> 
var
   AObject: TMyBaseObject;                                                     
   ADerivedObject: TMyDerivedObject;                                                     
   AInterface: IMyInterface;
begin

   AObject :=  ADerivedObject;
   AObject.VirtualMethod;
	
	AInterface := ADerivedObject;
	AInterface.InterfaceMethod;

end;    
                                                     
/// Object Virtual Call /////////////////////////////////////////////////////////////////////////
                                                     
   AObject -----+
                |
                |
                V 
      _TMyDerivedObjectInstance
   
  (offset:0)    TMyDerivedObjectVMTPtr  -----+
                                             |
                                             |
                                             V  
                                     TMyDerivedObject.VMT                  
                                             :
            (offset of VirtualMethod)   @TMyDerivedObject.VirtualMethod 
                                        


   ClassPtr   := AObject^  
   ClassVMT   := ClassPtr^  
   EntryPtr   := ClassVMT + (VMToffset of VirtualMethod)
   
   call EntryPtr^ ( AObject );
   
  
/// Interface Method Call /////////////////////////////////////////////////////////////////////////
                                                     
   AInterface ----+
                  |
                  |
                  V
    _TMyDerivedObjectInstance.IMyInterface (hidden field)
   
(offset:0)    TMyDerivedObjectIMyInterfaceVTablePtr  ----+
                                                         |
                                                         |
                                                         V  
                                     TMyDerivedObjectIMyInterfaceVTable                  
                                                        :
          (offset of InterfaceMethod)   @TMyDerivedObjectIMyInterfaceVTableInterfaceMethodThunk 
                                        


   VTablePtr  := AInterface^  
   VTable     := VTablePtr^  
   ThunkPtr   := VTable + (VTable offset of InterfaceMethod)
   
   call ThunkPtr^ ( AInterface );
   
   
   (thunk code)
   
   		Self := AInterface - constant offset
   		jmp  ImplEntry 

</pre>
</tt>
</div>
<p>
<br>
<br>
This is quite important, because let you fool VB client by let them believe to call regular VC++ COM Server ...&nbsp;
<p>
More seriuosly, VPtr,VTable are a part of COM specification, and DAX (Delphi COM internal implementation) follows this specification.&nbsp;<p>
(BTW, there a lot more in DAX: Class Factories, Registration, Type Libraries, Marshalling, Events, MTS...)<p>
&nbsp;<p>
So far, so good ...<p>But sometime things are trickier.<p>What about calling
convention and implementation with virtual methods?<p>Delphi by default uses
Register calling (left to right parameter passing, stored possibly in register),
Delphi COM uses safecall (an stdcall variant, with right to left parameter passing,
stored in the stack) and there are also pascal and cdecl calling modes.<p>The
effect is that there are many possible position where Self pointer as to be
adjusted, and this as to be declared in both interface declaration and
implementing method. The generated code has to handle all the cases...<p>For
virtual calls, everything work as expected: <p>&nbsp;
<ol>
  <li>adjust the Self pointer (in the right place)</li>
  <li>dereference the Self pointer to get the VMT pointer of the implementing
    class</li>
  <li>read the address of the virtual method (at fixed offset from VMT start)</li>
  <li>jump into this address, passing in the right position the self pointer as
    first parameter</li>
</ol>
<p>&nbsp;</p>
<p>And the &quot;implements&quot; keyword ...</p>
<p>&nbsp;
<div class=Code>
<tt>
<pre class="Code"> 
type

   IXInterface = interface(IUnknown)
   ['{713252E5-4636-11D5-B572-00AA00ACFD08}']
      procedure XStaticMethod;
      procedure XVirtualMethod;
   end;

   IYInterface = interface(IUnknown)
   ['{713252E6-4636-11D5-B572-00AA00ACFD08}']
      procedure YMethod;
   end;

   IZInterface = interface(IUnknown)
   ['{713252E4-4636-11D5-B572-00AA00ACFD08}']
   end;


type
   TInnerObject = class(TAggregatedObject,IXInterface,IYInterface)
   public
      procedure XStaticMethod;
      procedure XVirtualMethod; virtual;
      procedure YMethod;
   end;

   TSpecialObject = class(TInnerObject,IXInterface,IYInterface)
   public
      procedure XStaticMethod;
      procedure XVirtualMethod; override;
      procedure YMethod;
   end;

   TFoo = class(TObject,IXInterface,IYInterface,IZInterface)
   private
      FInnerX: TInnerObject;
   protected
    function QueryInterface(const IID: TGUID; out Obj): HResult; virtual; stdcall;
    function _AddRef: Integer; stdcall;
    function _Release: Integer; stdcall;
    function GetX: TInnerObject; virtual;
    function GetY: IYInterface;
   public
      constructor Create;
      destructor  Destroy; override;
      property  InnerX: TInnerObject read GetX implements IXInterface;
      property  InnerY: IYInterface  read GetY implements IYInterface;
   end;

   TBar = class(TFoo,IXInterface,IYInterface,IUnknown)
   private
    FX: TSpecialObject;
    FY: IYInterface;
   protected
      function GetX: TInnerObject; override;
   public
      constructor Create;
      destructor  Destroy; override;
      property Y: IYInterface  read FY implements IYInterface;
      property  X: TSpecialObject read FX implements IXInterface;
   end;
</pre>
</tt>
</div>
<p>The &quot;implements&quot; keyword lets you delegate the implementation of an
interface to a property. This is a very powerful feature that can be applied to
COM style aggregation in a quite simple way. But there a lot of work behind ...<p>There
are two ways to implements an interface with a property<br>
<ul>
  <li>implementing with an interface type property</li>
  <li>implementing with a class type property</li>
</ul>
<p>but there are also three ways to implement a property getter (read clause in
property declaration)</p>
<ul>
  <li>a field getter, directly specifying&nbsp; an object field</li>
  <li>a static (function) method</li>
  <li>a virtual (function) method</li>
</ul>
<p>This is definitively 2 hard 4 me! I hope i can read it on the next Developer
Guide </p>
<p>I just write here some &quot;impressions&quot;</p>
<div align="center">
  <center>
  <table border="1" width="100%" cellpadding="10" cellspacing="0">
    <tr>
      <td width="12%" align="center"><span class="TblKey">Interface</span></td>
      <td width="12%" align="center"><span class="TblKey">Field</span></td>
      <td width="76%" align="left">in this case, there no
        hidden pointer and vtable generation for the class, but when the
        interface is requested the value of the field is returned (instead of is
        address)</td>
    </tr>
    <tr>
      <td width="12%" align="center"><span class="TblKey">Interface</span></td>
      <td width="12%" align="center"><span class="TblKey">Static</span></td>
      <td width="76%" align="left">again, no hidden pointer
        and vtable, but the value of interface field is retrieved by a call to
        the static property getter</td>
    </tr>
    <tr>
      <td width="12%" align="center"><span class="TblKey">Interface</span></td>
      <td width="12%" align="center"><span class="TblKey">Virtual</span></td>
      <td width="76%" align="left">as above, with a virtual
        function call</td>
    </tr>
    <tr>
      <td width="12%" align="center"><span class="TblKey">Class</span></td>
      <td width="12%" align="center"><span class="TblKey">Field</span></td>
      <td width="76%" align="left">here we have a different
        Self to pass to the delegated implementation, so the hidden interface
        pointer points to a VTable for the interface that direct to a special
        version of thunk code that after adjusting the Self of the delegating
        class, uses this information to read the delegated Self to pass to the
        implementation call (jump)</td>
    </tr>
    <tr>
      <td width="12%" align="center"><span class="TblKey">Class</span></td>
      <td width="12%" align="center"><span class="TblKey">Static</span></td>
      <td width="76%" align="left">similar to the previous,
        with the addition that the Self is not available, but is returned by a
        static (function) method call </td>
    </tr>
    <tr>
      <td width="12%" align="center"><span class="TblKey">Class</span></td>
      <td width="12%" align="center"><span class="TblKey">Virtual</span></td>
      <td width="76%" align="left">again, but this time the
        function getter to call is virtual (retrieved by delegating Self,
        ClassPtr, ClassVMT + function offset)</td>
    </tr>
  </table>
  </center>
</div>
<p>&nbsp;</p>
<p>just for example,<p>&nbsp;<p>
<br>

<div class=Code>
<tt>
<pre class="Code"> 
{ TFoo }

constructor TFoo.Create;
var
   i: IZInterface;
begin
   i := Self;
   FInnerX := TInnerObject.Create(i);  //interface inh. to IUnknown
end;

destructor TFoo.Destroy;
begin
  WriteLn('TFoo.Destroy');
  FInnerX.Free;
  inherited;
end;

function TFoo.GetX: TInnerObject;
begin
   result := FInnerX;
end;

{ TFoo.IUnknown }

function TFoo._AddRef: Integer;
begin
   result := -1;
end;

function TFoo._Release: Integer;
begin
   result := -1;
end;

function TFoo.QueryInterface(const IID: TGUID; out Obj): HResult;
begin
  if GetInterface(IID, Obj) then Result := 0 else Result := E_NOINTERFACE;
end;

function TFoo.GetY: IYInterface;
begin
   result := FInnerX;
end;

{ TBar }

constructor TBar.Create;
begin
   inherited;
   FX := TSpecialObject.Create(Self);  //explicit IUnknown
   FY := FX;
end;

destructor TBar.Destroy;
begin
  WriteLn('TBar.Destroy');
  FY := nil;
  FX.Free;
  inherited;
end;

function TBar.GetX: TInnerObject;
begin
   result := FX;
end;


{ TInnerObject }

procedure TInnerObject.XStaticMethod;
begin
  WriteLn(Format(
  'Calls TInnerObject.XStaticMethod  on a %s',[ClassName]));
end;

procedure TInnerObject.XVirtualMethod;
begin
  WriteLn(Format(
  'Calls TInnerObject.XVirtualMethod on a %s',[ClassName]));
end;


procedure TInnerObject.YMethod;
begin
  WriteLn(Format(
  'Calls TInnerObject.YMethod on a %s',[ClassName]));
end;

{ TSpecialObject }

procedure TSpecialObject.XStaticMethod;
begin
  WriteLn(Format(
  'Calls TSpecialObject.XStaticMethod  on a %s',[ClassName]));
end;

procedure TSpecialObject.XVirtualMethod;
begin
  // inherited;
  WriteLn(Format(
  'Calls TSpecialObject.XVirtualMethod on a %s',[ClassName]));
end;

procedure TSpecialObject.YMethod;
begin
  WriteLn(Format(
  'Calls TSpecialObject.YMethod on a %s',[ClassName]));
end;
</pre>
</tt>
</div>
<p>
<br>
<br>
<br>
here a test code ...


<div class=Code>
<tt>
<pre class="Code"> 
procedure TestFoo(AFoo: TFoo);
var
   o: TFoo;
   x: IXInterface;
   y: IYInterface;
   z: IZInterface;
begin

  o := AFoo;

  x := o;
  x.XStaticMethod;   // if AFoo is TBar TFoo.XStatic hides TBar.XStatic
  x.XVirtualMethod;

  y := o;
  y.YMethod;

  z := x as IZInterface;
  z := y as IZInterface;

  z := o;
  x := z as IXInterface;

end;

procedure TestBar(ABar: TBar);
var
   o: TBar;
   x: IXInterface;
   y: IYInterface;
   z: IZInterface;
begin

  o := ABar;

  x := o;
  x.XStaticMethod;
  x.XVirtualMethod;

  y := o;
  y.YMethod;

  z := x as IZInterface;
  z := y as IZInterface;

  z := o;
  x := z as IXInterface;

end;

procedure Test;
var
   AFoo: TFoo;
   ABar: TBar;
begin
   AFoo := TFoo.Create;
   ABar := TBar.Create;

   WriteLn('***TestFoo(AFoo)*****************');
   TestFoo(AFoo);
   Pause;

   WriteLn('***TestFoo(ABar)*****************');
   TestFoo(ABar);
   Pause;

   WriteLn('***TestBar(ABar)*****************');
   TestBar(ABar);
   Pause;

   AFoo.Free;
   ABar.Free;

   Pause;
end;


initialization
   WriteLn('IntGetter.TInnerObject.InstanceSize: ',TInnerObject.InstanceSize);
   WriteLn('IntGetter.TSpecialObject.InstanceSize: ',TSpecialObject.InstanceSize);
   WriteLn('IntGetter.TFoo.InstanceSize: ',TFoo.InstanceSize);
   WriteLn('IntGetter.TBar.InstanceSize: ',TBar.InstanceSize);
end.
</pre>
</tt>
</div>

<p>
<br>
<br>
<br>
and its output ...

<div class=Code>
<tt>
<pre class="Code"> 
IntGetter.TInnerObject.InstanceSize: 16
IntGetter.TSpecialObject.InstanceSize: 24
IntGetter.TFoo.InstanceSize: 16
IntGetter.TBar.InstanceSize: 32

***TestFoo(AFoo)*****************
Calls TInnerObject.XStaticMethod  on a TInnerObject
Calls TInnerObject.XVirtualMethod on a TInnerObject
Calls TInnerObject.YMethod on a TInnerObject

***TestFoo(ABar)*****************
Calls TInnerObject.XStaticMethod  on a TSpecialObject
Calls TSpecialObject.XVirtualMethod on a TSpecialObject
Calls TInnerObject.YMethod on a TInnerObject

***TestBar(ABar)*****************
Calls TSpecialObject.XStaticMethod  on a TSpecialObject
Calls TSpecialObject.XVirtualMethod on a TSpecialObject
Calls TSpecialObject.YMethod on a TSpecialObject

TFoo.Destroy
TBar.Destroy
TFoo.Destroy

</pre>
</tt>
</div>

<p>
<br>
<br>
<br>
<b><span style="background-color: #000000"><font color="#808080">
--- After a pause, a dark night has fallen&&nbsp; ---</font></span></b><p>but we must not be
frightened by the darkness<p>&nbsp;<p>... let's return to interfaces<p>The
description made until now just covers only one aspect of Delphi interface:&nbsp;<p>abusing term, we could call it &quot;the static face&quot; of
interfaces<p>But there is another characteristic in interfaces that (also abusing) we
call &quot;dynamic discovery&quot; ...<p>This is a COM specification aspect,
but it has nothing to do with M$-COM implementation.<p>(Delphi interfaces are a
native language feature and DO NOT require COM as you may want to verify trying
Kylix on Linux.<p>There are (a lot of) Delphi interfaces that provide COM
support, but the vice versa is not true! )<p>Interface inheritance is single
rooted, and the root is IInterface (=IUnknown) type, declared in&nbsp;
System.pas.<p>And the first method in the root is &quot;Query Interface&quot;,
that has to be very &quot;primitive&quot;, considering its position and the fact
that, because of inheritance, every interface requires the implementation of a
&quot;Query&quot; ability.<p>What the action of query, applied to an interface,
is supposed to do?<p>    
<div class=Code>
<tt>
<pre class="Code"> 
  IUnknown = interface
  ...
	function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
  ...	
</pre>
</tt>
</div>

<p>
<br<p>&nbsp;At a first sight it seems quite cryptic, but it has to conform to
COM specification ...<p>It is a function with a out parameter, so returns two
things:<p>&nbsp;
<ul>
  <li>an &quot;untyped&quot; output value (Obj), that can be seen as a reference
    (pointer) to the (generic...) variable Out of the caller<br>
  </li>
  <li>the function result, HResult, a Windows integer typedef.<br>
  </li>
</ul>
<p>the only input parameter is a IID of type TGUID, passed by (constant)
reference, and if this is not enough, the function is declared &quot;stdcall&quot;.<p>Starting
from the last:<br>
<ul>
  <li>&quot;stdcall&quot; tells the compiler to check that implementation
    functions are declared &quot;stdcall&quot; too.<br>
    This means that those functions are compiled following&nbsp; the standard
    Windows calling convention,<br>
    providing a cross language calling capability among Delphi and other
    languages (VB,C++, etc..)<br>
  </li>
  <li>HResult&nbsp; is a return code that usually takes the values S_OK (=
    success,found) or E_NOINTERFACE (= fail, not found)<br>
    (integer constants defined in unit Windows)<br>
  </li>
  <li>the Out parameter should be passed a variable of interface type that will
    be filled&nbsp; with an interface pointer if the query succeeds,&nbsp;<br>
    or cleared to nil in the case of failure.<br>
  </li>
  <li>the &quot;Interface Identification&quot; (IID) is the &quot;Global Unique
    Identifier&quot; (GUID) of an interface<br>
    For example: {00000000-0000-0000-C000-000000000046}<br>
    <br>
  </li>
</ul>
<p>&nbsp;mm..., maybe i ought to have started from the first ...<p>An
Universally Unique Identifier (UUID) is a &quot;key&quot; that can be generated
independently by a computer system in a way that is (statistically) guaranteed
to be different from all other UUID generated by the same or other computer
somewhere in the world (universe ?). It is a DCE standard specification required
by RPC (Remote Procedure Call) protocol, from which COM derives.&nbsp;<p>A
&quot;Global Unique Identifier&quot; (GUID) is a 16 bytes binary constant&nbsp;
and it is the Windows equivalent of a UUID, that follows&nbsp; DCE
specification. A GUID can be obtained from Windows OS with a API call to CoCreateGuid.&nbsp;<p>In
Delphi, GUID constants are declared of type TGUID, a record type in System.pas.
VCL provides a Pascal wrapper of CoCreateGuid with the CreateClassID function
(in the group of COM utility functions as IsEqualGuid, GUIDToString and StringToGUID that do
what their name let guess). In Delphi editor, you may press
&quot;Ctrl-Shift-G&quot; to insert a new fresh GUID in your source.&nbsp;<p>TGUID
type is somewhat &quot;special&quot; and the compiler reserves it a form of
&quot;meta&quot; handling.<p>Consider this code:<p>&nbsp;
<p>
<div class=Code>
<tt>
<pre class="Code"> 
  ...
   IXInterface = interface(IUnknown)
   ['{713252E5-4636-11D5-B572-00AA00ACFD08}']  // TGUIDs are (special) &quot;Attributes&quot;
      procedure XStaticMethod;
      procedure XVirtualMethod;
   end;

   IYInterface = interface(IUnknown)
   ['{713252E6-4636-11D5-B572-00AA00ACFD08}']
      procedure YMethod;
   end;

   IZInterface = interface(IUnknown)
   ['{713252E4-4636-11D5-B572-00AA00ACFD08}']
   end;
  
  ...	

///////////////////////////////////////////////////////////////////////////////

{ Test }

procedure TestGuidType(AObject: TObject; const IID: TGUID);
var
   x: IXInterface;
   y: IYInterface;
   z: IZInterface;
begin

   if Supports(AObject,IZInterface,z) then  //Supports (overloaded) function
   begin

     if IsEqualGUID(IID,IXInterface) then  // IID = IXInterface (type var = type const) ...
     begin
        WriteLn('IXInterface:',GuidToString(IXInterface));   //Interface Type to string
        
        if z.QueryInterface(IXInterface,x) = S_OK then       // QueryInterface (interface) call 
           x.XStaticMethod;
     end;

     if IsEqualGUID(IID,IYInterface) then
     begin
        WriteLn('IYInterface:',GuidToString(IYInterface));
        
        y :=  z as IYInterface;         // &quot;as&quot; operator (can raise EInterfaceNotSupported)
        y.YMethod;
        
     end;

   end;
end;

procedure TestGuid;
var
   o: TBar;
begin

  o := TBar.Create;

  TestGuidType(o,IXInterface);   //Interface Types cast to a TGUID
  TestGuidType(o,IYInterface);

  o.Free;

end;

  
  ...	
  
</pre>
</tt>
</div>

&nbsp;<p>If you look at interface declaration, you see an &quot;extra&quot;
construct&nbsp; [{...}]. It 's an interface identification attribute, that is
not used only to provide (possible) COM support, but it gives Delphi a way to
uniquely identify an interface type. In effect, the compiler automatically
provides a form of cast between (static) interface type constants and the
associated GUID (talking of interfaces, GUIDs are called IIDs ... ).<p>Variable
of TGUID type can be seen as &quot;interface-type&quot; variable, in analogy to
what happens with Delphi &quot;MetaClasses&quot;, although this analogy is not
complete (more on this later).<p>Returning to the &quot;query&quot; question, we
can describe QueryInterface as the action, applied to an interface value, of
asking to its implementation if is able to provide another &quot;view&quot; of
itself, in the form of an interface value (out pointer) of the (variable) type
specified in&nbsp; IID input parameter.<p>A very important point here is that
the identity of the object behind the returned interface can be different from
the one behind the interface queried !&nbsp; (consider &quot;implements&quot;
keyword...)&nbsp;<p>COM specification is very strict on this point and requires
the following (equivalence) rules:<p>&nbsp;
<ul>
  <li>all the queries to an interface for a fixed interface must return the same
    pointer (interface value)<br>
  </li>
  <li>all the interfaces that can be successfully obtained from a fixed
    interface form a &quot;static&quot; set.<br>
    In other words, interfaces cannot be dynamically added or removed from the
    set.<br>
  </li>
  <li>Query calls must be &quot;reflexive&quot;: querying an interface value for
    its interface type must succeeded<br>
  </li>
  <li>Query calls must be &quot;symmetrical&quot; in this sense: if you obtain
    an interface querying another one,<br>
    then querying the obtained interface for the interface type of the original
    interface must succeeded&nbsp;&nbsp;<br>
    (with the same original pointer ...)<br>
  </li>
  <li>Query calls must be &quot;transitive&quot;: if interface B can be obtained
    from A, and C from B,<br>
    then the query of an interface of type C made on A must succeeded<br>
  </li>
</ul>
<p>Delphi is more tolerant, but it is not wise to brake these rules ...<p>With
all these requirements, it could seem that implementing a QueryInterface
function were a complex task, but this is not the case. Rarely it takes more
than three lines of code, thanks to TObject.GetInterface method.<p>In fact, for
non-delegated interfaces, a typical implementation looks like:<p>&nbsp;
<div class=Code>
<tt>
<pre class="Code">  
function TFoo.QueryInterface(const IID: TGUID; out Obj): HResult;
begin
    if GetInterface(IID, Obj) then Result := 0 else Result := E_NOINTERFACE;
end;
</pre>
</tt>
</div>

<p>&nbsp;<p>&nbsp;&nbsp;while, for a delegated one, this version can be used <p>&nbsp;
<div class=Code>
<tt>
<pre class="Code">  
function TDelegated.QueryInterface(const IID: TGUID; out Obj): HResult;
begin
    Result := IUnknown(FDelegating).QueryInterface(IID, Obj);
end;
</pre>
</tt>
</div>
<p>mm.., the second has a &quot;recursive&quot; taste and, despite its brief
form it's actually trickier...</p>
<p>and if we follow the path of calls, we should find another GetInterface.</p>
<p>So what GetInterface does for us ?</p>
<p>In description of InitInstance function, we consider IntfTable as an array of
interface entry records of four fields.</p>
<ul>
  <li>a GUID value, corresponding to the IID of a particular interface
    implemented by the class</li>
  <li>a pointer to the vtable of the &lt;class,interface&gt; couple</li>
  <li>the offset of the hidden interface pointer, inside instance data record</li>
  <li>the ImplGetter field</li>
</ul>
<p>in InitInstance, the GUID field was not used, it is there to mark the
&quot;key&quot; of the interface record that is needed by GetInterface to
retrieve the entry corresponding to the IID input parameter. The search for an
entry starts from the IntfTable of the class of the object, and it can continue
for along class inheritance (Class.Parent/Class.Self).</p>
<p>This structure is a kind of &quot;mirror&quot;, to which the object has to
see, to retrieve its hidden interface pointer (that already owns...) . </p>
<p>What is returned (as pointer) in out variable is this address: </p>
<p>Self + InterfaceEntry.Offset </p>
<p>and that's the &quot;inverse&quot; of what &quot;thunk&quot; code compute!</p>
<p>For delegated interfaces (&quot;implements&quot;) of interface type, ImplGetter field allows to
&quot;retrieve&quot; the value of the interface pointer field with a property access (see InvokeImplGetter
for details)</p>
<p>In case of class type property delegation, the address of the hidden
interface field of the delegating object is returned (see &quot;class type
interface delegation&quot; above...).</p>
<p>In brief,&nbsp; GetInterface retrieve the address of an interface
pointer for the instance, of the interface type (=&gt; GUID) required.</p>
<p>Although they looks quite similar, GetInterface and QueryInterface have a
different meaning:</p>


<ul>
  <li>GetInterface is applied to an object to retrieve an interface that the
    object (directly or by delegation) implements.<br>
  </li>
  <li>QueryInterface is applied to an interface, but it should be
    implemented&nbsp; following COM equivalence rules<br>
    <br>
    (in particular, it must provide&nbsp; &quot;symmetrical&quot; and
    &quot;transitive&quot;&nbsp; behavior)</li>
</ul>
<p>If an object delegates the implementation of an interface to another object,
we cannot use GetInterface of the delegated object to retrieve the interfaces of
the delegating one (a good thing with delegation is that we can reuse
implementation for many delegating classes ...).</p>
<p>But if we consider that GetInterface of the delegating object is able to
retrieve all the interfaces (delegated or not), we have a simple solution to
equivalence problem:</p>
 <p>&nbsp;
<div class=Code>
<tt>
<pre class="Code">  
function TDelegated.QueryInterface(const IID: TGUID; out Obj): HResult;
begin
   Result := IUnknown(FDelegating).QueryInterface(IID, Obj);
end;
</pre>
</tt>
</div>
<p>the only thing needed here is that delegated object stores a
&quot;reference&quot; to delegating one (possibly passed as constructor
argument).</p>
<p>OK, but why this cast? </p>
<p>
<tt>
&nbsp;&nbsp;&nbsp; IUnknown(FDelegating)
</tt>
</p>
<p>
It's indeed a big (and a bit controversial) question ...</p>
<p>COM specification of IUnknown does not stop at QueryInterface but it goes
further:</p>

<div class=Code>
<tt>
<pre class="Code">
  IUnknown = interface
    ['{00000000-0000-0000-C000-000000000046}']
    function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
    function _AddRef: Integer; stdcall;
    function _Release: Integer; stdcall;
  end;
</pre>
</tt>
</div>

<p>It requires that every interface must provide a support for &quot;reference
counting&quot;, with an increment (_AddRef) and decrement (_Release) methods.</p>


<p>But the heavy part is on the client side, because it requires also that every
interface (obtained by a QueryInterface or by assignment) must be properly _AddRefed
and _Released.</p>


<p>These requirements are a sign of COM heritage as a local (machine) way of
dynamic linking executable code (DLL are refcounted...) and although it was
extended to LAN environments (DCOM), it does not work well in wider contexts
(there are many other problems here, and SOAP may surely help more COM than
CORBA ...).</p>


<p>The good news here is that Delphi handles transparently all this stuff,
without a single line of code!</p>


<p>Maybe you may want to read these two (quite old) articles of Don Box, to
compare Delphi COM programming to C++ COM coding&nbsp;</p>


<ul>
  <li><a href="http://www.develop.com/dbox/cxx/InterfacePtr.htm">Interface Pointer Considered Harmful</a></li>
  <li><a href="http://www.develop.com/dbox/cxx/SmartPtr.htm">COM Smart Pointer Even More Harmful</a></li>
</ul>
<p>In fact, Delphi interface pointers aren't just pointer, they are
&quot;smarter&quot;...</p>
<p>The compiler generates around interface variable the code needed to handle
reference count (without errors, it's a compiler!). There are several cases to
deal with:</p>
<ul>
  <li>_AddRef for a new interface obtained with a GetInterface call</li>
  <li>assignment&nbsp; (_Release old value and _AddRef for new value), handling
    nil value and self-assignment</li>
  <li>calling functions with in/out/var interface parameters</li>
  <li>automatic _Release of local interface variables</li>
  <li>stack cleanup for local interface variable in exception handling</li>
  <li>finalization for records and objects before memory deallocation</li>
  <li>temporary interface variable allocation and release for dynamic casts</li>
</ul>
<p>As a small sample of this behavior:</p>
<div class=Code>
<tt>
<pre class="Code">
unit IntCount;

interface

uses
   SysUtils;

type

   IPInterface = interface
   ['{82F64441-505E-11D5-B57A-00AA00ACFD08}']
   end;

   IQInterface = interface
   ['{82F64442-505E-11D5-B57A-00AA00ACFD08}']
   end;

   TFoo = class(TObject,IPInterface,IQInterface,IUnknown)
   protected
    function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
    function _AddRef: Integer; stdcall;
    function _Release: Integer; stdcall;
   public
     destructor Destroy; override;
   end;

   TBar = class(TInterfacedObject,IPInterface,IQInterface)
   protected
    function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
    function _AddRef: Integer; stdcall;
    function _Release: Integer; stdcall;
   public
     destructor Destroy; override;
   end;

procedure Test;

implementation

uses
   IntUti,
   Windows, //for E_NOINTERFACE
   ComObj;  //for GuidToString

{ TFoo }

function TFoo._AddRef: Integer;
begin
   result := -1;
   WriteLn(ClassName,'._AddRef (After)');
end;

function TFoo._Release: Integer;
begin
   WriteLn(ClassName,'._Release (Before)');
   result := -1;
end;

function TFoo.QueryInterface(const IID: TGUID; out Obj): HResult;
begin
  WriteLn(ClassName,'.QueryInterface for ',GuidToString(IID));
  if GetInterface(IID, Obj) then Result := 0 else Result := E_NOINTERFACE;
end;

destructor TFoo.Destroy;
begin
  WriteLn(ClassName,'.Destroy');
  inherited;
end;

{ TBar }

function TBar._AddRef: Integer;
begin
   result := inherited _AddRef;
   WriteLn(ClassName,'._AddRef (After):',RefCount);
end;

function TBar._Release: Integer;
begin
   WriteLn(ClassName,'._Release (Before):',RefCount);
   result := inherited _Release;
end;

function TBar.QueryInterface(const IID: TGUID; out Obj): HResult;
begin
   WriteLn(ClassName,'.QueryInterface for ',GuidToString(IID));
   result := inherited QueryInterface(IID,Obj);
end;

destructor TBar.Destroy;
begin
  WriteLn(ClassName,'.Destroy');
  inherited;
end;

///////////////////////////////////////////////////////////////////////////////

{ Test }

procedure NoNeedsQ(Q: IQInterface);
begin
   WriteLn('Inside NoNeedsQ');   // no reference to Q in body ...
end;


procedure NeedsQ(Q: IQInterface; DoRaise: Boolean);
var
   Z: IPInterface;
begin                         // here Q is stabilized (_AddRef) once more

   WriteLn('Inside NeedsQ - Before Z');

   Z := Q as IPInterface;

   WriteLn('Inside NeedsQ - After Z');

   if DoRaise then
      raise Exception.Create('Raise in NeedsQ');

   WriteLn('Inside NeedsQ - not raised ...');

end;                       // here Z and Q are _Released (if not raised only ...)


procedure TestExceptCounted;
var
   P: IPInterface;
begin

   P := TBar.Create;                // P._AddRef in assignment

   try

      WriteLn('Before NeedsQ');

      NeedsQ(P as IQInterface, true);  //temporary (_AddRefed) interface by QueryInterface

      WriteLn('After NeedsQ');

   except                           // stack cleanup (_Release of Z,Q)
   
      WriteLn('Exception in NeedsQ');
      
   end;

   P := nil;                        // P._Release

   WriteLn('After P := nil');

end;  // temporary interface released here ...

procedure TestRefCounted;
var
   P: IPInterface;
begin

   P := TBar.Create;                // P._AddRef in assignment

   WriteLn('Before NoNeedsQ');

   NoNeedsQ(P as IQInterface);  //temporary (_AddRefed) interface by QueryInterface

   WriteLn('After NoNeedsQ');

   WriteLn('Before NeedsQ');

   NeedsQ(P as IQInterface, false);  //temporary (_AddRefed) interface by QueryInterface

   WriteLn('After NeedsQ');

   P := nil;                        // P._Release

   WriteLn('After P := nil');

end;  // temporary interface released here ...


procedure TestNODanger;
var
   AFoo: TFoo;
begin

   AFoo := TFoo.Create;

   NoNeedsQ(AFoo); // static (compiler) @hidden pointer (no temporary/_AddRef)

   AFoo.Free;

   WriteLn('AFoo DESTROYED HERE ...');

end; // ok, safe

procedure TestAVDanger;
var
   AFoo: TFoo;
begin

   AFoo := TFoo.Create;

   NoNeedsQ(AFoo as IQInterface); // temporary _AddRefed

   AFoo.Free;

   WriteLn('AFoo DESTROYED HERE ...');

end;  // calls _Release on a Destroyed object; BANG...



procedure Test;
begin

   WriteLn('--- Before TestNODanger---------------------');
   TestNODanger;
   WriteLn('--- After  TestNODanger---------------------');
   WriteLn('');
   WriteLn('--- Before TestAVDanger---------------------');
   TestAVDanger;
   WriteLn('--- After  TestAVDanger---------------------');

   WriteLn('');
   WriteLn('--- Before TestRefCounted-------------------');
   TestRefCounted;
   WriteLn('--- After  TestRefCounted-------------------');
   WriteLn('');
   WriteLn('--- Before TestExceptCounted----------------');
   TestExceptCounted;
   WriteLn('--- After  TestExceptCounted----------------');
   Pause;
end;

end.

</pre>
</tt>
</div>

<p>that gives this output ...</p>

<div class=Code>
<tt>
<pre class="Code">

--- Before TestNODanger----------------------
Inside NoNeedsQ
TFoo.Destroy
AFoo DESTROYED HERE ...
--- After  TestNODanger----------------------


--- Before TestAVDanger----------------------
TFoo._AddRef (After)
Inside NoNeedsQ
TFoo.Destroy
AFoo DESTROYED HERE ...
TFoo._Release (Before)
--- After  TestAVDanger----------------------


--- Before TestRefCounted--------------------
TBar._AddRef (After):1
Before NoNeedsQ
TBar.QueryInterface for {82F64442-505E-11D5-B57A-00AA00ACFD08}
TBar._AddRef (After):2
Inside NoNeedsQ
After NoNeedsQ
Before NeedsQ
TBar.QueryInterface for {82F64442-505E-11D5-B57A-00AA00ACFD08}
TBar._AddRef (After):3
TBar._AddRef (After):4
Inside NeedsQ - Before Z
TBar.QueryInterface for {82F64441-505E-11D5-B57A-00AA00ACFD08}
TBar._AddRef (After):5
Inside NeedsQ - After Z
Inside NeedsQ - not raised ...
TBar._Release (Before):5
TBar._Release (Before):4
After NeedsQ
TBar._Release (Before):3
After P := nil
TBar._Release (Before):2
TBar._Release (Before):1
TBar.Destroy
--- After  TestRefCounted--------------------


--- Before TestExceptCounted-----------------
TBar._AddRef (After):1
Before NeedsQ
TBar.QueryInterface for {82F64442-505E-11D5-B57A-00AA00ACFD08}
TBar._AddRef (After):2
TBar._AddRef (After):3
Inside NeedsQ - Before Z
TBar.QueryInterface for {82F64441-505E-11D5-B57A-00AA00ACFD08}
TBar._AddRef (After):4
Inside NeedsQ - After Z
TBar._Release (Before):4
TBar._Release (Before):3
Exception in NeedsQ
TBar._Release (Before):2
After P := nil
TBar._Release (Before):1
TBar.Destroy
--- After  TestExceptCounted-----------------
</pre>
</tt>
</div>


<p>in particular, an example on how things can sometimes go wrong with reference counting, look at this apparently innocent code</p>

<div class=Code>
<tt>
<pre class="Code">
procedure TestAVDanger;
var
   AFoo: TFoo;
begin

   AFoo := TFoo.Create;

   NoNeedsQ(AFoo as IQInterface); // temporary _AddRefed

   AFoo.Free;

   WriteLn('AFoo DESTROYED HERE ...');

end;  // calls _Release on a Destroyed object; BANG...
</pre>
</tt>
</div>

<p>here you may have an &quot;access violation&quot; because temporary interface is Relesed after object destruction ...</p>


<div class=Code>
<tt>
<pre class="Code">
TFoo._AddRef (After)
Inside NoNeedsQ
TFoo.Destroy
AFoo DESTROYED HERE ...
TFoo._Release (Before)
</pre>
</tt>
</div>




<p>&nbsp;</p>
<p>but if you don't mix different allocation approaches (reference counting vs
explicit destruction) everything works well, even with exceptions!</p>
<p>Smart pointers are for &quot;client&quot; side. In the &quot;server&quot;,
there are four alternatives for &quot;reference counting&quot; implementation:</p>
<ul>
  <li>ignore it, (as TFoo class in the previous example) with _AddRef and
    _Release simply returning -1 at every call.<br>
    destruction has to be done in the usual Delphi way, with an explicit Free
    call.<br>
    This can be a good strategy, but it requires that every interface reference
    to the object is nil-fied before object destruction.<br>
    (after all, it's never a good practice leaving around references to
    destroyed objects... but with interfaces you cannot do that, because you get
    AV on automatic _Release)<br>
  </li>
  <li>increment an reference counter object variable in _AddRef, decrement it in
    _Release, and when the count is zero, &quot;suicide&quot; the object (Self.Free)<br>
    This is the behavior of TInterfacedObject, and the standard for COM objects.<br>
    It must be noted that counter update should be done in a thread safe manner
    (TInterfacedObject uses InterlockedIncrement API function)<br>
  </li>
  <li>for a delegated object, delegate to delegating _AddRef,_Release should be
    adopted (don't blame me, i'm not a good writer...)<br>
    TAggregatedObject (in ComObj unit, what a strange place...) implements
    RefCounting in this way.<br>
    (delegating/delegated objects share the same counter and should be
    destroyed&nbsp; all together when no more interfaces are around)<br>
  </li>
  <li>&quot;aggregation support&quot;, that&nbsp; merges reference counting and
    delegation for object that can work alone or as a delegated object in an
    &quot;aggregate&quot;.<br>
    TComObject is an example of class supporting aggregation (in ComObj, maybe a
    lightweight version in System in the future...)</li>
</ul>
<p>&nbsp;</p>
<p>Returning to the cast question </p>
<p>
<tt>
&nbsp;&nbsp;&nbsp; IUnknown(FDelegating)
</tt>
</p>
<p>If you look at TAggregatedObject class, you see that the Controller parameter
of IUnknown type is stored in a untyped pointer field, and casted back to
IUnknown when used. The reason why is that casting an interface to a pointer
&quot;disables&quot;&nbsp; automatic _AddRef,_Release call generation. This is
important to avoid the &quot;Circular Reference Problem&quot;, when two object
references one another, and don't bias interface counter for the delegating
object. Without this trick, the delegating object could&nbsp; never be freed.</p>
<p>&nbsp;</p>
<p>&quot;Aggregation&quot; is a very powerful feature, and very easy to do in
Delphi:</p>
<p>&nbsp;</p>

<div class=Code>
<tt>
<pre class="Code">
unit GlueBox;

interface

uses
   ComObj,ComServ;

const
  SIID_IFirst           = '{B96D9865-4006-11D5-B56D-00AA00ACFD08}';
 SCLASS_First           = '{B96D9866-4006-11D5-B56D-00AA00ACFD08}';
   IID_IFirst:          TGUID =
  SIID_IFirst;
  CLASS_First:          TGUID =
 SCLASS_First;

  SIID_ISecond          = '{B96D9867-4006-11D5-B56D-00AA00ACFD08}';
 SCLASS_Second          = '{B96D9868-4006-11D5-B56D-00AA00ACFD08}';
   IID_ISecond:         TGUID =
  SIID_ISecond;
  CLASS_Second:         TGUID =
 SCLASS_Second;

  SIID_IThird           = '{B96D9869-4006-11D5-B56D-00AA00ACFD08}';
 SCLASS_Third           = '{B96D986A-4006-11D5-B56D-00AA00ACFD08}';
   IID_IThird:          TGUID =
  SIID_IThird;
  CLASS_Third:          TGUID =
 SCLASS_Third;

type
   IFirst = interface(IUnknown)
   [SIID_IFirst]
      procedure Execute;
   end;

   ISecond = interface(IUnknown)
   [SIID_ISecond]
      procedure Execute;
   end;

   IThird = interface(IUnknown)
   [SIID_IThird]
      procedure Execute;
   end;

type
   TBase = class(TComObject)
   private
    FName: string;
   protected
    function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
    function _AddRef: Integer; stdcall;
    function _Release: Integer; stdcall;
   public
    function ObjQueryInterface(const IID: TGUID; out Obj): HResult; override; stdcall;
    function ObjAddRef: Integer;  override; stdcall;
    function ObjRelease: Integer;  override; stdcall;
    procedure Initialize; override;
    destructor Destroy; override;
    procedure Execute; virtual;
   end;

   TFirst = class(TBase,IFirst)
   public
    procedure Initialize; override;
   end;

   TSecond = class(TBase,ISecond,IFirst)
   private
    FFirst: IUnknown;
    function GetFirst: IFirst;
   public
    property First: IFirst read GetFirst implements IFirst;
    procedure Initialize; override;
    destructor Destroy; override;
   end;

   TThird = class(TBase,IThird,ISecond,IFirst)
   private
    FSecond: IUnknown;
    function GetSecond: ISecond;
    function GetFirst: IFirst;
   public
    property First: IFirst read GetFirst implements IFirst;
    property Second: ISecond read GetSecond implements ISecond;
    procedure Initialize; override;
    destructor Destroy; override;
   end;


procedure Test;

implementation

uses
   GlueUti;

///////////////////////////////////////////////////////////////////////////////

{ TBase }

function TBase._AddRef: Integer;
begin
   WriteLn(ClassName,'._AddRef');
   result := inherited _AddRef;
end;

function TBase._Release: Integer;
begin
   WriteLn(ClassName,'._Release');
   result := inherited _Release;
end;

function TBase.ObjAddRef: Integer;
begin
   WriteLn(ClassName,'.ObjAddRef');
   result := inherited ObjAddRef;
end;

function TBase.ObjRelease: Integer;
begin
   WriteLn(ClassName,'.ObjRelease');
   result := inherited ObjRelease;
end;

function TBase.ObjQueryInterface(const IID: TGUID; out Obj): HResult;
begin
   WriteLn(ClassName,'.ObjQueryInterface:',GuidToString(IID));
   result := inherited ObjQueryInterface(IID,Obj);
end;

function TBase.QueryInterface(const IID: TGUID; out Obj): HResult;
begin
   WriteLn(ClassName,'.QueryInterface:',GuidToString(IID));
   result := inherited QueryInterface(IID,Obj);
end;

procedure TBase.Execute;
begin
   WriteLn(ClassName,'.Execute ................');
end;

destructor TBase.Destroy;
begin
  WriteLn(ClassName,'.Destroy');
  inherited;
end;

procedure TBase.Initialize;
begin
  inherited;
  FName := ClassName;  //4 debug ...
  WriteLn(ClassName,'.Initialize');
end;

///////////////////////////////////////////////////////////////////////////////

{ TFirst }

procedure TFirst.Initialize;
begin
  inherited;
end;

{ TSecond }

function TSecond.GetFirst: IFirst;
begin
   result := FFirst as IFirst;
end;

procedure TSecond.Initialize;
var
   Unk: IUnknown;
begin
  inherited;

  if Assigned(Controller) then
     Unk := Controller
  else
     Unk := Self;

   FFirst :=  // IUnknown
    ComClassManager.GetFactoryFromClassID(CLASS_First)
       .CreateComObject(Unk)
end;

destructor TSecond.Destroy;
begin
  FFirst := nil;
  inherited;
end;

{ TThird }

destructor TThird.Destroy;
begin
  FSecond := nil;
  inherited;
end;

function TThird.GetFirst: IFirst;
begin
   result := FSecond as IFirst;
end;

function TThird.GetSecond: ISecond;
begin
   result := FSecond as ISecond;
end;

procedure TThird.Initialize;
var
   Unk: IUnknown;
begin
  inherited;

  if Assigned(Controller) then
     Unk := Controller
  else
     Unk := Self;

   FSecond :=  // IUnknown
    ComClassManager.GetFactoryFromClassID(CLASS_Second)
       .CreateComObject(Unk)
end;

///////////////////////////////////////////////////////////////////////////////

{ Test }

procedure NeedsFirst(o: IFirst);
begin
   o.Execute;
end;

procedure NeedsSecond(o: ISecond);
begin
   o.Execute;
end;

procedure NeedsThird(o: IThird);
begin
   o.Execute;
end;

procedure TestBox;
var
   Unk: IUnknown;
   First: IFirst;
   Second: ISecond;
   Third: IThird;
begin

   WriteLn('---TestBox--------------------------');

   Unk :=
      ComClassManager.GetFactoryFromClassID(CLASS_Third)
         .CreateComObject(nil);

   Third  := Unk as IThird;
   Second := Unk as ISecond;
   First  := Unk as IFirst;

   NeedsThird(Third);
   NeedsSecond(Second);
   NeedsFirst(First);

   NeedsThird(Third);
   NeedsSecond(Third as ISecond);
   NeedsFirst(Third as IFirst);

   NeedsSecond(Second);
   NeedsThird(Second as IThird);
   NeedsFirst(Second as IFirst);

   NeedsFirst(First);
   NeedsThird(First as IThird);
   NeedsSecond(First as ISecond);

   Third  := nil;
   Second := nil;
   First  := nil;

   WriteLn('Still Alive ...');

end; //destroyed here

procedure Test;
begin
   TestBox;
   GlueUti.Pause;
end;


initialization
  TComObjectFactory.Create(ComServ.ComServer, TFirst, Class_First,
    'First','First',ciMultiInstance, tmApartment);  // tmSingle?
  TComObjectFactory.Create(ComServ.ComServer, TSecond, Class_Second,
    'Second','Second',ciMultiInstance, tmApartment);
  TComObjectFactory.Create(ComServ.ComServer, TThird, Class_Third,
    'Third','Third',ciMultiInstance, tmApartment);
end.
</pre>
</tt>
</div>






<p>&nbsp;</p>
<p>just few comments on this code:</p>
<ul>
  <li>M$-COM is not involved here, despite the base class is TComObject<br>
  </li>
  <li>aggregation&nbsp; works on different layers (an aggregate of TFirst and
    TSecond can be considered a whole thing that can be aggregated to a TThird
    object)<br>
  </li>
  <li>TSecond and TThird classes do not known the class type of the aggregated
    part, but they know only the GUID of the interface<br>
  </li>
  <li>all the references among delegating and delegated object is made with
    interfaces (consider aggregation with non Delphi objects...)<br>
  </li>
  <li>the implementation conforms to COM equivalence rules, and correctly shares
    reference counting<br>
  </li>
  <li>if you look at TComObject class, you may find two IUnkown implementations
    (called &quot;Delegating Unknown&quot; and &quot;Non Delegating
    Unknown&quot;)<br>
    the &quot;Delegating&quot; one redirects to the Controller if the object is
    aggregated or to the &quot;Non Delegating&quot; one otherwise (similar to
    TAggregatedObject)<br>
    the &quot;Non Delegating&quot; one implements standard reference counting
    (as TInterfacedObject) and it's owned only by the controller.<br>
    Delphi syntax for interface method name resolution gives a very elegant
    solution for this. (weak virtual inheritance with exceptions ...)</li>
</ul>
<p>&nbsp;</p>
<p>uh.., i'm quite tired and the sun is rising now ...</p>
<p>but just one more thing ...</p>
<p>&quot;Interface reflection&quot;</p>
<p>With Type Library Delphi offers a rich set of reflection features for
interfaces (see ITypeInfo COM interface), you may &quot;query&quot; your
interfaces deep down method argument types (useful for automatic marshalling).
There's an example for that in Eric Harmon's &quot;Delphi COM Programming&quot;
nice book.</p>
<p>But this is a COM feature, and i prefer to mention this OP feature.</p>
<p>In&nbsp; unit TypInfo, you find functions to access RTTI (RunTimeTypeInfo)
tables for a lot of things, and interfaces are in, as you may guess ...</p>
<p>&nbsp;
</p>




<div class=Code>
<tt>
<pre class="Code">
unit IntSight;

interface

uses
  Windows,SysUtils,Classes;

type

   IAInterface = interface(IUnknown)
   ['{713252E1-4636-11D5-B572-00AA00ACFD08}']
      procedure AMethod;
   end;

   IBInterface = interface(IAInterface)
   ['{713252E2-4636-11D5-B572-00AA00ACFD08}']
      procedure BMethod;
   end;

   ICInterface = interface(IAInterface)
   ['{713252E3-4636-11D5-B572-00AA00ACFD08}']
      procedure BMethod;
      procedure CMethod;
   end;

   IZInterface = interface(IUnknown)
   ['{713252E4-4636-11D5-B572-00AA00ACFD08}']
   end;

implementation

uses
   ActiveX,ComObj,TypInfo,IntUti;

///////////////////////////////////////////////////////////////////////////////

{ Test }

procedure TestDumpType(IntfInfo: PTypeInfo);
var
   pInfo: PTypeInfo;
   pData: PTypeData;
begin
   WriteLn('--------------------------------');

   pInfo := IntfInfo;

   while pInfo &lt;&gt; nil do
   begin
      WriteLn('Type:', pInfo.Name );
      pData := GetTypeData(pInfo);
      WriteLn('Guid:',GuidToString(pData.Guid));
      WriteLn('Unit:',pData.IntfUnit);
      pInfo := nil;
      if pData.IntfParent &lt;&gt; nil then
         pInfo := pData.IntfParent^
   end;

end;

procedure TestType;
begin

   TestDumpType(TypeInfo(ICInterface));
   TestDumpType(TypeInfo(IBInterface));
   TestDumpType(TypeInfo(IAInterface));
   TestDumpType(TypeInfo(IZInterface));

end;


</pre>
</tt>
</div>

<br>
<br>

<div class=Code>
<tt>
<pre class="Code">

--------------------------------
Type:ICInterface
Guid:{713252E3-4636-11D5-B572-00AA00ACFD08}
Unit:IntSight
Type:IAInterface
Guid:{713252E1-4636-11D5-B572-00AA00ACFD08}
Unit:IntSight
Type:IUnknown
Guid:{00000000-0000-0000-C000-000000000046}
Unit:System
--------------------------------
Type:IBInterface
Guid:{713252E2-4636-11D5-B572-00AA00ACFD08}
Unit:IntSight
Type:IAInterface
Guid:{713252E1-4636-11D5-B572-00AA00ACFD08}
Unit:IntSight
Type:IUnknown
Guid:{00000000-0000-0000-C000-000000000046}
Unit:System
--------------------------------
Type:IAInterface
Guid:{713252E1-4636-11D5-B572-00AA00ACFD08}
Unit:IntSight
Type:IUnknown
Guid:{00000000-0000-0000-C000-000000000046}
Unit:System
--------------------------------
Type:IZInterface
Guid:{713252E4-4636-11D5-B572-00AA00ACFD08}
Unit:IntSight
Type:IUnknown
Guid:{00000000-0000-0000-C000-000000000046}
Unit:System

</pre>
</tt>
</div>


<hr>
<font face="Arial,Helv,Helvetica" size="+1" color="yellow">
<table border="1" width="120" cellspacing="5">
  <tr align="center">
    <td width="50%" bgcolor="black"><a href="ipragmatic.html"><font color="cyan"><b>Next</b></font></a>&nbsp;</td>
    <td width="50%" bgcolor="black"><a href="index.html"><font color="cyan"><b>Up..<b></font></a> </td>
  </tr>
</table>
<font>
</body>

</html>
<!-- text below generated by server. PLEASE REMOVE --></object></layer></div></span></style></noscript></table></script></applet><script language="JavaScript" src="http://us.i1.yimg.com/us.yimg.com/i/mc/mc.js"></script><script language="JavaScript" src="http://us.js2.yimg.com/us.js.yimg.com/lib/smb/js/hosting/cp/js_source/geov2_001.js"></script><script language="javascript">geovisit();</script><noscript><img src="http://visit.geocities.yahoo.com/visit.gif?us1240654469" alt="setstats" border="0" width="1" height="1"></noscript>
<IMG SRC="http://geo.yahoo.com/serv?s=76001067&t=1240654469&f=us-w5" ALT=1 WIDTH=1 HEIGHT=1>
implements.html (69,497 bytes)   

Jonas Maebe

2009-04-25 12:15

manager   ~0027067

I've attached that page since Geocities is being discontinued.

Issue History

Date Modified Username Field Change
2007-05-26 01:48 Mario R. Carro New Issue
2007-05-26 16:09 Jonas Maebe Note Added: 0012814
2007-06-05 00:54 Mario R. Carro File Added: test.pas
2007-06-05 00:57 Mario R. Carro Note Added: 0012961
2007-10-28 14:00 Marco van de Voort FPCOldBugId => 0
2007-10-28 14:00 Marco van de Voort FPCTarget => -
2007-10-28 14:00 Marco van de Voort Note Added: 0015797
2007-10-28 14:00 Marco van de Voort Severity minor => major
2007-10-28 14:01 Marco van de Voort Relationship added related to 0004842
2008-01-29 23:25 Marco van de Voort Status new => assigned
2008-01-29 23:25 Marco van de Voort Assigned To => Florian
2008-02-03 21:41 Marco van de Voort Note Added: 0017592
2008-05-07 12:08 Marco van de Voort Tag Attached: jcl
2008-12-10 09:04 Marco van de Voort Relationship added related to 0012778
2009-04-25 12:15 Jonas Maebe File Added: implements.html
2009-04-25 12:15 Jonas Maebe Note Added: 0027067
2009-08-09 11:28 Florian Target Version => 2.4.0
2009-10-16 23:39 Marco van de Voort Tag Attached: com
2010-03-27 16:18 Ivo Steinmann Assigned To Florian => Ivo Steinmann
2010-04-29 18:01 Jonas Maebe Relationship added related to 0016365
2012-01-04 15:53 Marco van de Voort Relationship added parent of 0016531