1. Technology
Send to a Friend via Email
An Introduction to COM Programming with Delphi (2 / 6)
What is an Interface? How to implement an Interface? Describing the TInterfacedObject.
 More Delphi COM Lessons
TOC
• Lesson 1
• Lesson 3
• Lesson 4
Lesson 5
Lesson 6
 Join the Discussion
"Post your views, comments, questions and doubts to this article."
Discuss!
 Related Resources
• COM / OLE / ActiveX programming with Delphi
 Download Source Code
• COMCodeExample2

Article written by Curtis Socha, brought to you by Zarko Gajic.

So, what is an Interface?

An interface is the heart of COM technology. When I use the word interface, do not confuse it with the visual display that the user interacts with. It is actually a fairly new reserved word incorporated into Delphi. An Interface is little more than group of abstract methods used to manipulate an object.

We established during the last lesson that an interface is not a class; it is an interface. Therefore, we must declare it as such. Let us explore some interface characteristics.

  • An interface is defined as type interface
  • You are to use the letter I to define your interface. I.E. "IMyInterface = interface"
  • An interface inherits from IUnknown
  • You cannot create an instance of an interface like you can a class that contains abstract methods
  • An interface cannot contain variables. (Read: It can contain properties as I will show you later on)
  • All functions and procedures declared in an interface are public virtual abstract methods. Even though you do not declare them this way, it is understood that is what they are.
  • Interfaces are reference counted
Now that we know more about what an interface can and cannot do, let us look at what it is like to declare an interface.

IMyInterface = interface
        ['{676C8DA0-D8B3-11D4-BDE0-00A024BAF736}']
  procedure SomeUnimplementedMethod;
end;

On GUIDs
I am sure you are wondering what the big string of crazy characters are. It is called a GUID and it stands for Globally Unique Identifier. Some people pronounce it as "gwid" or as "Gooey I.D.". Here some characteristics of a GUID:

  • Your interface will have a GUID unlike any other in the world
  • All interfaces and COM interfaces will have a GUID attached to it
  • You can create a GUID in Delphi by pressing CTRL-SHIFT-G
  • You are never to copy a GUID from one interface into another

Okay, but how do I implement an interface?

That is a good question. The answer is: You cannot implement an interface directly. You must create a class that will implement your interface. Refer to the coding example shown below.

unit InterfaceUnit;

interface

uses
  Windows, Messages, SysUtils, Classes, 
  Graphics, Controls, Forms, Dialogs, StdCtrls;

type
  IMyInterface = interface
      ['{6675C5C0-D95C-11D4-BDE0-00A024BAF736}']
    procedure DisplaySomething;
  end;

  TMyClass = class(TInterfacedObject, IMyInterface)
    procedure DisplaySomething;
  end;

  TMyForm = class(TForm)
    TestBtn: TButton;
    procedure TestBtnClick(Sender: TObject);
  end;

var
  MyForm: TMyForm;

implementation

{$R *.DFM}
procedure TMyForm.TestBtnClick(Sender: TObject);
var
  //Declare a TMyClass object
  MyClass : TMyClass;             
  //Declare a IMyInterface Object
  MyInterface : IMyInterface;     
begin
  //Create an interface pointing
  //to a TMyClass object
  MyInterface := TMyClass.Create; 

  //Call TMyClass'es DisplaySomething 
  //using our interface
  MyInterface.DisplaySomething;   
end;

{ TMyClass }
procedure TMyClass.DisplaySomething;
begin
  ShowMessage('MyInterface was created and 
               will now fall out of scope!');
end;

end.

The coding example listed above is about as simple as it can get while still having some type of visual functionality. Let's take a closer look at procedure TextBtnClick. Is there something missing? If you haven’t notice, we are not explicitly freeing the interface. This would appear to be a memory leak, but appearances are deceiving. As soon as the interface goes out of scope, Delphi will actually free the interface for you automatically! Interface's declared within a procedure or function will naturally fall out of scope when the procedure ends. Interface's declared within a class or are declared globally will naturally fall out of scope when the object is freed or the program ends.

Now we need to discuss Reference Counting. Every time you retrieve an interface to an object, e.g. IMyInterface := MyClass; Delphi will automatically increment its reference count using IUnknown's _AddRef function. When an interface falls out of scope, Delphi automatically calls IUnknown's ._Release function. If you haven't figured it out yet, IUnkown is an interface as well, and since we already know that an interface cannot implement the methods it defines, then you may be wondering where AddRef and Release come from. The answer is TInterfacedObject!

What in the world is TInterfacedObject?

TInterfacedObject and IUnknown are defined in system.pas. Before I can elaborate on this, we need to at least take a look at the code:

SYSTEM.PAS 
  Declaration of IUnkown
  Declaration of TInterfacedObject and its implementation

  IUnknown = interface
    ['{00000000-0000-0000-C000-000000000046}']
    function QueryInterface
      (const IID: TGUID; out Obj): HResult; stdcall;
    function _AddRef: Integer; stdcall;
    function _Release: Integer; stdcall;
  end;

  TInterfacedObject = class(TObject, IUnknown)
  protected
    FRefCount: Integer;
    function QueryInterface
      (const IID: TGUID; out Obj): HResult; stdcall;
    function _AddRef: Integer; stdcall;
    function _Release: Integer; stdcall;
  public
    procedure BeforeDestruction; override;
    property RefCount: Integer read FRefCount;
  end;
 
procedure TInterfacedObject.BeforeDestruction;
begin
  if RefCount <> 0 then Error(reInvalidPtr);
end;

function TInterfacedObject.QueryInterface
  (const IID: TGUID; out Obj): HResult;
const
  E_NOINTERFACE = $80004002;
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
  Result := InterlockedDecrement(FRefCount);
  if Result = 0 then Destroy;
end;

The code may look a little scary, but we are going to walk through this step-by-step since it is crucial that you understand reference counting in order to effectively program in COM. It is obvious from the code above that TInterfacedObject implements the methods defined in IUnkown. When you create a class that uses TInterfacedObject, you are essentially telling that class that it is going to be reference counted if an interface is assigned to it.

Let us look at simple piece of code so that I can describe the process that takes place.

TMyInterfaceClass = class(TInterfacedObject, IMyInterface)
 {blah blah blah}
 {blah blah blah}
procedure DoThis;
var
  MyClass : TMyInterfaceClass;
  MyInterface : IMyInterface;
begin
  MyClass := TMyInterfaceClass.Create;
  MyInterface := MyClass
end;

When you use Direct Assignment to assign MyInterface to MyClass, the method _AddRef gets called automatically by Delphi. This says to MyClass, "Hey, an interface is referencing you! Were going to increment your reference count." If you were to change the aforementioned code to the following:

TMyInterfaceClass = class(TInterfacedObject, IMyInterface)
  {blah blah blah}
  {blah blah blah}
procedure DoThis;
var
  MyClass : TMyInterfaceClass;
  MyInterface : IMyInterface;
  MyInterface2: IMyInterface;
begin
  MyClass := TMyInterfaceClass.Create;
  MyInterface := MyClass;
  MyInterface2 := MyClass
end;

Notice that I added a new variable called MyInterface2. Since we are now setting 2 different interfaces equal to MyClass, guess what happens to the Myclass’s reference count? It becomes 2 because we now have two interfaces assigned to it!

You may be wondering about the _Release method that is implemented by TInterfacedObject. The _Release method automatically gets called for each interface assigned to a class that falls out of scope. Obviously, at the end of procedure DoThis, the program execution is going to return whence it came, therefore, your local class and interface declarations fall out of the scope in which they were declared. Since we have two interfaces defined, _Release will get called how many times? The answer is a bright shiny "2"! And once our reference count hits zero, then MyClass is automatically freed by Delphi. How nice and clean!

Homework Assignment

In this homework assignment, you will need to review the following source code: COMCodeExample2

Read through the code and the comments. I have provided the four methods you can use to retrieve an interface to an object. These four methods are necessary to fully understanding how interfaces work. The comments tell you exactly what, when, and how.

After you have finished reading through it, I want you to pick TWO of the methods shown in the code, (except for Direct Assignment which we discuss earlier), and build a program that uses both types of assignment. What the program does is not important to me. What is important is that you should play with interfaces because once we get to TCOMObjects, the real fun begins. We will not be playing with true COM objects for yet another week. Next week, we will go over the implements directive, method resolution, pseudo-multiple inheritance, and interface properties. We have a LOT to learn so I am going to be slamming you with information that you will need to digest. Make sure you do your homework.

Good luck.

An Introduction to COM Programming with Delphi: Table of Content
<< Previous Lesson (1): A brief historical rundown on COM's glorious past. Abstract methods vs. Interfaces. Classes and Interfaces: An interesting paradox.
>> Next Lesson (3): What is the implements directive? What is the Method Resolution Clauses? Pseudo-Multiple Interface Inheritance. Interface properties and other fine tales of horror.

 

©2014 About.com. All rights reserved.