Interface in Delphi - ablealias/Delphi GitHub Wiki

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. 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 by default
  • You cannot create an instance of an interface like you can a class that contains abstract methods
  • An interface cannot contain variables.
  • All functions and procedures declared in an interface are public virtual abstract methods by default.
  • Interfaces are reference counted let us look at what it is like to declare an interface,
IMyInterface = interface
        ['{676C8DA0-D8B3-11D4-BDE0-00A024BAF736}']
  procedure SomeUnimplementedMethod;
end;

GUID stands for Globally Unique Identifier. 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

how do I implement an interface?

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.

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.

What is 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 is TInterfacedObject?

Here is the implementation of TInterfacedObject class,

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;

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."

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!

What is implements directive?

Implements directive allows you to implement your methods using a separate object instantiated within the class itself. Better way to explain this is shown in the flow chart shown below:

Notice that TMyClass never actually implements the procedure Something, even though IMyInterface has been assigned to it. What we are doing is saying to Delphi: "Hey, I'm not going to personally implement this procedure, but I know of a class that does. TMyImplementClass will implement this procedure for me!"

TMyImplementClass is literally defined as just "class", not “class(TInterfacedObject, IMyInterface)”. The benefit of using this type of structure is that you can centralize all of your interfaces and the classes that implement the interface methods into a single class that contains only properties. This will allow you to keep your classes separate rather than having a giant class that implements the methods of a couple interfaces. The source code can become unwieldy and very annoying when you want only part of the class functionality, but you are forced to instantiate the entire class for just a few of it's methods.