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;