Sven Barth
2017-01-28 12:29:42 UTC
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
_______________________________________________
fpc-announce maillist - fpc-***@lists.freepascal.org
http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-announce
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
_______________________________________________
fpc-announce maillist - fpc-***@lists.freepascal.org
http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-announce