[fpc-announce] Feature announcement: Interface RTTI

Sven Barth pascaldragon at googlemail.com
Sat Jan 28 13:29:42 CET 2017


Hello together!

I'm pleased to finally announce the addition of Interface RTTI to Free
Pascal.

Interface RTTI essentially provides a list of all methods available in
an interface if it's declared is parsed with $M+ or has such an
interface as parent.

For now however this only applies to COM style interfaces. CORBA/Raw
style interfaces don't respect $M+ yet, so they don't have that RTTI.

Also note that while this RTTI provides the same content as Delphi it is
*not* Delphi compatible. In fact since FPC's TypInfo unit never has been
fully compatible with Delphi it's save to come out and say that the
TypInfo unit is considered a known incompatibility to Delphi and always
will be. For compatibility with newer versions of Delphi it's suggested
to use the RTTI unit (patches to improve/extend its functionality are
welcome).

To access the methods it's best to use the new types provided by the
TypInfo unit with the starting point being TInterfaceData.
The list of methods is available in the property MethodTable of type
TIntfMethodTable. This contains two count fields, namely Count and
RTTICount. The former *always* contains the number of methods contained
in *this* interface, the latter is either $FFFF if $M- or the same as
Count if $M+. Directly after that follow the message information in the
form of TIntfMethodEntry and can be easily accessed using the Method[]
property of TIntfMethodTable.

Each method consists of its name, calling convention, method kind,
return type (if any), needed stack size for the parameters, any
parameters (which can be accessed using the Param[] property) and if the
return type is not Nil then also the location of the return value.

A parameter consists of its name, the parameter type (Note: open array
parameters have their element type as type!), parameter flags and the
location the parameter needs to reside in for invoking the method. These
locations aren't restricted to single locations however as for example
on 32-bit platforms 64-bit values might be passed using two registers.
The parameters also contain hidden parameters not really visible in the
methods declarations like the Self argument, an eventual Result
parameter (for example AnsiString or UnicodeString is passed this way on
some platforms) or the high parameter for open arrays.

=== example begin ===

program tintfrtti;

{$mode objfpc}{$H+}

uses
  typinfo;

type
  {$push}
  {$M+}
  ITest = interface
    procedure Test;
    function Test2(aArg1: LongInt): Int64;
    function Test3(aArg1: array of String): String;
  end;
  {$pop}

var
  id: PInterfaceData;
  imt: PIntfMethodTable;
  ime: PIntfMethodEntry;
  vmp: PVmtMethodParam;
  i, j: LongInt;
begin
  id := PInterfaceData(GetTypeData(TypeInfo(ITest)));
  imt := id^.MethodTable;
  Writeln('Methods: ', imt^.Count, ' ', imt^.RTTICount);
  for i := 0 to imt^.Count - 1 do begin
    ime := imt^.Method[i];
    Writeln('Method ', ime^.Name);
    Writeln(#9'CC: ', ime^.CC);
    Writeln(#9'Kind: ', ime^.Kind);
    Writeln(#9'StackSize: ', ime^.StackSize);
    if Assigned(ime^.ResultType) then begin
      Writeln(#9'Result Type: ', ime^.ResultType^^.Name);
      Writeln(#9'Result Locations: ', ime^.ResultLocs^.Count);
    end else begin
      Writeln(#9'Return Type: <none>');
      Writeln(#9'Result Locations: <none>');
    end;
    Writeln(#9'Params: ', ime^.ParamCount);
    for j := 0 to ime^.ParamCount - 1 do begin
      vmp := ime^.Param[j];
      Writeln(#9'Param ', vmp^.Name);
      Writeln(#9#9'Type: ', vmp^.ParamType^^.Name);
      Writeln(#9#9'Flags: ', HexStr(Word(vmp^.Flags), 4));
      Writeln(#9#9'Locations: ', vmp^.ParaLocs^.Count);
    end;
  end;
end.

=== example end ===

On a x86_64-linux it will print the following:

=== output begin ===

Methods: 3 3
Method Test
	CC: ccReg
	Kind: mkProcedure
	StackSize: 0
	Return Type: <none>
	Result Locations: <none>
	Params: 1
	Param $self
		Type: ITest
		Flags: 0288
		Locations: 1
Method Test2
	CC: ccReg
	Kind: mkFunction
	StackSize: 0
	Result Type: Int64
	Result Locations: 1
	Params: 2
	Param $self
		Type: ITest
		Flags: 0288
		Locations: 1
	Param aArg1
		Type: LongInt
		Flags: 0000
		Locations: 1
Method Test3
	CC: ccReg
	Kind: mkFunction
	StackSize: 0
	Result Type: AnsiString
	Result Locations: 1
	Params: 4
	Param $self
		Type: ITest
		Flags: 0288
		Locations: 1
	Param $result
		Type: AnsiString
		Flags: 0881
		Locations: 1
	Param aArg1
		Type: AnsiString
		Flags: 0014
		Locations: 1
	Param $highAARG1
		Type: Int64
		Flags: 0182
		Locations: 1

=== output end ===

A note regarding performance: The indexing properties of except the one
in TParameterLocations have a complexity of O(n) as they always need to
iterate from the 0th element. So if you have code that relies on
performance it might be better to iterate them like this:

=== code begin ===

i := 0;
ime := imt^.Method[0];
while i < imt^.Count do begin
  { do something with ime }
  ime := ime^.Next;
  Inc(i);
end;

=== code end ===

The Next property correctly handles alignment on targets that requires
them, so they are the recommended, platform independent way of accessing
the following entry. Please note however that Next will *not* return Nil
once the end is reached (because it has no knowledge about this; it
simply returns a pointer to the location after itself).

Maybe in the future for-in-iterators might be added.

Similar functionality has been added to TPropData which can also be
accessed from TInterfaceData.PropertyTable and
TInterfaceRawData.PropertyTable.
Further utility records to simplify navigation of the raw RTTI are
planned to be added.
Also the Delphi compatible RTTI unit will be extended accordingly.

Regards,
Sven


More information about the fpc-announce mailing list