Introduction
One of the limitations of the TRttiProperty class is which not expose any direct relation between the property and the getter and setters. On this post we will check out how we can access to such info using the RTTI. So the aim is obtain a TRttiField and/or TRttiMethod instance to the getter and setter of the property. This will be done using a class helper like so.
type TRttiPropertyHelper = class helper for TRttiProperty private function GetSetterField : TRttiField; function GetGetterField : TRttiField; public property SetterField : TRttiField read GetSetterField; property GetterField : TRttiField read GetGetterField; function SetterMethod (Instance : TObject) : TRttiMethod; function GetterMethod (Instance : TObject) : TRttiMethod; end;
Getting the address of the getter and setters.
The System.TypInfo.TPropInfo type contains two fields which holds the address of the getter and setters members
PPropInfo = ^TPropInfo; TPropInfo = packed record PropType: PPTypeInfo; GetProc: Pointer; SetProc: Pointer; StoredProc: Pointer; Index: Integer; Default: Integer; NameIndex: SmallInt; Name: TSymbolName; function NameFld: TTypeInfoFieldAccessor; inline; function Tail: PPropInfo; inline; end;
We need to examine such addresses to determine if represents a field or a method. So I will define a set of sample classes and dump the address of the getter and setter.
type TBar = class private FReadOnlyProp: string; FWriteOnlyProp: string; function GetReadBaseProp: string; virtual; procedure SetWriteBaseProp(const Value: string); virtual; public property ReadBaseProp: string read GetReadBaseProp; property WriteBaseProp: string write SetWriteBaseProp; end; TFoo = class(TBar) private function GetReadOnlyPropwGet: string; procedure SetWriteOnlyPropwSet(const Value: string); function GetReadBaseProp: string; override; public property ReadOnlyProp: string read FReadOnlyProp; property WriteOnlyProp: string Write FWriteOnlyProp; property ReadOnlyPropwGet: string read GetReadOnlyPropwGet; property WriteOnlyPropwSet: string write SetWriteOnlyPropwSet; end;
And now to obtain the addresses we will access the PropInfo member of each property in the class.
procedure DumpPropInfo(AClass: TObject); var LContext: TRttiContext; LType: TRttiType; LProp: TRttiProperty; LPropInfo: PPropInfo; begin //Get the typeinfo of the class LType := LContext.GetType(AClass.ClassInfo); for LProp in LType.GetProperties() do if LProp is TRttiInstanceProperty then begin //Get the pointer to the PPropInfo LPropInfo := TRttiInstanceProperty(LProp).PropInfo; //show the addresses of the getter and setter Writeln(Format('%-18s GetProc %p SetProc %p', [LProp.Name, LPropInfo.GetProc, LPropInfo.SetProc])); end; end;
If we run the above code passing an instance to the TFoo class we will obtain this output
ReadOnlyProp GetProc FF000004 SetProc 00000000 //Field WriteOnlyProp GetProc 00000000 SetProc FF000008 //Field ReadOnlyPropwGet GetProc 004CCE70 SetProc 00000000 //Static method WriteOnlyPropwSet GetProc 00000000 SetProc 004CCE80 //Static method ReadBaseProp GetProc FE000000 SetProc 00000000 //virtual method WriteBaseProp GetProc 00000000 SetProc FE000004 //virtual method
Obtaining the TRttiField instance
A visual inspection of the returned values, indicates which the fields offsets are masked with the $FF000000 value. So with this info we can build a couple of helper functions to obtain an instance to the TRttiField .
First we need determine when a getter/setter represents a field.
In the System.TypInfo unit exists a private boolean function called IsField which indicates when a Pointer (GetProc, SetProc) represents a field.
function IsField(P: Pointer): Boolean; inline; begin Result := (IntPtr(P) and PROPSLOT_MASK) = PROPSLOT_FIELD; end;
Now using the above method and some additional code we can build the next function which returns a TRttiField instance for the getter of a property.
function GetPropGetterField(AProp : TRttiProperty) : TRttiField; var LPropInfo : PPropInfo; LField: TRttiField; LOffset : Integer; begin Result:=nil; //Is a readable property? if (AProp.IsReadable) and (AProp.ClassNameIs('TRttiInstancePropertyEx')) then begin //get the propinfo of the porperty LPropInfo:=TRttiInstanceProperty(AProp).PropInfo; //check if the GetProc represent a field if (LPropInfo<>nil) and (LPropInfo.GetProc<>nil) and IsField(LPropInfo.GetProc) then begin //get the offset of the field LOffset:= IntPtr(LPropInfo.GetProc) and PROPSLOT_MASK_F; //iterate over the fields of the class for LField in AProp.Parent.GetFields do //compare the offset the current field with the offset of the getter if LField.Offset=LOffset then Exit(LField); end; end; end;
To obtain the setter field the code looks very similar but instead we inspect the SetProc member.
function GetPropSetterField(AProp : TRttiProperty) : TRttiField; var LPropInfo : PPropInfo; LField: TRttiField; LOffset : Integer; begin Result:=nil; //Is a writable property? if (AProp.IsWritable) and (AProp.ClassNameIs('TRttiInstancePropertyEx')) then begin //get the propinfo of the porperty LPropInfo:=TRttiInstanceProperty(AProp).PropInfo; //check if the GetProc represent a field if (LPropInfo<>nil) and (LPropInfo.SetProc<>nil) and IsField(LPropInfo.SetProc) then begin //get the offset of the field LOffset:= IntPtr(LPropInfo.SetProc) and PROPSLOT_MASK_F; //iterate over the fields of the class for LField in AProp.Parent.GetFields do //compare the offset the current field with the offset of the setter if LField.Offset=LOffset then Exit(LField); end; end; end;
Obtaining the TRttiMethod instance
To obtain a TRttiMethod instance for the setter and getter, first we need to determine if the GetProc/SetProc represent a static o virtual method, then we need to obtain the real address of the method. Luckily exist the private function GetCodePointer in the System.TypInfo unit which do this task. Note that we need a instance to the object to resolve the code address.
function GetCodePointer(Instance: TObject; P: Pointer): Pointer; inline; begin if (IntPtr(P) and PROPSLOT_MASK) = PROPSLOT_VIRTUAL then // Virtual Method Result := PPointer(PNativeUInt(Instance)^ + (UIntPtr(P) and $FFFF))^ else // Static method Result := P; end;
Now we can create a function to return a TRttiMethod for the getter of a property.
function GetPropGetterMethod(Instance: TObject; AProp : TRttiProperty) : TRttiMethod; var LPropInfo : PPropInfo; LMethod: TRttiMethod; LCodeAddress : Pointer; LType : TRttiType; LocalContext: TRttiContext; begin Result:=nil; if (AProp.IsReadable) and (AProp.ClassNameIs('TRttiInstancePropertyEx')) then begin //get the PPropInfo pointer LPropInfo:=TRttiInstanceProperty(AProp).PropInfo; if (LPropInfo<>nil) and (LPropInfo.GetProc<>nil) and not IsField(LPropInfo.GetProc) then begin //get the real address of the ,ethod LCodeAddress := GetCodePointer(Instance, LPropInfo^.GetProc); //get the Typeinfo for the current instance LType:= LocalContext.GetType(Instance.ClassType); //iterate over the methods of the instance for LMethod in LType.GetMethods do begin //compare the address of the currrent method against the address of the getter if LMethod.CodeAddress=LCodeAddress then Exit(LMethod); end; end; end; end;
And for the setter is the same again but we inspect the SetProc instead.
function GetPropSetterMethod(Instance: TObject; AProp : TRttiProperty) : TRttiMethod; var LPropInfo : PPropInfo; LMethod: TRttiMethod; LCodeAddress : Pointer; LType : TRttiType; LocalContext: TRttiContext; begin Result:=nil; if (AProp.IsWritable) and (AProp.ClassNameIs('TRttiInstancePropertyEx')) then begin //get the PPropInfo pointer LPropInfo:=TRttiInstanceProperty(AProp).PropInfo; if (LPropInfo<>nil) and (LPropInfo.SetProc<>nil) and not IsField(LPropInfo.SetProc) then begin LCodeAddress := GetCodePointer(Instance, LPropInfo^.SetProc); //get the Typeinfo for the current instance LType:= LocalContext.GetType(Instance.ClassType); //iterate over the methods for LMethod in LType.GetMethods do begin //compare the address of the currrent method against the address of the setter if LMethod.CodeAddress=LCodeAddress then Exit(LMethod); end; end; end; end;
TRttiPropertyHelper
Finally we can implement the methods of our helper for the TRttiProperty class.
function TRttiPropertyHelper.GetGetterField: TRttiField; begin Result:= GetPropGetterField(Self); end; function TRttiPropertyHelper.GetSetterField: TRttiField; begin Result:= GetPropSetterField(Self); end; function TRttiPropertyHelper.GetterMethod(Instance: TObject): TRttiMethod; begin Result:= GetPropGetterMethod(Instance, Self); end; function TRttiPropertyHelper.SetterMethod(Instance: TObject): TRttiMethod; begin Result:= GetPropSetterMethod(Instance, Self); end;
Now we can use the helper like this
procedure DumpPropInfoExt(AClass: TObject); var LContext: TRttiContext; LType: TRttiType; LProp: TRttiProperty; LPropInfo: PPropInfo; LField: TRttiField; LMethod: TRttiMethod; begin LType := LContext.GetType(AClass.ClassInfo); for LProp in LType.GetProperties() do if LProp is TRttiInstanceProperty then begin LPropInfo := TRttiInstanceProperty(LProp).PropInfo; Writeln(Format('%-18s GetProc %p SetProc %p', [LProp.Name, LPropInfo.GetProc, LPropInfo.SetProc])); if LProp.IsReadable then begin LField := LProp.GetterField; if LField <> nil then Writeln(Format(' Getter Field Name %s', [LField.Name])) else begin LMethod := LProp.GetterMethod(AClass); if LMethod <> nil then Writeln(Format(' Getter Method Name %s', [LMethod.Name])) end; end; if LProp.IsWritable then begin LField := LProp.SetterField; if LField <> nil then Writeln(Format(' Setter Field Name %s', [LField.Name])) else begin LMethod := LProp.SetterMethod(AClass); if LMethod <> nil then Writeln(Format(' Setter Method Name %s', [LMethod.Name])) end; end; end; end;
Limitations
Exist some limitations to use the above code.
- Delphi Array Properties are not supported for the GetProperties method. Instead you must use the GetIndexedProperties method to get a list of TRttiIndexedProperty and from there you can access to the ReadMethod and WriteMethod properties.
- The getters and setters methods of the property must emit RTTI info this implies which depending of the visibility of these methods you will need instruct to the compiler generate RTTI info for the methods, adding a sentence like this to your class {$RTTI EXPLICIT METHODS([vcPrivate])}
You can download the sample code from Github.
Rodrigo.
September 25, 2015 at 6:28 pm
I have do something similar and I would like to add a comment:
In your limitations, the first point (Array Properties) is only valid for XE (and maybe other early versions of eRTTI). They are supported in later versions. This was one of the gotchas I came across in back porting to XE. The class is “TRttiIndexedProperty”
This is more a question than a comment. In GetPropSetterField/GetPropGetterField, you use/do “for LField in AProp.Parent.GetFields do”. Can’t the similar technique be used in GetPropSetterMethod/GetPropGetterMethod : for LMethod in AProp.Parent.GetMethods do” ?
September 25, 2015 at 8:50 pm
Nicholas, About the Array properties. I will check later. Now about your second question The AProp.Parent returns the class where the property is defined, So for example for the ReadBaseProp property the AProp.Parent is ‘TBar’, but the implementation for the GetReadBaseProp method is located in TFoo, So the Address will be not found if I use AProp.Parent, Because that the Instance is used to get the methods.
September 27, 2015 at 12:28 pm
Nicholas I just edited that part of the article which refers to the Array properties.
October 20, 2016 at 7:50 am
I’m using this examples in code, i just found out that is isn;t working right if you define a few private fields which you don’t use as properties. Then the offsets don’t match.
October 20, 2016 at 2:37 pm
Please report this (and attach a sample project) using the issue page of the blog repository https://github.com/RRUZ/blog/issues
July 1, 2017 at 7:00 pm
Nice.
For me was “GetDeclaredMethods” enough which returns an array with 8 elements instead of 258.
Btw. you should put {$RTTI EXPLICIT METHODS([vcPrivate])} into the code, as a selective reader i had to figure it myself ;)