SlideShare una empresa de Scribd logo
1 de 11
Descargar para leer sin conexión
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.

Más contenido relacionado

La actualidad más candente

Basic construction of c
Basic construction of cBasic construction of c
Basic construction of ckinish kumar
 
Ch2 C Fundamentals
Ch2 C FundamentalsCh2 C Fundamentals
Ch2 C FundamentalsSzeChingChen
 
Learn c language Important topics ( Easy & Logical, & smart way of learning)
Learn c language Important topics ( Easy & Logical, & smart way of learning)Learn c language Important topics ( Easy & Logical, & smart way of learning)
Learn c language Important topics ( Easy & Logical, & smart way of learning)Rohit Singh
 
C++ programming language basic to advance level
C++ programming language basic to advance levelC++ programming language basic to advance level
C++ programming language basic to advance levelsajjad ali khan
 
SOFTWARE TOOL FOR TRANSLATING PSEUDOCODE TO A PROGRAMMING LANGUAGE
SOFTWARE TOOL FOR TRANSLATING PSEUDOCODE TO A PROGRAMMING LANGUAGESOFTWARE TOOL FOR TRANSLATING PSEUDOCODE TO A PROGRAMMING LANGUAGE
SOFTWARE TOOL FOR TRANSLATING PSEUDOCODE TO A PROGRAMMING LANGUAGEIJCI JOURNAL
 
What's New In Python 2.4
What's New In Python 2.4What's New In Python 2.4
What's New In Python 2.4Richard Jones
 
Hands-on Introduction to the C Programming Language
Hands-on Introduction to the C Programming LanguageHands-on Introduction to the C Programming Language
Hands-on Introduction to the C Programming LanguageVincenzo De Florio
 
Unit 1 c - all topics
Unit 1   c - all topicsUnit 1   c - all topics
Unit 1 c - all topicsveningstonk
 
Complete c programming presentation
Complete c programming presentationComplete c programming presentation
Complete c programming presentationnadim akber
 

La actualidad más candente (17)

Basic construction of c
Basic construction of cBasic construction of c
Basic construction of c
 
Ch2 C Fundamentals
Ch2 C FundamentalsCh2 C Fundamentals
Ch2 C Fundamentals
 
Learn c language Important topics ( Easy & Logical, & smart way of learning)
Learn c language Important topics ( Easy & Logical, & smart way of learning)Learn c language Important topics ( Easy & Logical, & smart way of learning)
Learn c language Important topics ( Easy & Logical, & smart way of learning)
 
Advanced Java Topics
Advanced Java TopicsAdvanced Java Topics
Advanced Java Topics
 
Learning the C Language
Learning the C LanguageLearning the C Language
Learning the C Language
 
C# in depth
C# in depthC# in depth
C# in depth
 
Ppt of c vs c#
Ppt of c vs c#Ppt of c vs c#
Ppt of c vs c#
 
C++ programming language basic to advance level
C++ programming language basic to advance levelC++ programming language basic to advance level
C++ programming language basic to advance level
 
C#unit4
C#unit4C#unit4
C#unit4
 
SOFTWARE TOOL FOR TRANSLATING PSEUDOCODE TO A PROGRAMMING LANGUAGE
SOFTWARE TOOL FOR TRANSLATING PSEUDOCODE TO A PROGRAMMING LANGUAGESOFTWARE TOOL FOR TRANSLATING PSEUDOCODE TO A PROGRAMMING LANGUAGE
SOFTWARE TOOL FOR TRANSLATING PSEUDOCODE TO A PROGRAMMING LANGUAGE
 
What's New In Python 2.4
What's New In Python 2.4What's New In Python 2.4
What's New In Python 2.4
 
Hands-on Introduction to the C Programming Language
Hands-on Introduction to the C Programming LanguageHands-on Introduction to the C Programming Language
Hands-on Introduction to the C Programming Language
 
Ppt of c++ vs c#
Ppt of c++ vs c#Ppt of c++ vs c#
Ppt of c++ vs c#
 
Handout#02
Handout#02Handout#02
Handout#02
 
Unit 1 c - all topics
Unit 1   c - all topicsUnit 1   c - all topics
Unit 1 c - all topics
 
Complete c programming presentation
Complete c programming presentationComplete c programming presentation
Complete c programming presentation
 
3.2
3.23.2
3.2
 

Similar a delphi-interfaces.pdf

embeddedc-lecture1-160404055102.pptx
embeddedc-lecture1-160404055102.pptxembeddedc-lecture1-160404055102.pptx
embeddedc-lecture1-160404055102.pptxsangeetaSS
 
Algorithm and c language
Algorithm and c languageAlgorithm and c language
Algorithm and c languagekamalbeydoun
 
Maxbox starter19
Maxbox starter19Maxbox starter19
Maxbox starter19Max Kleiner
 
INTRODUCTION TO C PROGRAMMING MATERIAL.pdf
INTRODUCTION TO C PROGRAMMING MATERIAL.pdfINTRODUCTION TO C PROGRAMMING MATERIAL.pdf
INTRODUCTION TO C PROGRAMMING MATERIAL.pdfSubramanyambharathis
 
C++ by shantu
C++ by shantuC++ by shantu
C++ by shantuShant007
 
Inline function
Inline functionInline function
Inline functionTech_MX
 
1588147798Begining_ABUAD1.pdf
1588147798Begining_ABUAD1.pdf1588147798Begining_ABUAD1.pdf
1588147798Begining_ABUAD1.pdfSemsemSameer1
 
Composing an App with Free Monads (using Cats)
Composing an App with Free Monads (using Cats)Composing an App with Free Monads (using Cats)
Composing an App with Free Monads (using Cats)Hermann Hueck
 
Lecture-1&2.pdf Visual Programming C# .net framework
Lecture-1&2.pdf Visual Programming C# .net frameworkLecture-1&2.pdf Visual Programming C# .net framework
Lecture-1&2.pdf Visual Programming C# .net frameworkAbdullahNadeem78
 
TCP Sockets Tutor maXbox starter26
TCP Sockets Tutor maXbox starter26TCP Sockets Tutor maXbox starter26
TCP Sockets Tutor maXbox starter26Max Kleiner
 
Arduino LED maXbox starter18_3
Arduino LED maXbox starter18_3Arduino LED maXbox starter18_3
Arduino LED maXbox starter18_3Max Kleiner
 

Similar a delphi-interfaces.pdf (20)

Embedded C - Lecture 1
Embedded C - Lecture 1Embedded C - Lecture 1
Embedded C - Lecture 1
 
embeddedc-lecture1-160404055102.pptx
embeddedc-lecture1-160404055102.pptxembeddedc-lecture1-160404055102.pptx
embeddedc-lecture1-160404055102.pptx
 
C tutorial
C tutorialC tutorial
C tutorial
 
Compiler presentaion
Compiler presentaionCompiler presentaion
Compiler presentaion
 
Algorithm and c language
Algorithm and c languageAlgorithm and c language
Algorithm and c language
 
Maxbox starter19
Maxbox starter19Maxbox starter19
Maxbox starter19
 
INTRODUCTION TO C PROGRAMMING MATERIAL.pdf
INTRODUCTION TO C PROGRAMMING MATERIAL.pdfINTRODUCTION TO C PROGRAMMING MATERIAL.pdf
INTRODUCTION TO C PROGRAMMING MATERIAL.pdf
 
C++ by shantu
C++ by shantuC++ by shantu
C++ by shantu
 
.NET for hackers
.NET for hackers.NET for hackers
.NET for hackers
 
C Course Material0209
C Course Material0209C Course Material0209
C Course Material0209
 
C tutorial
C tutorialC tutorial
C tutorial
 
Inline function
Inline functionInline function
Inline function
 
COM Introduction
COM IntroductionCOM Introduction
COM Introduction
 
1588147798Begining_ABUAD1.pdf
1588147798Begining_ABUAD1.pdf1588147798Begining_ABUAD1.pdf
1588147798Begining_ABUAD1.pdf
 
Csharp
CsharpCsharp
Csharp
 
Composing an App with Free Monads (using Cats)
Composing an App with Free Monads (using Cats)Composing an App with Free Monads (using Cats)
Composing an App with Free Monads (using Cats)
 
Lecture-1&2.pdf Visual Programming C# .net framework
Lecture-1&2.pdf Visual Programming C# .net frameworkLecture-1&2.pdf Visual Programming C# .net framework
Lecture-1&2.pdf Visual Programming C# .net framework
 
TCP Sockets Tutor maXbox starter26
TCP Sockets Tutor maXbox starter26TCP Sockets Tutor maXbox starter26
TCP Sockets Tutor maXbox starter26
 
Arduino LED maXbox starter18_3
Arduino LED maXbox starter18_3Arduino LED maXbox starter18_3
Arduino LED maXbox starter18_3
 
Report_Ines_Swayam
Report_Ines_SwayamReport_Ines_Swayam
Report_Ines_Swayam
 

Último

How AI, OpenAI, and ChatGPT impact business and software.
How AI, OpenAI, and ChatGPT impact business and software.How AI, OpenAI, and ChatGPT impact business and software.
How AI, OpenAI, and ChatGPT impact business and software.Curtis Poe
 
"ML in Production",Oleksandr Bagan
"ML in Production",Oleksandr Bagan"ML in Production",Oleksandr Bagan
"ML in Production",Oleksandr BaganFwdays
 
Ensuring Technical Readiness For Copilot in Microsoft 365
Ensuring Technical Readiness For Copilot in Microsoft 365Ensuring Technical Readiness For Copilot in Microsoft 365
Ensuring Technical Readiness For Copilot in Microsoft 3652toLead Limited
 
Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)
Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)
Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)Mark Simos
 
Anypoint Exchange: It’s Not Just a Repo!
Anypoint Exchange: It’s Not Just a Repo!Anypoint Exchange: It’s Not Just a Repo!
Anypoint Exchange: It’s Not Just a Repo!Manik S Magar
 
Artificial intelligence in cctv survelliance.pptx
Artificial intelligence in cctv survelliance.pptxArtificial intelligence in cctv survelliance.pptx
Artificial intelligence in cctv survelliance.pptxhariprasad279825
 
Leverage Zilliz Serverless - Up to 50X Saving for Your Vector Storage Cost
Leverage Zilliz Serverless - Up to 50X Saving for Your Vector Storage CostLeverage Zilliz Serverless - Up to 50X Saving for Your Vector Storage Cost
Leverage Zilliz Serverless - Up to 50X Saving for Your Vector Storage CostZilliz
 
Powerpoint exploring the locations used in television show Time Clash
Powerpoint exploring the locations used in television show Time ClashPowerpoint exploring the locations used in television show Time Clash
Powerpoint exploring the locations used in television show Time Clashcharlottematthew16
 
H2O.ai CEO/Founder: Sri Ambati Keynote at Wells Fargo Day
H2O.ai CEO/Founder: Sri Ambati Keynote at Wells Fargo DayH2O.ai CEO/Founder: Sri Ambati Keynote at Wells Fargo Day
H2O.ai CEO/Founder: Sri Ambati Keynote at Wells Fargo DaySri Ambati
 
SIP trunking in Janus @ Kamailio World 2024
SIP trunking in Janus @ Kamailio World 2024SIP trunking in Janus @ Kamailio World 2024
SIP trunking in Janus @ Kamailio World 2024Lorenzo Miniero
 
Gen AI in Business - Global Trends Report 2024.pdf
Gen AI in Business - Global Trends Report 2024.pdfGen AI in Business - Global Trends Report 2024.pdf
Gen AI in Business - Global Trends Report 2024.pdfAddepto
 
CloudStudio User manual (basic edition):
CloudStudio User manual (basic edition):CloudStudio User manual (basic edition):
CloudStudio User manual (basic edition):comworks
 
WordPress Websites for Engineers: Elevate Your Brand
WordPress Websites for Engineers: Elevate Your BrandWordPress Websites for Engineers: Elevate Your Brand
WordPress Websites for Engineers: Elevate Your Brandgvaughan
 
"Debugging python applications inside k8s environment", Andrii Soldatenko
"Debugging python applications inside k8s environment", Andrii Soldatenko"Debugging python applications inside k8s environment", Andrii Soldatenko
"Debugging python applications inside k8s environment", Andrii SoldatenkoFwdays
 
Unleash Your Potential - Namagunga Girls Coding Club
Unleash Your Potential - Namagunga Girls Coding ClubUnleash Your Potential - Namagunga Girls Coding Club
Unleash Your Potential - Namagunga Girls Coding ClubKalema Edgar
 
Merck Moving Beyond Passwords: FIDO Paris Seminar.pptx
Merck Moving Beyond Passwords: FIDO Paris Seminar.pptxMerck Moving Beyond Passwords: FIDO Paris Seminar.pptx
Merck Moving Beyond Passwords: FIDO Paris Seminar.pptxLoriGlavin3
 
TrustArc Webinar - How to Build Consumer Trust Through Data Privacy
TrustArc Webinar - How to Build Consumer Trust Through Data PrivacyTrustArc Webinar - How to Build Consumer Trust Through Data Privacy
TrustArc Webinar - How to Build Consumer Trust Through Data PrivacyTrustArc
 
Advanced Test Driven-Development @ php[tek] 2024
Advanced Test Driven-Development @ php[tek] 2024Advanced Test Driven-Development @ php[tek] 2024
Advanced Test Driven-Development @ php[tek] 2024Scott Keck-Warren
 
Developer Data Modeling Mistakes: From Postgres to NoSQL
Developer Data Modeling Mistakes: From Postgres to NoSQLDeveloper Data Modeling Mistakes: From Postgres to NoSQL
Developer Data Modeling Mistakes: From Postgres to NoSQLScyllaDB
 

Último (20)

DMCC Future of Trade Web3 - Special Edition
DMCC Future of Trade Web3 - Special EditionDMCC Future of Trade Web3 - Special Edition
DMCC Future of Trade Web3 - Special Edition
 
How AI, OpenAI, and ChatGPT impact business and software.
How AI, OpenAI, and ChatGPT impact business and software.How AI, OpenAI, and ChatGPT impact business and software.
How AI, OpenAI, and ChatGPT impact business and software.
 
"ML in Production",Oleksandr Bagan
"ML in Production",Oleksandr Bagan"ML in Production",Oleksandr Bagan
"ML in Production",Oleksandr Bagan
 
Ensuring Technical Readiness For Copilot in Microsoft 365
Ensuring Technical Readiness For Copilot in Microsoft 365Ensuring Technical Readiness For Copilot in Microsoft 365
Ensuring Technical Readiness For Copilot in Microsoft 365
 
Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)
Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)
Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)
 
Anypoint Exchange: It’s Not Just a Repo!
Anypoint Exchange: It’s Not Just a Repo!Anypoint Exchange: It’s Not Just a Repo!
Anypoint Exchange: It’s Not Just a Repo!
 
Artificial intelligence in cctv survelliance.pptx
Artificial intelligence in cctv survelliance.pptxArtificial intelligence in cctv survelliance.pptx
Artificial intelligence in cctv survelliance.pptx
 
Leverage Zilliz Serverless - Up to 50X Saving for Your Vector Storage Cost
Leverage Zilliz Serverless - Up to 50X Saving for Your Vector Storage CostLeverage Zilliz Serverless - Up to 50X Saving for Your Vector Storage Cost
Leverage Zilliz Serverless - Up to 50X Saving for Your Vector Storage Cost
 
Powerpoint exploring the locations used in television show Time Clash
Powerpoint exploring the locations used in television show Time ClashPowerpoint exploring the locations used in television show Time Clash
Powerpoint exploring the locations used in television show Time Clash
 
H2O.ai CEO/Founder: Sri Ambati Keynote at Wells Fargo Day
H2O.ai CEO/Founder: Sri Ambati Keynote at Wells Fargo DayH2O.ai CEO/Founder: Sri Ambati Keynote at Wells Fargo Day
H2O.ai CEO/Founder: Sri Ambati Keynote at Wells Fargo Day
 
SIP trunking in Janus @ Kamailio World 2024
SIP trunking in Janus @ Kamailio World 2024SIP trunking in Janus @ Kamailio World 2024
SIP trunking in Janus @ Kamailio World 2024
 
Gen AI in Business - Global Trends Report 2024.pdf
Gen AI in Business - Global Trends Report 2024.pdfGen AI in Business - Global Trends Report 2024.pdf
Gen AI in Business - Global Trends Report 2024.pdf
 
CloudStudio User manual (basic edition):
CloudStudio User manual (basic edition):CloudStudio User manual (basic edition):
CloudStudio User manual (basic edition):
 
WordPress Websites for Engineers: Elevate Your Brand
WordPress Websites for Engineers: Elevate Your BrandWordPress Websites for Engineers: Elevate Your Brand
WordPress Websites for Engineers: Elevate Your Brand
 
"Debugging python applications inside k8s environment", Andrii Soldatenko
"Debugging python applications inside k8s environment", Andrii Soldatenko"Debugging python applications inside k8s environment", Andrii Soldatenko
"Debugging python applications inside k8s environment", Andrii Soldatenko
 
Unleash Your Potential - Namagunga Girls Coding Club
Unleash Your Potential - Namagunga Girls Coding ClubUnleash Your Potential - Namagunga Girls Coding Club
Unleash Your Potential - Namagunga Girls Coding Club
 
Merck Moving Beyond Passwords: FIDO Paris Seminar.pptx
Merck Moving Beyond Passwords: FIDO Paris Seminar.pptxMerck Moving Beyond Passwords: FIDO Paris Seminar.pptx
Merck Moving Beyond Passwords: FIDO Paris Seminar.pptx
 
TrustArc Webinar - How to Build Consumer Trust Through Data Privacy
TrustArc Webinar - How to Build Consumer Trust Through Data PrivacyTrustArc Webinar - How to Build Consumer Trust Through Data Privacy
TrustArc Webinar - How to Build Consumer Trust Through Data Privacy
 
Advanced Test Driven-Development @ php[tek] 2024
Advanced Test Driven-Development @ php[tek] 2024Advanced Test Driven-Development @ php[tek] 2024
Advanced Test Driven-Development @ php[tek] 2024
 
Developer Data Modeling Mistakes: From Postgres to NoSQL
Developer Data Modeling Mistakes: From Postgres to NoSQLDeveloper Data Modeling Mistakes: From Postgres to NoSQL
Developer Data Modeling Mistakes: From Postgres to NoSQL
 

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.