Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Getting the Unit Name which belongs to any type (TRttiType)

I need to get the name of the unit (namespace) of any TRttiType.

so far, I have tried the following.

1) using the PTypeData.UnitName, this solution works, but only when the TTypeKind is tkClass.

procedure ListAllUnits;
var
  ctx  : TRttiContext;
  lType: TRttiType;
  Units: TStrings;
begin
  Units:=TStringList.Create;
  try
    ctx := TRttiContext.Create;
    for lType in ctx.GetTypes do
     if lType.IsInstance then //only works for classes
      if Units.IndexOf(UTF8ToString(GetTypeData(lType.Handle).UnitName))<0 then
      Units.Add(UTF8ToString(GetTypeData(lType.Handle).UnitName));
  Writeln(Units.Text);
  finally
    Units.Free;
  end;
end;

2) Parsing the QualifiedName property, This solution works ok until now, but I'm not very happy with it.

procedure ListAllUnits2;

  function GetUnitName(lType: TRttiType): string;
  begin
    Result := StringReplace(lType.QualifiedName, '.' + lType.Name, '',[rfReplaceAll])
  end;

var
  ctx: TRttiContext;
  lType: TRttiType;
  Units: TStrings;
begin
  Units := TStringList.Create;
  try
    ctx := TRttiContext.Create;
    for lType in ctx.GetTypes do
      if Units.IndexOf(GetUnitName(lType)) < 0 then
        Units.Add(GetUnitName(lType));
    Writeln(Units.Text);
  finally
    Units.Free;
  end;
end;

the question is, exist another reliable way to get the unit name of any TRttiType?

like image 886
RRUZ Avatar asked Oct 01 '10 23:10

RRUZ


2 Answers

It doesn't look like there is. The RTTI comes out of the TTypeData structure, which only has a UnitName field explicitly declared for specific types. (This predates D2010 and extended RTTI.) Your #2 looks like the best way to get it, and is probably how a hypothetical TRTTIObject.UnitName would calculate it if they were to put one in.

like image 163
Mason Wheeler Avatar answered Sep 22 '22 05:09

Mason Wheeler


The information is there but Parsing the Qualified Name is currently the best way to get to it.

If you want to do it the hard way you can by:

In the system.pas unit you have a variable LibModuleList: PLibModule = nil; that contains the list of loaded modules. This is the pointer to the Raw RTTI Information that can be used without RTTI.pas. You could iterate all the raw information get determine the unit name.

The key values of the TLibModule are:

  PLibModule = ^TLibModule;
  TLibModule = record
    Next: PLibModule;  { Linked List of Loaded Modules)
    Instance: LongWord;
    ...
    TypeInfo: PPackageTypeInfo; { List of contained Package Information }
    ...
  end;

Using the TypeInfo: PPackageTypeInfo; you get access to

  PPackageTypeInfo = ^TPackageTypeInfo;
  TPackageTypeInfo = record
    TypeCount: Integer;
    TypeTable: PTypeTable;
    UnitCount: Integer;
    UnitNames: PShortString; { concatenation of Pascal strings, 
                               one for each unit }
  end;

Then there is TypeTable which contains the information to get to PTypeInfo.
sequence.

  TTypeTable = array[0..MaxInt div SizeOf(Pointer) - 1] of Pointer;
  PTypeTable = ^TTypeTable;

An example of how all this works can be found in Rtti.pas TPackage.MakeTypeLookupTable is the key method. This method also shows that QualifiedName always will contain the UnitName. As such your original method of parsing QualfiedName can be depended on.

like image 42
Robert Love Avatar answered Sep 19 '22 05:09

Robert Love