Interfaces without objects

17

Delphi interfaces are usually implemented by objects, but that is not necessary. Like you can implement a COM interface in pure C, you can implement a Delphi interface (which follows COM specifications in general) in pure Pascal without using objects.

To demonstrate the technique let us consider the following example:

unit CalcIntf;

interface

type
  ICalculator = interface
    procedure Clear;
    procedure Add(AValue: Integer);
    procedure Sub(AValue: Integer);
    function GetAccumulator: Integer;
  end;

implementation

end.

Here is a standard Delphi implementation of ICalculator interface by TInterfacedObject descendant:

unit CalcObjects;

interface

uses CalcIntf;

function GetCalculator: ICalculator;

implementation

type
  TCalcObj = class(TInterfacedObject, ICalculator)
    FAccumulator: Integer;
    procedure Clear;
    procedure Add(AValue: Integer);
    procedure Sub(AValue: Integer);
    function GetAccumulator: Integer;
  end;

procedure TCalcObj.Clear;
begin
  FAccumulator:= 0;
end;

function TCalcObj.GetAccumulator: Integer;
begin
  Result:= FAccumulator;
end;

procedure TCalcObj.Add(AValue: Integer);
begin
  Inc(FAccumulator, AValue);
end;

procedure TCalcObj.Sub(AValue: Integer);
begin
  Dec(FAccumulator, AValue);
end;

function GetCalculator: ICalculator;
begin
  Result:= TCalcObj.Create;
end;

end.

Our task is to implement ICalculator interface using a record instance instead of an object instance. I have described the internals of Delphi interfaces before. In the above example the GetCalculator function returns a pointer to the hidden vtable field of TCalcObj instance. If we use a record instead of an object we should declare this field explicitely; if we declare this field first we also get rid of stub code that converts a pointer to vtable field of an instance to a pointer to an instance itself and make interface functions’ calls faster:

type
  PCalcData = ^TCalcData;
  TCalcData = record
    FVTable: Pointer;
    FRefCount: Integer;
    FAccumulator: Integer;
  end;

Now the GetCalculator function should return a pointer to FVTable field of a record; since FVTable field is the first field of the TCalcData record, it is a pointer to record instance itself. A working solution is presented below; I used static class methods of an advanced record instead of ordinary functions only to scope the functions’ names:

unit CalcRecords;

interface

uses CalcIntf;

function GetCalculator: ICalculator;

implementation

type
  PCalcData = ^TCalcData;
  TCalcData = record
    FVTable: Pointer;
    FRefCount: Integer;
    FAccumulator: Integer;
  end;

  TCalcRec = record
    Data: PCalcData;
    class function QueryIntf(Inst: PCalcData; const IID: TGUID;
                             out Obj): HResult; stdcall; static;
    class function Addref(Inst: PCalcData): Integer; stdcall; static;
    class function Release(Inst: PCalcData): Integer; stdcall; static;
    class procedure Clear(Inst: PCalcData); static;
    class procedure Add(Inst: PCalcData; AValue: Integer); static;
    class procedure Sub(Inst: PCalcData; AValue: Integer); static;
    class function GetAccumulator(Inst: PCalcData): Integer; static;
  end;

function InterlockedAdd(var Value: Integer; Increment: Integer): Integer;
asm
      MOV   ECX,EAX
      MOV   EAX,EDX
 LOCK XADD  [ECX],EAX
      ADD   EAX,EDX
end;

function InterlockedIncrement(var Value: Integer): Integer;
asm
      MOV   EDX,1
      JMP   InterlockedAdd
end;

function InterlockedDecrement(var Value: Integer): Integer;
asm
      MOV   EDX,-1
      JMP   InterlockedAdd
end;

class function TCalcRec.QueryIntf(Inst: PCalcData; const IID: TGUID; out Obj): HResult;
begin
  Result:= E_NOINTERFACE;
end;

class function TCalcRec.Addref(Inst: PCalcData): Integer;
begin
  Result:= InterlockedIncrement(Inst^.FRefCount);
end;

class function TCalcRec.Release(Inst: PCalcData): Integer;
begin
  Result:= InterlockedDecrement(Inst^.FRefCount);
  if Result = 0 then
    Dispose(Inst);
end;

class procedure TCalcRec.Clear(Inst: PCalcData);
begin
  Inst^.FAccumulator:= 0;
end;

class function TCalcRec.GetAccumulator(Inst: PCalcData): Integer;
begin
  Result:= Inst^.FAccumulator;
end;

class procedure TCalcRec.Add(Inst: PCalcData; AValue: Integer);
begin
  Inc(Inst^.FAccumulator, AValue);
end;

class procedure TCalcRec.Sub(Inst: PCalcData; AValue: Integer);
begin
  Dec(Inst^.FAccumulator, AValue);
end;

const
  CalcVTable: array[0..6] of Pointer =
  (
    @TCalcRec.QueryIntf,
    @TCalcRec.Addref,
    @TCalcRec.Release,
    @TCalcRec.Clear,
    @TCalcRec.Add,
    @TCalcRec.Sub,
    @TCalcRec.GetAccumulator
  );

function GetCalculator: ICalculator;
var
  P: PCalcData;

begin
  New(P);
  P^.FVTable:= @CalcVTable;
  P^.FRefCount:= 0;
  P^.FAccumulator:= 0;
  Result:= ICalculator(P);
end;

end.

Here is a simple test code:

uses CalcIntf, CalcObjects, CalcRecords;

procedure TForm1.Button1Click(Sender: TObject);
var
  ICalc: ICalculator;

begin
  ICalc:= CalcObjects.GetCalculator;
  ICalc.Add(42);
  ShowMessage(IntToStr(ICalc.GetAccumulator));
end;

procedure TForm1.Button2Click(Sender: TObject);
var
  ICalc: ICalculator;

begin
  ICalc:= CalcRecords.GetCalculator;
  ICalc.Add(42);
  ShowMessage(IntToStr(ICalc.GetAccumulator));
end;

Why we need interfaces in Delphi.

23

Objects are normally accessed by an object reference. Interface reference is a different method to access an object’s functionality. A simple question – why do we need interface references at all, why can’t we use object references everywhere?
There are several reasons to use interface references instead of object references, but most important of them (at least historically) is accessing an object created in a different program module.
Let us consider a simple example – an object is created in .dll module and consumed in .exe module.
The TMathObject class implements Square and Cube functions on the FOperand field; we start with the following naive code:

unit MathUnit;

interface

type
  TMathObject = class
  private
    FOperand: Double;
  public
    function Square: Double;
    function Cube: Double;
    property Operand: Double read FOperand write FOperand;
  end;

implementation

function TMathObject.Square: Double;
begin
  Result:= Sqr(FOperand);
end;

function TMathObject.Cube: Double;
begin
  Result:= Sqr(FOperand) * FOperand;
end;

end.

We want to create and destroy TMathObject instances in dll module:

library MathDll;

uses
  MathUnit in 'MathUnit.pas';

function CreateObject: TMathObject;
begin
  Result:= TMathObject.Create;
end;

procedure FreeObject(Obj: TMathObject);
begin
  Obj.Free;
end;

exports
  CreateObject, FreeObject;

{$R *.res}

begin
end.

and use an instance of TMathObject in exe module:

program MathTest;

{$APPTYPE CONSOLE}

uses
  MathUnit in 'MathUnit.pas';

function CreateObject: TMathObject; external 'MathDll.dll';
procedure FreeObject(Obj: TMathObject); external 'MathDll.dll';

var
  MathObj: TMathObject;

begin
  MathObj:= CreateObject;
  MathObj.Operand:= 2;
  Writeln('Square = ', MathObj.Square:3:2, '; Cube = ', MathObj.Cube:3:2);
  FreeObject(MathObj);
  Write('Press ''Enter'' key ... ');
  Readln;
end.

If you compile the above example you can see it works, but TMathObject implementation (MathUnit.pas) is duplicated in both program modules (MathTest.exe and MathDll.dll), and that is not just a waste of program memory.
One of the main reasons to split a program into program modules is a possibility to modify the modules separately; for example to modify and deploy a different .dll version while keeping an .exe module intact. In the above example the implementation of TMathObject is a contract that both sides (exe and dll) should adhere, so the implementation of TMathObject can’t be changed in dll module only.
We need a different form of contract that does not include an object’s implementation. A possible solution is to introduce a base class containing virtual abstract methods only:

unit BaseMath;

interface

type
  TBaseMathObject = class
  protected
    function GetOperand: Double; virtual; abstract;
    procedure SetOperand(const Value: Double); virtual; abstract;
  public
    function Square: Double; virtual; abstract;
    function Cube: Double; virtual; abstract;
    property Operand: Double read GetOperand write SetOperand;
  end;

implementation

end.

Note that we can’t access FOperand field directly now because it is a part of TMathObject implementation that should be hidden in .dll module, so we introduce getter (GetOperand) and setter (SetOperand) virtual methods.
Now we inherit a class that implements virtual methods from TBaseMathObject.

unit MathUnit;

interface

uses BaseMath;

type
  TMathObject = class(TBaseMathObject)
  private
    FOperand: Double;
  protected
    function GetOperand: Double; override;
    procedure SetOperand(const Value: Double); override;
  public
    function Square: Double; override;
    function Cube: Double; override;
  end;

implementation

function TMathObject.GetOperand: Double;
begin
  Result:= FOperand;
end;

procedure TMathObject.SetOperand(const Value: Double);
begin
  FOperand:= Value;
end;

function TMathObject.Square: Double;
begin
  Result:= Sqr(FOperand);
end;

function TMathObject.Cube: Double;
begin
  Result:= Sqr(FOperand) * FOperand;
end;

end.

The library module source code now is

library MathDll;

uses
  BaseMath in 'BaseMath.pas',
  MathUnit in 'MathUnit.pas';

function CreateObject: TBaseMathObject;
begin
  Result:= TMathObject.Create;
end;

procedure FreeObject(Obj: TBaseMathObject);
begin
  Obj.Free;
end;

exports
  CreateObject, FreeObject;

{$R *.res}

begin
end.

The executable module source code is

program MathTest;

{$APPTYPE CONSOLE}

uses
  BaseMath in 'BaseMath.pas';

function CreateObject: TBaseMathObject; external 'MathDll.dll';
procedure FreeObject(Obj: TBaseMathObject); external 'MathDll.dll';

var
  MathObj: TBaseMathObject;

begin
  MathObj:= CreateObject;
  MathObj.Operand:= 2;
  Writeln('Square = ', MathObj.Square:3:2, '; Cube = ', MathObj.Cube:3:2);
  FreeObject(MathObj);
  Write('Press ''Enter'' key ... ');
  Readln;
end.

We can see that MathTest project does not contain MathUnit.pas unit, and is not dependent on TMathObject implementation; in fact MathTest project does not know that TMathObject class even exist. We can change TMathObject implementation in dll module as much as we want provided that we keep TBaseMathObject intact, inherit TMathObject from TBaseMathObject and override TBaseMathObject‘s virtual abstract methods.
We implemented a general concept of interface in the form of pure abstract class. Pure abstract classes are a way how interfaces are implemented in C++ . This approach has a limited value in Delphi because Delphi does not support multiple inheritance, and a Delphi class can have only one contract in the form of base abstract class. Another problem is a limited use of ‘is’ and ‘as’ operators for an object created in a different program module:

program IsTest;

{$APPTYPE CONSOLE}

uses
  SysUtils,
  BaseMath in 'BaseMath.pas';

function CreateObject: TBaseMathObject; external 'MathDll.dll';
procedure FreeObject(Obj: TBaseMathObject); external 'MathDll.dll';

var
  MathObj: TBaseMathObject;

procedure TestObj(Obj: TObject);
begin
  try
    Assert(Obj is TBaseMathObject);  // fails
  except
    on E:Exception do
      Writeln(E.Classname, ': ', E.Message);
  end;
end;

procedure TestObj2(Obj: TBaseMathObject);
begin
  try
    Assert(Obj is TBaseMathObject);  // success
  except
    on E:Exception do
      Writeln(E.Classname, ': ', E.Message);
  end;
end;

begin
  MathObj:= CreateObject;
  TestObj(MathObj);
  TestObj2(MathObj);
  FreeObject(MathObj);
  Write('Press ''Enter'' key ... ');
  Readln;
end.

Here is a brief explanation why the first assertion fails while the second assertion succeeds. Delphi class can be identified by its name or by its VMT. Even if a class does not define any virtual method and VMT itself is empty a pointer to class VMT is still valid. For the TBaseMathObject class we have a single name and two VMT’s, one in .exe module and one in .dll module. That may cause strange behavior, as shown in the above example.
Starting from version 3 Delphi introduces a concept of interface that is different from a pure abstract class and solves the problems with object’s export by using interface references instead of object references:

unit BaseMath;

interface

type
  IBaseMath = interface
  ['{92E9AFF4-25B7-41BD-9EB6-557D12F98BE6}']
    function GetOperand: Double;
    procedure SetOperand(const Value: Double);
    function Square: Double;
    function Cube: Double;
    property Operand: Double read GetOperand write SetOperand;
  end;

implementation

end.

There is no need to inherit TMathObject class from a given base class now; we can inherit TMathObject class from any class we like. Since all Delphi interfaces are descendants of IUnknown (also nicknamed as IInterface in Delphi) we should also implement the methods of IUnknown interface in TMathObject class. Delphi provides a helper TInterfacedObject class that already implements the methods of IUnknown and can be used as TMathObject ancestor:

unit MathUnit;

interface

uses BaseMath;

type
  TMathObject = class(TInterfacedObject, IBaseMath)
  private
    FOperand: Double;
  protected
    function GetOperand: Double;
    procedure SetOperand(const Value: Double);
  public
    function Square: Double;
    function Cube: Double;
  end;

implementation

function TMathObject.GetOperand: Double;
begin
  Result:= FOperand;
end;

procedure TMathObject.SetOperand(const Value: Double);
begin
  FOperand:= Value;
end;

function TMathObject.Square: Double;
begin
  Result:= Sqr(FOperand);
end;

function TMathObject.Cube: Double;
begin
  Result:= Sqr(FOperand) * FOperand;
end;

end.

There is no need for FreeObject procedure now. The FreeObject procedure was introduced in the previous examples to enforce that a TMathObject instance is destroyed in the same program module where it was created (i.e. in .dll module). It is always a good rule of thumb that the one who creates an object is the one who destroys it. But now there is no need to enforce it – if we use interface references object instances are automatically destroyed in the same program module where they were created.

library MathDll;

uses
  BaseMath in 'BaseMath.pas',
  MathUnit in 'MathUnit.pas';

function CreateObject: IBaseMath;
begin
  Result:= TMathObject.Create;
end;

exports
  CreateObject;

{$R *.res}

begin
end.

In the next example a TMathObject object instance is destroyed by assigning nil value to MathObj interface reference. In most cases there is no need for doing it because an object is destroyed automatically when all interface references goes out of scope. In the following code the MathObj interface reference is a global variable and never goes out of scope, so assigning it to nil explicitly makes sense:

program MathTest;

{$APPTYPE CONSOLE}

uses
  BaseMath in 'BaseMath.pas';

function CreateObject: IBaseMath; external 'MathDll.dll';

var
  MathObj: IBaseMath;

begin
  MathObj:= CreateObject;
  MathObj.Operand:= 2;
  Writeln('Square = ', MathObj.Square:3:2, '; Cube = ', MathObj.Cube:3:2);
  MathObj:= nil;
  Write('Press ''Enter'' key ... ');
  Readln;
end.

TInteger

2

Since the introduction of operator overloading in Delphi one can easily define custom data types that support arithmetic operations. For example it is quite easy to implement arithmetic of complex numbers like this:

type
  PComplex = ^TComplex;
  TComplex = record
    Re, Im: Extended;

    class operator Implicit(const A: Extended): TComplex;

    class operator Add(const A, B: TComplex): TksComplex;
    class operator Subtract(const A, B: TComplex): TComplex;
    class operator Multiply(const A, B: TComplex): TComplex;
{...}

Note that TComplex is a static type with fixed data size (Re, Im fields) and is normally allocated on stack. The problems arise when you need to implement arithmetic on dynamic data allocated on heap.
If you are about to implement big integer (TInteger) arithmetic you have two alternatives.
First, you can stick to static data types; that means that you should limit maximum number value in the implementation.
The second alternative is to use dynamic type to allocate the numbers, so there is no need to introduce artificial limits on maximum number value.

The first approach is obvious, the second is more interesting.
You can’t use GetMem/FreeMem to allocate/deallocate the numbers on heap because memory deallocation should be performed automatically by the compiler, and you just cannot instruct the compiler to call FreeMem for temporary TInteger value while evaluating arithmetic expression like

A:= (B + C) * D;

Consequently, the dynamic type to hold the number data should be lifetime-managed type – and the dynamic array seems to be the most probable candidate:

type
  TInteger = record
  private
    FData: array of LongWord;
{...}

The above implementation forbids “in-place” operations on TInteger variables – the reason is a side effect of TInteger assignment. Dynamic arrays do not support copy-on-write, so the following assertion will fail:

var
  A, B: TInteger;

begin
  A:= 1;
  B:= A;
  Inc(A);
  Assert(B=1);
end;

After B:= A both A.FData and B.FData reference the same data, so that after Inc(A) we have B = 2.

Now we have 3 alternatives:

  • we can avoid completely the in-place operations on TInteger;
  • we can use undocumented format of dynamic array to implement TInteger “copy-on-write” by hacking dynamic array’s reference count before calling in-place operations;
  • we can use interfaces.

(Well, there are lifetime-managed data types in Delphi that support “copy-on-write” semantic – strings, but they can’t help us since we need some low-level hacking to manipulate the binary data in string format and “copy-on-write” will not work as we need).

Let us try the following:

type
  PIntegerData = ^TIntegerData;
  TIntegerData = record
    Size: LongInt;   // number of allocated longwords in Data
    Data: array[0..0] of LongWord;
  end;

  IInteger = interface
    function GetData: PIntegerData;
    function GetRefCount: Integer;
  end;

  TIntegerObject = class(TInterfacedObject, IInteger)
  private
    FData: PIntegerData;
  public
    constructor Create(Limbs: Integer);
    destructor Destroy; override;
    function GetData: PIntegerData;
    function GetRefCount: Integer;
  end;

  TInteger = record
  private
    FInteger: IInteger;
{..}

The implementation based on interfaces is the most “correct”. It does not use any dirty hacking or undocumented features to get reference count and implement “copy-on-write”. The bad thing is that the above implementation has the worst performance. For example, to allocate a single TInteger value you need to allocate two memory blocks on heap – first for a TIntegerObject and second for TIntegerData referenced by the TIntegerObject.

All in all, a TInteger implementation based on dynamic arrays (with or without refcount hacking) looks most optimal today.

Delphi interfaces without reference counting

13

Any interface in Delphi inherits from IInterface (which is a nickname for IUnknown). It is nice for reference-counted interfaces, but sometimes we do not need reference counting at all. Consider the following example:

unit IValues;

interface

type
  IValue = interface(IInterface)
    function GetValue: Integer;
  end;

function GetIValue: IValue;

implementation

type
  TValueObject = class(TObject, IValue)
  protected
    FValue: Integer;
    function GetValue: Integer;
  public
    constructor Create(AValue: Integer);
  end;

var
  FValueObject: TValueObject;

function GetIValue: IValue;
begin
  Result:= FValueObject;
end;

constructor TValueObject.Create(AValue: Integer);
begin
  FValue:= AValue;
end;

function TValueObject.GetValue: Integer;
begin
  Result:= FValue;
end;

initialization
  FValueObject:= TValueObject.Create(11);

finalization
  FValueObject.Free;

end.

The above code does not compile because TValueObject does not implement IUnknown. A workaround is to implement stub methods for IUnknown:

type
  TValueObject = class(TObject, IValue)
  protected
    FValue: Integer;
    function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
    function _AddRef: Integer; stdcall;
    function _Release: Integer; stdcall;
    function GetValue: Integer;
  public
    constructor Create(AValue: Integer);
  end;
 
function TValueObject.QueryInterface(const IID: TGUID; out Obj): HResult;
begin
  if GetInterface(IID, Obj) then
    Result:= S_OK
  else
    Result:= E_NOINTERFACE;
end;

function TValueObject._AddRef: Integer;
begin
  Result:= -1;
end;

function TValueObject._Release: Integer;
begin
  Result:= -1;
end;

You can find the same code in VCL sources (TCustomVariantType class from Variants.pas unit).

Now, why should I implement IUnknown in my code if I don’t need it at all?

The above implementation of IUnknown looks like a standard implementation for non-reference-counted interfaces in Delphi. Why not to include it into System.pas and apply compiler magic to make the code in the first example compilable and legitimate? The implementation does not use additional object fields (TInterfacedObject implementation requires additional FRefCount field), so it seems like all that is required from the compiler is to insert pointers to the default IUnknown method’s implementations into the interface tables of any class that does not implement or inherit these methods can be implemented directly in TObject. It will save the programmer from writing a useless duplicate code. The compiler may also issue a warning for the safety reasons, if necessary.

Delphi interfaces on binary level

11

An interface reference in Delphi is a pointer to pointer to an interface method table (IMT). That follows the COM specifications and is a good starting point to understand what Delphi interfaces are on binary level. Delphi interfaces can be made 100% compatible with COM specifications, but that is not necessary – it is also possible to implement “light COM-like” interfaces.

Interface methods in Delphi are implemented as object’s methods, so let us have a quick look on the Delphi objects on binary level. Each Delphi 2009 object instance have 2 necessary fields, 4 bytes each. The first field is a pointer to the class VMT, the last field (prefixed by ‘hf’ – Hidden Field? in System.pas) is used by TMonitor advanced record, currently I don’t know what this field is actually for. There are no more fields in TObject instance, so TObject instance size in Delphi 2009 is 8 bytes. We need not these fields here, but we must have in mind that the first 4 bytes and the last 4 bytes of any object instance are “reserved”.
Now let us consider what TInterfacedObject is on binary level. TInterfacedObject implements IInterface that is declared in System.pas as

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

The TInterfacedObject itself is declared as

  TInterfacedObject = class(TObject, IInterface)
  protected
    FRefCount: Integer;
    function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
    function _AddRef: Integer; stdcall;
    function _Release: Integer; stdcall;
  public
    procedure AfterConstruction; override;
    procedure BeforeDestruction; override;
    class function NewInstance: TObject; override;
    property RefCount: Integer read FRefCount;
  end;

TInterfacedObject instance size (in Delphi 2009) is 16 bytes, and we have 2 additional fields (4 bytes each). The first additional field is FRefCount field, the second is more interesting for us – it is a pointer to the interface method table. If we create an interface reference – for example, by calling

var
  II: IInterface;

begin  
  II:= TInterfacedObject.Create;
  ..

then we have

IMT consists of 3 entries – pointers to QueryInterface, AddRef and Release implementations.

Note that an interface reference is just a simple 4-byte pointer while interface methods are object’s methods and require two pointers – a 4-byte pointer to the method’s code and a 4-byte pointer to the object’s instance. The code pointer can be found in IMT, but what about the object’s instance pointer? After having a closer look on the above picture we can guess that the compiler “knows” the offset of IMT field and subtracts it from an interface reference value to obtain a pointer to object’s instance. Let us check the guess:

procedure TForm1.Button1Click(Sender: TObject);
var
  II: IInterface;

begin
  II:= TInterfacedObject.Create;
  II._AddRef;
  II._Release;
end;

The above code just calls two interface methods – _AddRef and _Release. The compiler implements these calls as follows:

II._AddRef;
        mov eax,[ebp-$04]
        push eax
        mov eax,[eax]
        call dword ptr [eax+$04]
II._Release;
        mov eax,[ebp-$04]
        push eax
        mov eax,[eax]
        call dword ptr [eax+$08]

[ebp-$04] is the interface reference II. The compiler pushes it onto stack (as required by stdcall calling conventions), takes a pointer to IMT from the object’s field pointed by interface reference, adds a IMT offset ($04 for _AddRef, $08 for _Release) and calls the corresponding code. No offset is subtracted – the calls are implemented as if a pointer to object’s instance is equal to an interface reference value, but we know they are different.
Let us go further and have a look to the code called by

        call dword ptr [eax+$04]
        call dword ptr [eax+$08]

instructions:

        add dword ptr [esp+$04],-$08
        jmp TInterfacedObject._AddRef
        add dword ptr [esp+$04],-$08
        jmp TInterfacedObject._Release

Yes! That is where the compiler uses its knowledge about the IMT field offset in an object’s instance. Instead of calling TInterfacedObject methods directly the compiler calls a proxy code that converts an interface reference value into a pointer to an object’s instance and jumps to an object’s method implementation. For optimization reasons the compiler adds -8 instead of subtracting 8 (the offset of IMT field in TInterfacedObject instance), that does not matter for us.

Now all pieces of the puzzle are in place.
On the “client” side we have an interface reference – a plain 4-byte pointer. An interface reference is a pointer to pointer to the IMT; the IMT is an array of pointers to the method’s proxy code. When the compiler calls an interface method pointed by an IMT entry it uses the value of the interface reference as an additional “Self” method’s argument.
On the “server” side we have an object with methods that implements the interface methods. Object’s methods require “Self” argument – a pointer to the object’s instance. But the object’s “Self” is not equal to the value of interface reference.
Between the “client” and “server” there is a proxy code that converts a “client” interface reference value to a “server” object’s instance pointer and jumps to the object’s method implementation.

Now we understand how object’s methods are called using an interface reference, and we can convert an interface reference into a method pointer manually. The following code is a modification of the code from Barry Kelly’s post about the anonymous methods in Delphi:

procedure IntRefToMethPtr(const IntRef; var MethPtr; MethNo: Integer);
type
  TVtable = array[0..999] of Pointer;
  PVtable = ^TVtable;
  PPVtable = ^PVtable;
begin
  // QI=0, AddRef=1, Release=2, etc
  TMethod(MethPtr).Code := PPVtable(IntRef)^^[MethNo];
  TMethod(MethPtr).Data := Pointer(IntRef);
end;

Let us test the above procedure:

type
  TIntFunc = function: Integer of object; stdcall;

procedure TForm1.Button2Click(Sender: TObject);
var
  II: IInterface;
  AddRefMeth: TIntFunc;
  Obj: TInterfacedObject;

begin
  Obj:= TInterfacedObject.Create;
  II:= Obj;
  IntRefToMethPtr(II, AddRefMeth, 1);
  ShowMessage(IntToStr(Obj.RefCount));
  AddRefMeth;
  ShowMessage(IntToStr(Obj.RefCount));
  II._Release;
  ShowMessage(IntToStr(Obj.RefCount));
end;

We can see that AddRefMeth method pointer call do the same as _AddRef interface method call – increments a reference count.