SlideShare a Scribd company logo
1 Delphi interfaces/abstract
This article covers interfaces in unmanaged Windows and Linux code. It focuses on
practical issues developers have to face when they use interfaces in their code.

2 Why interfaces?
Interfaces enable us to write code that is implementation-independent. This is very useful
when writing more complex applications and becomes even more important when we
decide to split the application into packages.
With appropriate use of interfaces we can change the implementation of a class in a
package, recompile the package and still use it with the original application.
Interfaces also allow us to write more loosely coupled class structures resulting in a more
flexible and easily upgradeable application.

2.1 Interface history
The first version of Delphi to support interfaces was Delphi 3. But there was a way to use
and develop COM interfaces even in Delphi 2. How was that possible? The answer is
simple. If you ignore the fact that a class can implement more than one interface, you can
think of an interface as a pure abstract class.
type
  IIntf1 = class
  public
     function Test; virtual; abstract;
  end;

  IIntf2 = interface
  public
    function Test;
  end;
Obviously, IIntf1 has many limitations, but this was the way to write COM interfaces in
Delphi 2. The reason why the two constructs are comparable is the structure of the
Virtual Method Table. You can think of an interface as a VMT definition.
Delphi 3 introduced native interface support, making constructs like IIntf1 obsolete. It
also added the biggest improvement to Object Pascal: multiple interface implementations.
type
  IIntf1 = interface … end;
  IIntf2 = interface … end;
  TImplementation = class(TAncestor, IIntf1, IIntf2) … end;                    // !

Construct on line // 1 would be illegal if IIntf1 and IIntf2 were declared as abstract
classes.

2.2 Interface implementation
Now that we have decided to use interfaces in our application we have to overcome a few
difficulties in declaring and implementing the application.
2.2.1 GUIDs
The most important difference between an abstract class and an interface is that an
interface should have a GUID. GUID is a 128bit constant that Delphi uses to uniquely
identify an interface. You may have encountered GUIDs in COM, and Delphi uses the
same principles as COM to get access to an interface.
type
  ISimpleInterface = interface
     ['{BCDDF1B6-73CC-406C-912F-7148095F1F4C}'] // 1
  end;
GUID is shown on the line // 1. As you can see the GUID on line // 1 is not a 128bit
integer, it is a string. Delphi compiler, however, recognizes the format of the string and
converts it into GUID structure.
type
  TGUID    = packed record
     D1:   LongWord;
     D2:   Word;
     D3:   Word;
     D4:   array[0..7] of Byte;
  end;
The same string to 128bit Integer also applies when defining a GUID constant:
type
  IID_ISimpleInterface: TGUID = '{BCDDF1B6-73CC-406C-912F-
7148095F1F4C}';

2.2.2 Why are GUIDs important?
Why does an interface need to be uniquely identifiable? The answer is simple: because
Delphi classes can implement multiple interfaces. When an application is running, there
has to be a mechanism that will get pointer to an appropriate interface from an
implementation. The only way to find out if an object implements an interface and to get
a pointer to implementation of that interface is through GUIDs.

2.3 Interface core methods
Because an interface is simply a template for the implementation, it cannot control it life.
This is why native Delphi (as well as COM) uses reference counting.
Reference counting in Delphi is implemented in three fundamental interface-helper
classes: TInterfacedObject, TAggregatedObject and TContainedObject. Each of these
classes has its specific uses, which will be covered later in this article. What is common
for all these classes, however, are the three fundamental interface methods:
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;
function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
Let us start with the simple ones; _AddRef and _Release. As you can probably guess
from their names, _AddRef increases a reference counter by one and _Release decreases
the counter. The behaviour of _Release depends on the class used in implementation. The
pivotal method of interface management is QueryInterface. It takes GUID of an interface
to get and returns a pointer to its implementation in Obj. For COM-compatibility, the
method returns OLE HResult result values.

2.3.1 QueryInterface, as operator and assignment operator
How is QueryInterface related to as and assignment operators? The answer is simple:
QueryInterface is used to get a pointer to an interface from the implementing class.
Let us consider this code snippet.
type
  TCls = class(TInterfacedObject, IIntf1, IIntf2)
  protected
     // implementation of interfaces.
  end;

var
  C: TCls;
  I1: Intf1;
  I2: Intf2;
begin
  C := TCls.Create;
  I1 := C; // 1
  I2 := C; // 2


  // call methods of I1 and I2

  I1 := nil;
  I2 := nil;
end;
The code on lines //1 and //2 is compiled as call to _IntfCast procedure. This procedure
calls QueryInterface, which returns a pointer to an interface in an implementation
instance. It also releases previous value of destination.
procedure _IntfCast(var Dest: IInterface; const Source: IInterface;
const IID: TGUID);
var
  Temp: IInterface;
begin
  if Source = nil then
    Dest := nil
  else
  begin
    Temp := nil;
     if Source.QueryInterface(IID, Temp) <> 0 then // 1
       Error(reIntfCastError)
     else
       Dest := Temp;
  end;
end;
Exactly the same code will be produced if we use as construct:
I1 := Impl as ISimpleInterface; // 1

The as and := operators raise EIntfCastError if QueryInterface returns nil pointer. If you
want to avoid using exception handling, use QueryInterface instead:
  Impl.QueryInterface(IAnotherInterface, A);

QueryInterface is one of the pivotal methods of interfaces in Delphi. The other two
methods, _AddRef and _Release are used in controlling lifetime of an interface.

2.3.2 Interface creation and destruction
An interface is created by calling implementation’s constructor. Then the RTL copies a
pointer to the interface from the created implementation instance to the interface variable.
You may have already guessed that copying of an interface is firstly a simple pointer
assignment and then increase of the reference counter. To increase the reference counter,
RTL calls _AddRef method provided by the implementation’s base class.
Let us have a look at Delphi pseudo-code for lines //1 to //3:
// line1
begin
  var C: TSimpleImplementation := TSimpleImplementacion.Create;
  if (C = nil) then Exit;
  var CVMT := C - VMTOffset;
  _IntfCopy(Intf, CVMT);
end;
The code on line 1 constructs an instance of the implementation class, get pointer to its
VMT and then call _IntfCopy function. The most important piece of code is _IntfCopy.
procedure _IntfCopy(var Dest: IInterface;             const Source: IInterface);
var
  OldDest: Pointer;
begin
  OldDest := Dest;                     //             1
  if Source <> nil then
     Source._AddRef;                   //             2
  Dest := Source;
  if OldDest <> nil then
     IInterface(OldDest)._Release;     //             3
end;
In most cases, the interface assignment means assigning non-nil pointer to existing
interface to a nil pointer. If a destination interface is not nil – that means it already
references an existing interface – it must be released after successful assignment of the
new interface. This is why code on line // 1 copies old destination to a temporary
variable. Then procedure then increases reference counter for source. It is important to
increase the reference counter before the actual assignment. If the procedure did not do
this, another thread might _Release an interface before _IntfCopy could finish executing.
This would result in assigning a freed instance, which would result in an Access violation
exception. Hence, line // 2 increases reference counter in the source interface before
copying its value to the destination. Finally, if Dest was assigned to another interface, the
interface is _Released.
Once the interface is created, reference counter increased and destination is assigned with
the newly created interface, we can safely call its methods.
// line 2:
begin
  var ImplVMT = Intf + VMTOffset;
  (ImplVMT + MethodOffset)(); // 2
end;
Bearing in mind that an interface is simply a VMT template a method call must be a call
to a method that is looked up in implementation’s VMT. In our simple example, Test is
the only virtual method if the implementation, MethodOffset is going to be 0, and
VMTOffset is going to be $0c. The actual compiled code looks like this:
// set eax to the address of the first local variable
mov      eax, [ebp - $04]
// edx := @eax
mov      edx, [eax]
// call to ((procedure of object)(edx + VMTOffset + MethodOffset))()
call     dword ptr [edx + $0c]
The code actually calls Test method of the implementation class. The code is not too
different from the call to a regular virtual method.
Line 3 in the original listing is as important as line 1, because it controls destruction of
the interface. It is important to remember that – in special cases – when an interface’s
reference counter reaches zero, the implementation class is destroyed. The danger is that
the pointer to the implementation may remain the same, thus an if-not-nil test for the
implementation does no guarantee that an implementation still exists.
// line 3:
begin
  _IntfClear(Intf);
end;
As you can tell, the most important code is hidden in _IntfClear method. This method
must _Release the interface, and (if appropriate, free the implementation).
function _IntfClear(var Dest: IInterface): Pointer;
var
  P: Pointer;
begin
  Result := @Dest;
  if Dest <> nil then
  begin
     P := Pointer(Dest);
     Pointer(Dest) := nil;               // 1
     IInterface(P)._Release;             // 2
  end;
end;
The line //1 sets the destination pointer to nil, and line //2 releases the interface. _Release
method must call implementation’s destructor when the reference counter reaches 0. Let
us have a look at the compiled code of our testing example:
// load effective address of the first local variable
lea      eax, [ebp - $04]
// in _IntfClear:
// edx := @eax
mov      edx, [eax]
// if (edx = nil) then goto $0e (end);
test     edx, edx
jz       $0e
// eax^ := 0;
mov      [eax], 0
// push original value of eax
push     eax
// push Self parameter
push     edx
// eax := @edx
mov      eax, [edx]
// call _Release.
call     dword ptr [eax + $08]
// restore eax
pop      eax
The most important thing to realize is that after line //3 in the original listing, the
interface is nil and the implementation is destroyed. The danger in this may be more
obvious from this code snippet:
var
  Impl: TSimpleImplementation;
  Intf: ISimpleInterface;
begin
  Impl := TSimpleImplementation.Create;
  Intf := Impl;
  Intf.Test;
  Intf := nil;
  if (Impl <> nil) then Impl.Free; // 1
end;
The danger is on line // 1: after an interface’s reference counter has reached zero,
implementation’s destructor is called; however, the value of the pointer to the instance of
the implementation still remains not nil. Line // 1 will result in a call to a destructor of
already destructed instance, which – in most cases – will cause an access violation.

2.3.3 Implications of automatic implementation destruction
What are the implications of the destruction mechanism? Perhaps the most important one
is that if you want to keep your code easily maintainable you should never have variable
for both implementation and interface.
Another issue is that you have to do some extra coding if you want to use your
implementation alive. Let’s consider this situation: an method of a class returns an
interface, but you do not want to instantiate an implementation class every time a call is
made to the method.
type
  TCls = class
  public
     function GetInterface: ISimpleInterface;
  end;
It is easy to forget the destruction rules and write this code:
type
  TCls = class
  private
     FImpl: TSimpleImplementation;
  public
     constructor Create;
     destructor Destroy; override;
     function GetInterface: ISimpleInterface;
  end;

constructor TCls.Create;
begin
  inherited Create;
  FImpl := TSimpleImplementation.Create;
end;

destructor TCls.Destroy;
begin
  if (FImpl <> nil) then FImp.Free;
  inherited;
end;

function TCls.GetInterface: ISimpleInterface;
begin
  Result := FImpl;
end;
The first error is to use instance of implementation instead of interface. The problems
(access violations, to be more specific) that you will encounter are the result of
misunderstood implementation destruction.
The only instance when this class will function correctly is when GetInterface method is
not called. If GetInterface is called once an error will occur in TCls’s destructor, if it is
called more than once, an error will occur when you try to call ISimpleInterface’s Test
method.
The way out of this mess is to use the correct base implementation class: Delphi’s System
unit provides three base implementation classes – TinterfacedObject, TAggregatedObject
and TContainedObject. These three classes provide thread-safe implementation of
interfaces.
2.3.4 TInterfacedObject
This is the simplest class for interface implementation. The requirement for thread-safe
implementation has interesting implications. First of all, TInterfacedObject has to make
sure that an interface is not released before it is completely constructed. This situation
can easily happen in a multi-threaded application. Consider a case where thread
constructs an instance of interface implementation class to get access to the interface.
Before the instance is fully constructed, thread 2 releases previously acquired interface of
the same type. This will trigger release mechanism and if the situation had not been
thought of this could result in premature release of the constructed interface.
The following code is taken directly from Delphi’s System.pas unit:
procedure TInterfacedObject.AfterConstruction;
begin
// Release the constructor's implicit refcount. Thread-safe increase is
// achieved using Win API call to InterlockedDecrement in place of Dec
  InterlockedDecrement(FRefCount);
end;

procedure TInterfacedObject.BeforeDestruction;
begin
  if RefCount <> 0 then
     Error(reInvalidPtr);
end;

// Set an implicit refcount so that refcounting
// during construction won't destroy the object.
class function TInterfacedObject.NewInstance: TObject;
begin
  Result := inherited NewInstance;
  TInterfacedObject(Result).FRefCount := 1;
end;

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

function TInterfacedObject._AddRef: Integer;
begin
  Result := InterlockedIncrement(FRefCount);
end;

function TInterfacedObject._Release: Integer;
begin
// _Release thread-safely decreases the reference count, and
  Result := InterlockedDecrement(FRefCount);
// if the reference count is 0, frees itself.
if Result = 0 then
     Destroy;
end;
This is the most important code in interface support. It is important to understand the
rules of interface and implementation creation and destruction.
Let’s now move on to other base implementation classes.

2.3.5 TContainedObject and TAggregatedObject
These two classes should be used when using implements syntax on interface property.
Both classes keep a weak reference to the controller that implements the interfaces.
type
  TCls2 = class(T[Contained|Aggregated]Object, ISimpleInterface)
  private
     function GetSimple: ISimpleInterface;
  public
     property Simple: ISimpleInterface read GetSimple
       implements ISimpleInterface;
  end;

function TCls2.GetSimple: ISimpleInterface;
begin
  Result := Controller as ISimpleInterface;
end;

var
  C: TCls2;
begin
  C := TCls2.Create(TSimpleImplementation.Create); // 1
  // Call interface methods
  C.Free;                                                        // 2
end;
Lines // 1 and // 2show differences between TInterfacedObject an TContainedObject.
Firstly, because of implements clause you do not have to implement methods of
ISimpleInterface in TCls2. Instead, TCls2 must provide a property and a selector method
to get a pointer to ISimpleInterface. The implementation of the selector method for the
Simple property gets interface from the controller. An instance of controller is passed as a
parameter of the constructor method.
Perhaps the most important difference between TContainedObject and TInterfacedObject
is the destruction mechanism. You must manually free an instance of TContainedObject.
There is no automatic destructor calling, however, the automatic destructor calls for the
container class are still in place.

2.3.6 TAggregatedObject
TAggregatedObject and TContainedObject are suitable base classes for interfaced objects
intended to be aggregated or contained in an outer controlling object. When using the
"implements" syntax on an interface property in an outer object class declaration, use
these types to implement the inner object.
Interfaces implemented by aggregated objects on behalf of the controller should not be
distinguishable from other interfaces provided by the controller. Aggregated objects
must not maintain their own reference count - they must have the same lifetime as their
controller. To achieve this, aggregated objects reflect the reference count methods to the
controller.
TAggregatedObject simply reflects QueryInterface calls to its controller. From such an
aggregated object, one can obtain any interface that the controller supports, and only
interfaces that the controller supports. This is useful for implementing a controller class
that uses one or more internal objects to implement the interfaces declared on the
controller class. Aggregation promotes implementation sharing across the object
hierarchy.
TAggregatedObject is what most aggregate objects should inherit from, especially when
used in conjunction with the "implements" syntax.
Let TCls2 be descendant of TAggregatedObject: in that case we can write this code:
var
  C: TCls2;
begin
  C := TCls2.Create(TSimpleImplementation.Create);
  C.Simple.Test;                                      // 1
  (C as ISimpleInterface).Test;                       // 2
  C.Free;
end;
The line // 1 is legal; it simply gets a pointer to ISimpleInterface using GetSimple selector
method, which gets the appropriate interface from the controller. Line // 2 is not legal,
because TAggregatedObject can use only the controller to return the appropriate
interface.

2.3.7 TContainedObject
The purpose of TContainedObject is to isolate QueryInterface method on the aggregate
from the controller. Classes derived from this class will only return interfaces that the
class itself implements, not the controller. This class should be used for implementing
interfaces that have the same lifetime as the controller. This design pattern is known as
forced encapsulation.
Let TCls2 be descendant of TContainedObject:
var
  C: TCls2;
begin
  C := TCls2.Create(TSimpleImplementation.Create);
  C.Simple.Test;                                      // 1
  (C as ISimpleInterface).Test;                       // 2
  C.Free;
end;
Unlike the previous case, we can now use both statements C.Simple.Test as well as (C as
ISimpleInterface).Test.
3 Conclusion
Interfaces are very powerful tool for writing flexible and extensible applications. Just like
every powerful tool, they can be very dangerous to use if you do not know what you want
to write and how the compiler is going to interpret the code.
In the next article I will focus on .NET interfaces and Delphi .NET compiler issues.

More Related Content

PDF
Deep C
PPTX
PPTX
c# at f#
PDF
DEF CON 23 - Topher Timzen and Ryan Allen - Hijacking Arbitrary NET App Contr...
PPTX
C programming interview questions
PDF
Handout#05
PDF
88 c-programs
PPTX
C sharp
Deep C
c# at f#
DEF CON 23 - Topher Timzen and Ryan Allen - Hijacking Arbitrary NET App Contr...
C programming interview questions
Handout#05
88 c-programs
C sharp

What's hot (17)

DOC
Basic construction of c
PPTX
Ch2 C Fundamentals
PPT
Advanced Java Topics
PDF
Learn c language Important topics ( Easy & Logical, & smart way of learning)
PDF
Learning the C Language
PPTX
C# in depth
ODP
Ppt of c vs c#
PPTX
C++ programming language basic to advance level
PPTX
C#unit4
PDF
SOFTWARE TOOL FOR TRANSLATING PSEUDOCODE TO A PROGRAMMING LANGUAGE
KEY
What's New In Python 2.4
PDF
Hands-on Introduction to the C Programming Language
ODP
Ppt of c++ vs c#
PDF
Handout#02
PPT
Unit 1 c - all topics
DOCX
Complete c programming presentation
PDF
Basic construction of c
Ch2 C Fundamentals
Advanced Java Topics
Learn c language Important topics ( Easy & Logical, & smart way of learning)
Learning the C Language
C# in depth
Ppt of c vs c#
C++ programming language basic to advance level
C#unit4
SOFTWARE TOOL FOR TRANSLATING PSEUDOCODE TO A PROGRAMMING LANGUAGE
What's New In Python 2.4
Hands-on Introduction to the C Programming Language
Ppt of c++ vs c#
Handout#02
Unit 1 c - all topics
Complete c programming presentation
Ad

Viewers also liked (20)

PDF
PDF
DOCX
REDUCING ARTIFACT ON ELECTROCARDIOGRAPHS.docx
PDF
NYS Landforms
PPTX
All about Ben
PDF
גלאוקומה
PDF
גלאוקומה
PDF
גלאוקומה
DOCX
Sound Sync Annotations
PPTX
Production Log for Foyer
PPT
Prueba Power Point
PPTX
Sims Pictures
PPT
MI PRESENTACION YARE
PDF
Elogio A Giner De Los Rios
PDF
Trading Charts May 17, 2009 Montek For FM, Chidambaram Fine As HM, Says India...
PPT
Celmedia
DOCX
Article
PDF
The Financial Express 19 May 2009 (New Delhi
DOCX
Artwork Annotations
XLS
Wwii Group & Indiv Assmt & Rubrics
REDUCING ARTIFACT ON ELECTROCARDIOGRAPHS.docx
NYS Landforms
All about Ben
גלאוקומה
גלאוקומה
גלאוקומה
Sound Sync Annotations
Production Log for Foyer
Prueba Power Point
Sims Pictures
MI PRESENTACION YARE
Elogio A Giner De Los Rios
Trading Charts May 17, 2009 Montek For FM, Chidambaram Fine As HM, Says India...
Celmedia
Article
The Financial Express 19 May 2009 (New Delhi
Artwork Annotations
Wwii Group & Indiv Assmt & Rubrics
Ad

Similar to delphi-interfaces.pdf (20)

DOC
Delphi qa
PPT
COM Introduction
PDF
D1 from interfaces to solid
PPT
PDF
Joel Landis Net Portfolio
DOCX
C# concepts
PDF
Object Oriented Programming (OOP) using C++ - Lecture 4
PPTX
interface in java explained in detailed form
PPTX
01. design pattern
PDF
Object-oriented Design: Polymorphism via Inheritance (vs. Delegation)
PPTX
Interfaces c#
PDF
Form Follows Function
PDF
Using new-delphi-coding-styles-and-architectures marco-cantu
PDF
CLASSES, STRUCTURE,UNION in C++
PDF
Introduction to c#
PDF
Introduction To Csharp
PPTX
Object Oriented Programming with Object Orinted Concepts
ZIP
147301 nol
Delphi qa
COM Introduction
D1 from interfaces to solid
Joel Landis Net Portfolio
C# concepts
Object Oriented Programming (OOP) using C++ - Lecture 4
interface in java explained in detailed form
01. design pattern
Object-oriented Design: Polymorphism via Inheritance (vs. Delegation)
Interfaces c#
Form Follows Function
Using new-delphi-coding-styles-and-architectures marco-cantu
CLASSES, STRUCTURE,UNION in C++
Introduction to c#
Introduction To Csharp
Object Oriented Programming with Object Orinted Concepts
147301 nol

delphi-interfaces.pdf

  • 1. 1 Delphi interfaces/abstract This article covers interfaces in unmanaged Windows and Linux code. It focuses on practical issues developers have to face when they use interfaces in their code. 2 Why interfaces? Interfaces enable us to write code that is implementation-independent. This is very useful when writing more complex applications and becomes even more important when we decide to split the application into packages. With appropriate use of interfaces we can change the implementation of a class in a package, recompile the package and still use it with the original application. Interfaces also allow us to write more loosely coupled class structures resulting in a more flexible and easily upgradeable application. 2.1 Interface history The first version of Delphi to support interfaces was Delphi 3. But there was a way to use and develop COM interfaces even in Delphi 2. How was that possible? The answer is simple. If you ignore the fact that a class can implement more than one interface, you can think of an interface as a pure abstract class. type IIntf1 = class public function Test; virtual; abstract; end; IIntf2 = interface public function Test; end; Obviously, IIntf1 has many limitations, but this was the way to write COM interfaces in Delphi 2. The reason why the two constructs are comparable is the structure of the Virtual Method Table. You can think of an interface as a VMT definition. Delphi 3 introduced native interface support, making constructs like IIntf1 obsolete. It also added the biggest improvement to Object Pascal: multiple interface implementations. type IIntf1 = interface … end; IIntf2 = interface … end; TImplementation = class(TAncestor, IIntf1, IIntf2) … end; // ! Construct on line // 1 would be illegal if IIntf1 and IIntf2 were declared as abstract classes. 2.2 Interface implementation Now that we have decided to use interfaces in our application we have to overcome a few difficulties in declaring and implementing the application.
  • 2. 2.2.1 GUIDs The most important difference between an abstract class and an interface is that an interface should have a GUID. GUID is a 128bit constant that Delphi uses to uniquely identify an interface. You may have encountered GUIDs in COM, and Delphi uses the same principles as COM to get access to an interface. type ISimpleInterface = interface ['{BCDDF1B6-73CC-406C-912F-7148095F1F4C}'] // 1 end; GUID is shown on the line // 1. As you can see the GUID on line // 1 is not a 128bit integer, it is a string. Delphi compiler, however, recognizes the format of the string and converts it into GUID structure. type TGUID = packed record D1: LongWord; D2: Word; D3: Word; D4: array[0..7] of Byte; end; The same string to 128bit Integer also applies when defining a GUID constant: type IID_ISimpleInterface: TGUID = '{BCDDF1B6-73CC-406C-912F- 7148095F1F4C}'; 2.2.2 Why are GUIDs important? Why does an interface need to be uniquely identifiable? The answer is simple: because Delphi classes can implement multiple interfaces. When an application is running, there has to be a mechanism that will get pointer to an appropriate interface from an implementation. The only way to find out if an object implements an interface and to get a pointer to implementation of that interface is through GUIDs. 2.3 Interface core methods Because an interface is simply a template for the implementation, it cannot control it life. This is why native Delphi (as well as COM) uses reference counting. Reference counting in Delphi is implemented in three fundamental interface-helper classes: TInterfacedObject, TAggregatedObject and TContainedObject. Each of these classes has its specific uses, which will be covered later in this article. What is common for all these classes, however, are the three fundamental interface methods: function _AddRef: Integer; stdcall; function _Release: Integer; stdcall; function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall; Let us start with the simple ones; _AddRef and _Release. As you can probably guess from their names, _AddRef increases a reference counter by one and _Release decreases the counter. The behaviour of _Release depends on the class used in implementation. The pivotal method of interface management is QueryInterface. It takes GUID of an interface
  • 3. to get and returns a pointer to its implementation in Obj. For COM-compatibility, the method returns OLE HResult result values. 2.3.1 QueryInterface, as operator and assignment operator How is QueryInterface related to as and assignment operators? The answer is simple: QueryInterface is used to get a pointer to an interface from the implementing class. Let us consider this code snippet. type TCls = class(TInterfacedObject, IIntf1, IIntf2) protected // implementation of interfaces. end; var C: TCls; I1: Intf1; I2: Intf2; begin C := TCls.Create; I1 := C; // 1 I2 := C; // 2 // call methods of I1 and I2 I1 := nil; I2 := nil; end; The code on lines //1 and //2 is compiled as call to _IntfCast procedure. This procedure calls QueryInterface, which returns a pointer to an interface in an implementation instance. It also releases previous value of destination. procedure _IntfCast(var Dest: IInterface; const Source: IInterface; const IID: TGUID); var Temp: IInterface; begin if Source = nil then Dest := nil else begin Temp := nil; if Source.QueryInterface(IID, Temp) <> 0 then // 1 Error(reIntfCastError) else Dest := Temp; end; end; Exactly the same code will be produced if we use as construct:
  • 4. I1 := Impl as ISimpleInterface; // 1 The as and := operators raise EIntfCastError if QueryInterface returns nil pointer. If you want to avoid using exception handling, use QueryInterface instead: Impl.QueryInterface(IAnotherInterface, A); QueryInterface is one of the pivotal methods of interfaces in Delphi. The other two methods, _AddRef and _Release are used in controlling lifetime of an interface. 2.3.2 Interface creation and destruction An interface is created by calling implementation’s constructor. Then the RTL copies a pointer to the interface from the created implementation instance to the interface variable. You may have already guessed that copying of an interface is firstly a simple pointer assignment and then increase of the reference counter. To increase the reference counter, RTL calls _AddRef method provided by the implementation’s base class. Let us have a look at Delphi pseudo-code for lines //1 to //3: // line1 begin var C: TSimpleImplementation := TSimpleImplementacion.Create; if (C = nil) then Exit; var CVMT := C - VMTOffset; _IntfCopy(Intf, CVMT); end; The code on line 1 constructs an instance of the implementation class, get pointer to its VMT and then call _IntfCopy function. The most important piece of code is _IntfCopy. procedure _IntfCopy(var Dest: IInterface; const Source: IInterface); var OldDest: Pointer; begin OldDest := Dest; // 1 if Source <> nil then Source._AddRef; // 2 Dest := Source; if OldDest <> nil then IInterface(OldDest)._Release; // 3 end; In most cases, the interface assignment means assigning non-nil pointer to existing interface to a nil pointer. If a destination interface is not nil – that means it already references an existing interface – it must be released after successful assignment of the new interface. This is why code on line // 1 copies old destination to a temporary variable. Then procedure then increases reference counter for source. It is important to increase the reference counter before the actual assignment. If the procedure did not do this, another thread might _Release an interface before _IntfCopy could finish executing. This would result in assigning a freed instance, which would result in an Access violation exception. Hence, line // 2 increases reference counter in the source interface before copying its value to the destination. Finally, if Dest was assigned to another interface, the interface is _Released.
  • 5. Once the interface is created, reference counter increased and destination is assigned with the newly created interface, we can safely call its methods. // line 2: begin var ImplVMT = Intf + VMTOffset; (ImplVMT + MethodOffset)(); // 2 end; Bearing in mind that an interface is simply a VMT template a method call must be a call to a method that is looked up in implementation’s VMT. In our simple example, Test is the only virtual method if the implementation, MethodOffset is going to be 0, and VMTOffset is going to be $0c. The actual compiled code looks like this: // set eax to the address of the first local variable mov eax, [ebp - $04] // edx := @eax mov edx, [eax] // call to ((procedure of object)(edx + VMTOffset + MethodOffset))() call dword ptr [edx + $0c] The code actually calls Test method of the implementation class. The code is not too different from the call to a regular virtual method. Line 3 in the original listing is as important as line 1, because it controls destruction of the interface. It is important to remember that – in special cases – when an interface’s reference counter reaches zero, the implementation class is destroyed. The danger is that the pointer to the implementation may remain the same, thus an if-not-nil test for the implementation does no guarantee that an implementation still exists. // line 3: begin _IntfClear(Intf); end; As you can tell, the most important code is hidden in _IntfClear method. This method must _Release the interface, and (if appropriate, free the implementation). function _IntfClear(var Dest: IInterface): Pointer; var P: Pointer; begin Result := @Dest; if Dest <> nil then begin P := Pointer(Dest); Pointer(Dest) := nil; // 1 IInterface(P)._Release; // 2 end; end; The line //1 sets the destination pointer to nil, and line //2 releases the interface. _Release method must call implementation’s destructor when the reference counter reaches 0. Let us have a look at the compiled code of our testing example:
  • 6. // load effective address of the first local variable lea eax, [ebp - $04] // in _IntfClear: // edx := @eax mov edx, [eax] // if (edx = nil) then goto $0e (end); test edx, edx jz $0e // eax^ := 0; mov [eax], 0 // push original value of eax push eax // push Self parameter push edx // eax := @edx mov eax, [edx] // call _Release. call dword ptr [eax + $08] // restore eax pop eax The most important thing to realize is that after line //3 in the original listing, the interface is nil and the implementation is destroyed. The danger in this may be more obvious from this code snippet: var Impl: TSimpleImplementation; Intf: ISimpleInterface; begin Impl := TSimpleImplementation.Create; Intf := Impl; Intf.Test; Intf := nil; if (Impl <> nil) then Impl.Free; // 1 end; The danger is on line // 1: after an interface’s reference counter has reached zero, implementation’s destructor is called; however, the value of the pointer to the instance of the implementation still remains not nil. Line // 1 will result in a call to a destructor of already destructed instance, which – in most cases – will cause an access violation. 2.3.3 Implications of automatic implementation destruction What are the implications of the destruction mechanism? Perhaps the most important one is that if you want to keep your code easily maintainable you should never have variable for both implementation and interface.
  • 7. Another issue is that you have to do some extra coding if you want to use your implementation alive. Let’s consider this situation: an method of a class returns an interface, but you do not want to instantiate an implementation class every time a call is made to the method. type TCls = class public function GetInterface: ISimpleInterface; end; It is easy to forget the destruction rules and write this code: type TCls = class private FImpl: TSimpleImplementation; public constructor Create; destructor Destroy; override; function GetInterface: ISimpleInterface; end; constructor TCls.Create; begin inherited Create; FImpl := TSimpleImplementation.Create; end; destructor TCls.Destroy; begin if (FImpl <> nil) then FImp.Free; inherited; end; function TCls.GetInterface: ISimpleInterface; begin Result := FImpl; end; The first error is to use instance of implementation instead of interface. The problems (access violations, to be more specific) that you will encounter are the result of misunderstood implementation destruction. The only instance when this class will function correctly is when GetInterface method is not called. If GetInterface is called once an error will occur in TCls’s destructor, if it is called more than once, an error will occur when you try to call ISimpleInterface’s Test method. The way out of this mess is to use the correct base implementation class: Delphi’s System unit provides three base implementation classes – TinterfacedObject, TAggregatedObject and TContainedObject. These three classes provide thread-safe implementation of interfaces.
  • 8. 2.3.4 TInterfacedObject This is the simplest class for interface implementation. The requirement for thread-safe implementation has interesting implications. First of all, TInterfacedObject has to make sure that an interface is not released before it is completely constructed. This situation can easily happen in a multi-threaded application. Consider a case where thread constructs an instance of interface implementation class to get access to the interface. Before the instance is fully constructed, thread 2 releases previously acquired interface of the same type. This will trigger release mechanism and if the situation had not been thought of this could result in premature release of the constructed interface. The following code is taken directly from Delphi’s System.pas unit: procedure TInterfacedObject.AfterConstruction; begin // Release the constructor's implicit refcount. Thread-safe increase is // achieved using Win API call to InterlockedDecrement in place of Dec InterlockedDecrement(FRefCount); end; procedure TInterfacedObject.BeforeDestruction; begin if RefCount <> 0 then Error(reInvalidPtr); end; // Set an implicit refcount so that refcounting // during construction won't destroy the object. class function TInterfacedObject.NewInstance: TObject; begin Result := inherited NewInstance; TInterfacedObject(Result).FRefCount := 1; end; function TInterfacedObject.QueryInterface(const IID: TGUID; out Obj): HResult; begin if GetInterface(IID, Obj) then Result := 0 else Result := E_NOINTERFACE; end; function TInterfacedObject._AddRef: Integer; begin Result := InterlockedIncrement(FRefCount); end; function TInterfacedObject._Release: Integer; begin // _Release thread-safely decreases the reference count, and Result := InterlockedDecrement(FRefCount); // if the reference count is 0, frees itself.
  • 9. if Result = 0 then Destroy; end; This is the most important code in interface support. It is important to understand the rules of interface and implementation creation and destruction. Let’s now move on to other base implementation classes. 2.3.5 TContainedObject and TAggregatedObject These two classes should be used when using implements syntax on interface property. Both classes keep a weak reference to the controller that implements the interfaces. type TCls2 = class(T[Contained|Aggregated]Object, ISimpleInterface) private function GetSimple: ISimpleInterface; public property Simple: ISimpleInterface read GetSimple implements ISimpleInterface; end; function TCls2.GetSimple: ISimpleInterface; begin Result := Controller as ISimpleInterface; end; var C: TCls2; begin C := TCls2.Create(TSimpleImplementation.Create); // 1 // Call interface methods C.Free; // 2 end; Lines // 1 and // 2show differences between TInterfacedObject an TContainedObject. Firstly, because of implements clause you do not have to implement methods of ISimpleInterface in TCls2. Instead, TCls2 must provide a property and a selector method to get a pointer to ISimpleInterface. The implementation of the selector method for the Simple property gets interface from the controller. An instance of controller is passed as a parameter of the constructor method. Perhaps the most important difference between TContainedObject and TInterfacedObject is the destruction mechanism. You must manually free an instance of TContainedObject. There is no automatic destructor calling, however, the automatic destructor calls for the container class are still in place. 2.3.6 TAggregatedObject TAggregatedObject and TContainedObject are suitable base classes for interfaced objects intended to be aggregated or contained in an outer controlling object. When using the "implements" syntax on an interface property in an outer object class declaration, use these types to implement the inner object.
  • 10. Interfaces implemented by aggregated objects on behalf of the controller should not be distinguishable from other interfaces provided by the controller. Aggregated objects must not maintain their own reference count - they must have the same lifetime as their controller. To achieve this, aggregated objects reflect the reference count methods to the controller. TAggregatedObject simply reflects QueryInterface calls to its controller. From such an aggregated object, one can obtain any interface that the controller supports, and only interfaces that the controller supports. This is useful for implementing a controller class that uses one or more internal objects to implement the interfaces declared on the controller class. Aggregation promotes implementation sharing across the object hierarchy. TAggregatedObject is what most aggregate objects should inherit from, especially when used in conjunction with the "implements" syntax. Let TCls2 be descendant of TAggregatedObject: in that case we can write this code: var C: TCls2; begin C := TCls2.Create(TSimpleImplementation.Create); C.Simple.Test; // 1 (C as ISimpleInterface).Test; // 2 C.Free; end; The line // 1 is legal; it simply gets a pointer to ISimpleInterface using GetSimple selector method, which gets the appropriate interface from the controller. Line // 2 is not legal, because TAggregatedObject can use only the controller to return the appropriate interface. 2.3.7 TContainedObject The purpose of TContainedObject is to isolate QueryInterface method on the aggregate from the controller. Classes derived from this class will only return interfaces that the class itself implements, not the controller. This class should be used for implementing interfaces that have the same lifetime as the controller. This design pattern is known as forced encapsulation. Let TCls2 be descendant of TContainedObject: var C: TCls2; begin C := TCls2.Create(TSimpleImplementation.Create); C.Simple.Test; // 1 (C as ISimpleInterface).Test; // 2 C.Free; end; Unlike the previous case, we can now use both statements C.Simple.Test as well as (C as ISimpleInterface).Test.
  • 11. 3 Conclusion Interfaces are very powerful tool for writing flexible and extensible applications. Just like every powerful tool, they can be very dangerous to use if you do not know what you want to write and how the compiler is going to interpret the code. In the next article I will focus on .NET interfaces and Delphi .NET compiler issues.