Understanding RTTI (Run-time Type Information) in Delphi

Posted: (EET/GMT+2)

 

Run-time type information, or RTTI for short, is a very integral part of Delphi applications. For example, the VCL form and component loading mechanism relies heavily on RTTI. For the average programmer, RTTI is the same as the "is" and "as" Object Pascal operators. However, RTTI in Delphi is much more. For example, did you know that every published property has it's own RTTI entry, which allows you to set the property if you know its name?

As so often in programming, topics are better demonstrated than described. In this How To, you will learn how to create a very simple "script language" which allows you to set object properties for any object. However, RTTI is really much more as this How To can teach you, but I hope you will at least learn something new.

To understand the scripting language, you might first want to see what you can do with it. For example, let's say you have a script like this in a string list:

Var SL : TStringList;
...
SL.Add('Width = 100');
SL.Add('Height = 100');
SL.Add('Caption = Now a 100x100 form!');
SL.Add('Visible = 1');

Then you could simply have code like this:

procedure TForm1.ExecuteClick(Sender: TObject);
begin
  ExecuteTFormScript(Self,SL);
end;

And the Width, Height, Caption and Visible properties would be set like magic. Of course, the script language is far from perfect, and lacks expression evaluation for example. However, you have the code, and you are free to extend it for your own personal purposes.

If you want to understand RTTI, open up the TypInfo unit in some editor. This unit is located at your \Delphi 2.0\Source\VCL\ directory (provided that you have the source!)

The GetTypeData function in the unit allows you get information about almost any type, let it be an ordinary type like an integer, or a more complex type like a class. Note that not all types have RTTI: records for example don't.

The GetTypeData requires a parameter of PTypeInfo, which can be retrieved using the TypeInfo standard function. For example:

MyTypeData := GetTypeData(TypeInfo(Integer));

The GetEnumName and GetEnumValue functions are great when playing with enumerated types. For example, using GetEnumName is a good optimization trick.

For this post, we are mainly interested in the rest of the functions in the unit, such as SetOrdProp and SetFloatProp.

To evaluate a line in the script (every line is assumed to have only one instruction), we first get RTTI for the object we want to use. In the example code, we manipulate a TForm object, but this could be easily changed. Next, we parse the line, and then get futher property information using the GetPropInfo function.

With the data returned we can decide what to do. For example, if the line were "Width = 100", we know using RTTI, that the type of the Width property is integer, and as such we use SetOrdProp function to set the value to equal 100.

As such, the code to execute the given script is almost trivial:

Procedure ExecuteTFormScript(Instance : TForm; Script : TStringList);
Var
  I,J : Integer;
  S,T : String;
  PI  : PPropInfo;

Begin
  For I := 0 to Script.Count-1 do Begin
    S := Script[I];    { assume exact "property = value" format }
    T := S;
    J := Pos(' = ',S);
    SetLength(S,J-1);
    Delete(T,1,J+2);   { S is left part, T right part }
    PI := GetPropInfo(TypeInfo(TForm),S); { S must be a TForm property name }
    If (PI <> nil) Then Begin
      Case PI^.PropType^.Kind of
        tkInteger,tkChar,
        tkEnumeration      : SetOrdProp(Instance,PI,StrToInt(T));
        tkFloat            : SetFloatProp(Instance,PI,StrToFloat(T));
        tkString,tkLString : SetStrProp(Instance,PI,T);
      End;
    End
    Else ShowMessage('Invalid line: '+Script[I]);
  End;
End;

In the first few lines, we simply parse the given line so that the S variable will hold the property name, and T holds the value you want the property to have. (Note that the code requires exactly this syntax.)

After the parsing, we simply call the GetPropInfo to get RTTI about the property, and then depending of its type (Kind field), assign a new value to it.

As you can see, using RTTI isn't necessarily difficult. But again, RTTI is even more than this. For example, I've only used properties here, events -- and even methods -- could also used or called. This way or the other, I hope this How To has teached you to respect Delphi even more.

Happy hacking!