The Road to Delphi

Delphi – Free Pascal – Oxygene


6 Comments

Getting the getter and setter of a property using RTTI

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.

  1. 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.
  2. 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.


4 Comments

Fun with Delphi RTTI – Dump a TRttiType

Here ‘s a sample code of how you can dump the declaration of a TRttiType using the Rtti.

Supports classes, records and interfaces.

Delphi

//Author  Rodrigo Ruz V. 2010-10-10
uses
  Rtti,
  TypInfo,
  Classes,
  Generics.Collections,
  SysUtils;

function  DumpTypeDefinition(ATypeInfo: Pointer;OnlyDeclarated:Boolean=False) : string;

  //add and format a field
  procedure AddField(List:TStrings;lField : TRttiField);
  begin
     if Assigned(lField.FieldType) then
      List.Add((Format('   %-20s:%s;',[lField.Name,lField.FieldType.Name])))
     else
      List.Add((Format('   %-20s:%s;',[lField.Name,'Unknow'])));
  end;

  //add and format a method
  procedure AddMethod(List:TStrings;lMethod : TRttiMethod);
  begin
     List.Add((Format('   %s;',[lMethod.ToString])));
  end;

  //add and format a Property
  procedure AddProperty(List:TStrings;lProperty : TRttiProperty);
  begin
     List.Add((Format('   %s;',[lProperty.ToString])));
  end;

const
 sType          = 'type';
 sIndent        = '  ';
 ArrVisibility  : Array[TMemberVisibility] of string = ('private','protected','public','published');//Helper array for Visibility
var
  ctx       : TRttiContext;
  lType     : TRttiType;
  lMethod   : TRttiMethod;
  lProperty : TRttiProperty;
  lField    : TRttiField;
  Definition: TObjectDictionary<string, TStrings>;
  i         : TMemberVisibility;
begin
   Result:='No Rtti Information';
   ctx       := TRttiContext.Create;
   Definition:= TObjectDictionary<string, TStrings>.Create([doOwnsValues]);
   try

     if not Assigned(ATypeInfo) then exit;
     lType:=ctx.GetType(ATypeInfo);
     if not Assigned(lType) then exit;

     Definition.Add(sType,TStringList.Create);
     Definition.Items[sType].Add('type');

     //Initialize the buffers to hold the data
     for i:=Low(TMemberVisibility) to High(TMemberVisibility) do
     begin
      Definition.Add(ArrVisibility[i]  ,TStringList.Create);
      Definition.Items[ArrVisibility[i]].Add(sIndent+ArrVisibility[i]);
     end;

     case lType.TypeKind of
       tkUnknown    : ;
       tkInteger    : ;
       tkChar       : ;
       tkEnumeration: ;
       tkFloat      : ;
       tkString     : ;
       tkSet        : ;
       tkClass      :
                     begin
                       //get the main definition
                       if Assigned(lType.BaseType) then
                        Definition.Items[sType].Add(Format('%s%s=class(%s)',[sIndent,lType.Name,lType.BaseType.Name]))
                       else
                        Definition.Items[sType].Add(Format('%s%s=class',[sIndent,lType.Name]));
                     end;
       tkMethod     : ;
       tkWChar      : ;
       tkLString    : ;
       tkWString    : ;
       tkVariant    : ;
       tkArray      : ;
       tkRecord     : begin
                       //get the main definition
                        Definition.Items[sType].Add(Format('%s%s=record',[sIndent,lType.Name]));
                      end;

       tkInterface  :
                     begin
                       //get the main definition
                       if Assigned(lType.BaseType) then
                        Definition.Items[sType].Add(Format('%s%s=Interface(%s)',[sIndent,lType.Name,lType.BaseType.Name]))
                       else
                        Definition.Items[sType].Add(Format('%s%s=Interface',[sIndent,lType.Name]));

                     end;
       tkInt64      : ;
       tkDynArray   : ;
       tkUString    : ;
       tkClassRef   : ;
       tkPointer    : ;
       tkProcedure  : ;
     end;

       //add the fields
       if OnlyDeclarated then
         for lField in lType.GetDeclaredFields do
           AddField(Definition.Items[ArrVisibility[lField.Visibility]],lField)
       else
         for lField in lType.GetFields do
           AddField(Definition.Items[ArrVisibility[lField.Visibility]],lField);

       //add the methods
       if OnlyDeclarated then
         for lMethod in lType.GetDeclaredMethods do
           AddMethod(Definition.Items[ArrVisibility[lMethod.Visibility]],lMethod)
       else
         for lMethod in lType.GetMethods do
           AddMethod(Definition.Items[ArrVisibility[lMethod.Visibility]],lMethod);

       //add the Properties
       if OnlyDeclarated then
         for lProperty in lType.GetDeclaredProperties do
           AddProperty(Definition.Items[ArrVisibility[lProperty.Visibility]],lProperty)
       else
         for lProperty in lType.GetProperties do
           AddProperty(Definition.Items[ArrVisibility[lProperty.Visibility]],lProperty);

     for i:=Low(TMemberVisibility) to High(TMemberVisibility) do
      if Definition.Items[ArrVisibility[i]].Count>1 then
       Definition.Items[sType].AddStrings(Definition.Items[ArrVisibility[i]]);

     Definition.Items[sType].Add(sIndent+'end;');
     Result:=Definition.Items[sType].Text;
   finally
    Definition.free;
    ctx.free;
   end;
end;

Use in this way

//to dump a Class
DumpTypeDefinition(TypeInfo(TStringList));
//or
DumpTypeDefinition(TStringList.ClassInfo);

OutPut

the output is this

type
  TStringList=class(TStrings)
  private
   FList               : PStringItemList ;
   FCount              :Integer;
   FCapacity           :Integer;
   FSorted             :Boolean;
   FDuplicates         :TDuplicates;
   FCaseSensitive      :Boolean;
   FOnChange           :TNotifyEvent;
   FOnChanging         :TNotifyEvent;
   FOwnsObject         :Boolean;
   FEncoding           :TEncoding;
   FDefined            :TStringsDefined;
   FDefaultEncoding    :TEncoding;
   FDelimiter          :Char;
   FLineBreak          :string;
   FQuoteChar          :Char;
   FNameValueSeparator :Char;
   FStrictDelimiter    :Boolean;
   FUpdateCount        :Integer;
   FAdapter            :IStringsAdapter;
   FWriteBOM           :Boolean;
  public
   constructor Create;
   constructor Create(OwnsObjects: Boolean);
   class destructor Destroy;
   function Add(const S: string): Integer;
   function AddObject(const S: string; AObject: TObject): Integer;
   procedure Assign(Source: TPersistent);
   procedure Clear;
   procedure Delete(Index: Integer);
   procedure Exchange(Index1: Integer; Index2: Integer);
   function Find(const S: string; var Index: Integer): Boolean;
   function IndexOf(const S: string): Integer;
   procedure Insert(Index: Integer; const S: string);
   procedure InsertObject(Index: Integer; const S: string; AObject: TObject);
   procedure Sort;
   procedure CustomSort(Compare: TStringListSortCompare);
   constructor Create;
   class destructor Destroy;
   function Add(const S: string): Integer;
   function AddObject(const S: string; AObject: TObject): Integer;
   procedure Append(const S: string);
   procedure AddStrings(Strings: TStrings);
   procedure AddStrings(const Strings: TArray);
   procedure AddStrings(const Strings: TArray; const Objects: TAr
ray);
   procedure Assign(Source: TPersistent);
   procedure BeginUpdate;
   procedure Clear;
   procedure Delete(Index: Integer);
   procedure EndUpdate;
   function Equals(Strings: TStrings): Boolean;
   procedure Exchange(Index1: Integer; Index2: Integer);
   function GetEnumerator: TStringsEnumerator;
   function GetText: PWideChar;
   function IndexOf(const S: string): Integer;
   function IndexOfName(const Name: string): Integer;
   function IndexOfObject(AObject: TObject): Integer;
   procedure Insert(Index: Integer; const S: string);
   procedure InsertObject(Index: Integer; const S: string; AObject: TObject);
   procedure LoadFromFile(const FileName: string);
   procedure LoadFromFile(const FileName: string; Encoding: TEncoding);
   procedure LoadFromStream(Stream: TStream);
   procedure LoadFromStream(Stream: TStream; Encoding: TEncoding);
   procedure Move(CurIndex: Integer; NewIndex: Integer);
   procedure SaveToFile(const FileName: string);
   procedure SaveToFile(const FileName: string; Encoding: TEncoding);
   procedure SaveToStream(Stream: TStream);
   procedure SaveToStream(Stream: TStream; Encoding: TEncoding);
   procedure SetText(Text: PWideChar);
   function ToStringArray: TArray;
   function ToObjectArray: TArray;
   class destructor Destroy;
   procedure Assign(Source: TPersistent);
   function GetNamePath: string;
   constructor Create;
   procedure Free;
   class function InitInstance(Instance: Pointer): TObject;
   procedure CleanupInstance;
   function ClassType: TClass;
   class function ClassName: string;
   class function ClassNameIs(const Name: string): Boolean;
   class function ClassParent: TClass;
   class function ClassInfo: Pointer;
   class function InstanceSize: Integer;
   class function InheritsFrom(AClass: TClass): Boolean;
   class function MethodAddress(const Name: ShortString): Pointer;
   class function MethodAddress(const Name: string): Pointer;
   class function MethodName(Address: Pointer): string;
   function FieldAddress(const Name: ShortString): Pointer;
   function FieldAddress(const Name: string): Pointer;
   function GetInterface(const IID: TGUID; out Obj): Boolean;
   class function GetInterfaceEntry(const IID: TGUID): PInterfaceEntry;
   class function GetInterfaceTable: PInterfaceTable;
   class function UnitName: string;
   function Equals(Obj: TObject): Boolean;
   function GetHashCode: Integer;
   function ToString: string;
   function SafeCallException(ExceptObject: TObject; ExceptAddr: Pointer): HRESU
LT;
   procedure AfterConstruction;
   procedure BeforeDestruction;
   procedure Dispatch(var Message);
   procedure DefaultHandler(var Message);
   class function NewInstance: TObject;
   procedure FreeInstance;
   class destructor Destroy;
   property Duplicates: TDuplicates;
   property Sorted: Boolean;
   property CaseSensitive: Boolean;
   property OnChange: TNotifyEvent;
   property OnChanging: TNotifyEvent;
   property OwnsObjects: Boolean;
   property Capacity: Integer;
   property CommaText: string;
   property Count: Integer;
   property DefaultEncoding: TEncoding;
   property Delimiter: Char;
   property DelimitedText: string;
   property Encoding: TEncoding;
   property LineBreak: string;
   property QuoteChar: Char;
   property NameValueSeparator: Char;
   property StrictDelimiter: Boolean;
   property Text: string;
   property StringsAdapter: IStringsAdapter;
   property WriteBOM: Boolean;
  end;
//to dump a Class with only the declarateds fields, methods and properties
DumpTypeDefinition(TypeInfo(TStringList),True);
//or
DumpTypeDefinition(TStringList.ClassInfo,True);

the output

type
  TStringList=class(TStrings)
  private
   FList               : PStringItemList;
   FCount              :Integer;
   FCapacity           :Integer;
   FSorted             :Boolean;
   FDuplicates         :TDuplicates;
   FCaseSensitive      :Boolean;
   FOnChange           :TNotifyEvent;
   FOnChanging         :TNotifyEvent;
   FOwnsObject         :Boolean;
  public
   constructor Create;
   constructor Create(OwnsObjects: Boolean);
   class destructor Destroy;
   function Add(const S: string): Integer;
   function AddObject(const S: string; AObject: TObject): Integer;
   procedure Assign(Source: TPersistent);
   procedure Clear;
   procedure Delete(Index: Integer);
   procedure Exchange(Index1: Integer; Index2: Integer);
   function Find(const S: string; var Index: Integer): Boolean;
   function IndexOf(const S: string): Integer;
   procedure Insert(Index: Integer; const S: string);
   procedure InsertObject(Index: Integer; const S: string; AObject: TObject);
   procedure Sort;
   procedure CustomSort(Compare: TStringListSortCompare);
   property Duplicates: TDuplicates;
   property Sorted: Boolean;
   property CaseSensitive: Boolean;
   property OnChange: TNotifyEvent;
   property OnChanging: TNotifyEvent;
   property OwnsObjects: Boolean;
  end;
//to dump a record
DumpTypeDefinition(TypeInfo(TSysLocale));

the output look like this

type
  TSysLocale=record
  public
   DefaultLCID         :Integer;
   PriLangID           :Integer;
   SubLangID           :Integer;
   FarEast             :Boolean;
   MiddleEast          :Boolean;
  end;
//finally to dump an interface
DumpTypeDefinition(TypeInfo(IInterfaceList));


Leave a comment

Fun with Delphi RTTI – Building a Unit Dependency Tree

You can build a unit dependency tree, wich shows the direct dependency for each unit in your project using the New Rtti.

Here a short description of the algorithm used in this code.

  • For each Type(TRttiType) in the list do the following
  • check if the basetype exist in the same unit else add the unit to the list.
  • for each public field in the current type check if exist in the same unit else add the unit to the list.
  • for each method in the current type with an valid ReturnType check if exist in the same unit else add the unit to the list.
  • for each property in the current type check if exist in the same unit else add the unit to the list.

Limitations:

  • Only show direct dependency of the units (example if Unit A depends on Unit B and Unit B depends on UnitC, the tree will show wich the Unit A depends on only of Unit B)
  • Only supports Types with Rtti info.
  • Due to Rtti Limitations only supports public fields (TRttiField).
uses
Rtti,
Generics.Collections,
TypInfo;

procedure FillTreeUnits(TreeViewUnits:TTreeView);
var
  ctx      : TRttiContext;
  TypeList : TArray<TRttiType>;
  lType    : TRttiType;
  lMethod  : TRttiMethod;
  lProperty: TRttiProperty;
  lField   : TRttiField;
  Node     : TTreeNode;
  UnitName : string;
  RefUnit  : string;
  UnitsDict: TObjectDictionary<String, TStringList>;
  UnitList : TStringList;

      function GetUnitName(lType: TRttiType): string;
      begin
        {
        if lType.IsInstance then
        Result:=lType.UnitName
        else
        }
        Result := StringReplace(lType.QualifiedName, '.' + lType.Name, '',[rfReplaceAll]);
      end;

      //Check if exist the Unit in the Dictionary and if has a Unit Children in the associated list
      procedure CheckAndAdd(UnitName,RefUnit:string);
      begin
            if UnitName<>RefUnit then
             if not UnitsDict.ContainsKey(UnitName) then
             begin
               UnitList:=TStringList.Create;
               UnitList.Add(RefUnit);
               UnitsDict.Add(UnitName,UnitList);
             end
             else
             begin
               UnitList:=UnitsDict.Items[UnitName];
               if UnitList.IndexOf(RefUnit)<0 then
               UnitList.Add(RefUnit);
             end;
      end;

begin
  ctx       := TRttiContext.Create;
  UnitsDict := TObjectDictionary<String, TStringList>.Create([doOwnsValues]);
  TreeViewUnits.Items.BeginUpdate;
  try
    TreeViewUnits.Items.Clear;
    TypeList:= ctx.GetTypes;

      //Fill a Dictionary with all the units and the dependencies
      for lType in TypeList do
      begin
             //Search references to another units in the BaseType
             UnitName:=GetUnitName(lType);
             if Assigned(lType.BaseType) then
                CheckAndAdd(UnitName,GetUnitName(lType.BaseType));

             //Search references to another units in the public fields (due to RTTI limitations only works with public fields)
             for lField in lType.GetDeclaredFields do
             if Assigned(lField.FieldType) and (lField.FieldType.IsPublicType) then
                CheckAndAdd(UnitName,GetUnitName(lField.FieldType));

             //Search references to another units in the properties
             for lProperty in lType.GetDeclaredProperties do
             if Assigned(lProperty.PropertyType) then
                CheckAndAdd(UnitName,GetUnitName(lProperty.PropertyType));

             //Search references to another units in functions with ExtendedInfo (HasExtendedInfo=True)
             for lMethod in lType.GetDeclaredMethods do
             if (lMethod.HasExtendedInfo) and (lMethod.MethodKind in [mkFunction,mkClassFunction]) then
                CheckAndAdd(UnitName,GetUnitName(lMethod.ReturnType));
        end;

       //finally fill the treeview
       for UnitName in UnitsDict.Keys do
       begin
          UnitList:=UnitsDict.Items[UnitName];
          Node    :=TreeViewUnits.Items.Add(nil,UnitName);
           for RefUnit in UnitList do
             TreeViewUnits.Items.AddChild(Node,RefUnit);
       end;

  finally
    UnitsDict.Destroy;
    ctx.Free;
    TreeViewUnits.Items.EndUpdate;
  end;
end;

Finally the output for the source code


3 Comments

Fun with Delphi RTTI – Building a TreeView with all your classes

Do you remember the great posters  that came with older versions of delphi?

Now you can build your own tree of classes using the new rtti, here I leave the source code

uses
Rtti;

procedure FillTreeClasses(TreeViewClasses:TTreeView);

        //function to get the node wich match with the TRttiType
        function FindTRttiType(lType:TRttiType):TTreeNode;
        var
          i        : integer;
          Node     : TTreeNode;
        begin
           Result:=nil;
             for i:=0 to TreeViewClasses.Items.Count-1 do
             begin
                Node:=TreeViewClasses.Items.Item[i];
                if Assigned(Node.Data) then
                 if lType=TRttiType(Node.Data) then
                 begin
                  Result:=Node;
                  exit;
                 end;
             end;
        end;

        //function to get the node wich not match with the BaseType of the Parent
        function FindFirstTRttiTypeOrphan:TTreeNode;
        var
          i        : integer;
          Node     : TTreeNode;
          lType    : TRttiType;
        begin
           Result:=nil;
             for i:=0 to TreeViewClasses.Items.Count-1 do
             begin
                 Node :=TreeViewClasses.Items[i];
                 lType:=TRttiType(Node.Data);

                if not Assigned(lType.BaseType) then Continue;

                if lType.BaseType<>TRttiType(Node.Parent.Data) then
                begin
                   Result:=Node;
                   break;
                 end;
             end;
        end;

var
  ctx      : TRttiContext;
  TypeList : TArray<TRttiType>;
  lType    : TRttiType;
  PNode    : TTreeNode;
  Node     : TTreeNode;
begin
  ctx := TRttiContext.Create;
  TreeViewClasses.Items.BeginUpdate;
  try
    TreeViewClasses.Items.Clear;

      //Add Root, TObject
      lType:=ctx.GetType(TObject);
      Node:=TreeViewClasses.Items.AddObject(nil,lType.Name,lType);

      //Fill the tree with all the classes
      TypeList:= ctx.GetTypes;
      for lType in TypeList do
        if lType.IsInstance then
        begin
             if Assigned(lType.BaseType) then
             TreeViewClasses.Items.AddChildObject(Node,lType.Name,lType);
        end;

      //Sort the classes
      Repeat
         Node:=FindFirstTRttiTypeOrphan;
         if Node=nil then break;
         //get the location of the node containing the BaseType
         PNode:=FindTRttiType(TRttiType(Node.Data).BaseType);
         //Move the node to the new location
         Node.MoveTo(PNode,naAddChild);
      Until 1<>1;

  finally
    TreeViewClasses.Items.EndUpdate;
    ctx.Free;
  end;
end;

When you run this code the output will look like this


17 Comments

Fun with Delphi RTTI – Rtti Explorer Lite

Just for fun, I wrote an application (unit) to inspect the types (With RTTI info) used on my projects developed using Delphi 2010 and Delphi XE.

This project has these features

  • A hierarchical view of all types   With Rtti info, tha data is showed  following this structure Package->Unit->Type->Fields (Methods, properties, fields)
  • Show rtti information about any Rtti element
  • Search a particular Rtti Element
  • A hierarchical view of all existing classes in your project

Check the Screenshots

This slideshow requires JavaScript.

If you want use this unit in your own projects (Delphi 2010 or Delphi XE), simply add the unit MainRttiExpl to your project and run the procedure ShowRttiLiteExplorer

The source code is available in Github and the demo app is here.

Links updated, now using Dropbox ;).