Interfaces without objects

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;

Advertisements

19 thoughts on “Interfaces without objects

    • I am currently considering big integer math implementation based on the above technique. It has its advantages over using dynamic array to hold the number data and free from overhead of interface implementation based on objects because does not require 2 heap memory blocks to hold a big number.

      • You implementation is using the heap for allocating the record content, just like a regular class.
        So I do not get the benefit here.
        You are emulating the class with a record, but it more painful and you’d better let the compiler do the work for you.
        You can create an interface with no allocation with a regular class, just by overriding the AddRef/Release methods.
        This is a nice proof of concept, but can not be useful as this.

        What is possible, but also more complex, is to create a whole class on the fly, using the interface RTTI. We use this for implementing our JSON interface-based services – see http://blog.synopse.info/post/2012/03/07/Interface-based-services-implementation-details – and I think this is more useful – a similar technique is used for SOAP calls, but IMHO our implementation is easier to read and more natural.

  1. This is how COM interfaces are implmented in C. It’s revolting. There’s a good reason why we get the compiler to generate our vtables. And why would you want to reimplement InterlockedIncrement?

    • There is nothing wrong in COM implementation in pure C. You can create interface vtable manually because its format is defined on binary level by COM specification. I reimplement InterlockedIncrement because I can’t find where it implemented in Delphi RTL.

  2. Don’t use this hack in production code!
    It’s interesting to find out how COM works; this code might help to get a better understanding what’s behind a interface.
    But combining records with interfaces and handmade vtables leads to the dark path of unmaintainable code.

  3. @Serg:
    Sorry for the misspelling of your name! Unfortunately I can’t edit the posts.

    This statement I can only agree!

    @DGuest:
    How many drivers know how the engine works?
    Almost every programmer using Delphi components. What percentage of them understand how they work internally?
    I programmed a framework. How many programmers know how it is structured? I don’t know and it does not matter. The important thing is that it is easy to use!

  4. Clever, and I believe, unnecessarily so. As david says, writing your own vtable out is … ugly. Perhaps you have a dire need to do this, but this is likely to break in some extremely unpredictable ways, at some future date, when some new delphi version comes out, or when you need to modify your interface.

    Warren

  5. Yes. Basically this is how interfaces can be implemented on old school objects too (same layout as records)
    It has the very very HUGE advantage that it generates no codebloat, needs no extremely unsafe rtti (read/write memory for code segments anyone?……. I am happy with it 😉
    Using this you can get huge exe size savings. I am considering it for KOL.

  6. Pingback: Big Integer redux « The Programming Works

  7. Pingback: Goodbye, TObject | The Programming Works

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s