Delphi RTTI and the Linker

Introduction

Like C# and Java, Delphi makes rich runtime type information available to developers, facilitating the use of reflection. Reflection is just another paradigm, but incredibly useful for performing certain tasks.
initsFor example, one of the tasks I use reflection for is to discover classes decorated with a SystemInitializer attribute on application startup.

Instead of putting all the configuration information into a single class, I’ll use several,  each responsible for a specific initialization need.  The classes are stored in a common initialization folder.

A SystemInitializer class is executed to bootstrap the application. It identifies initialization classes, sorts them by priority, then executes them in order. This makes it very simple to add or maintain initialization code. My motivation for this approach came from Ruby of Rails.

Another task I often use reflection for is to identify command classes and register them with a command registry. This is a nice way to decouple commands from the registration process. Adding a command to the registry is as simple as declaring it.

Reflection

This magic relies upon two features: Attributes and the RTTI classes. To declare an attribute in Delphi, inherit from TCustomAttribute:

  { Identifies a command to the registry }
  TCommandAttribute = class(TCustomAttribute)
  private
    FText: string;
    FHint: string;
  public
    constructor Create(const AText, AHint: string);

    property Text: string read FText write FText;
    property Hint: string read FHint write FHint;
  end;


This attribute has two properties, Text and Hint. It can now be used to decorate a class:

  [TCommandAttribute('roster', 'gets roster information for the current user.')]
  TRosterCommand = class(TCommand)
  public
    constructor Create;
    procedure Execute(AClient: TXmppClient); override;
  end;

In the above example, the properties of the command attribute instance for the TRosterCommand class will be initialized with the text roster‘ and the hintgets roster information for the current user.

Auto-registering our Commands

To discover the classes decorated with the TCommandAttribute at runtime, we now use the RTTI. This is done once on start up, so the performance costs are small. Although reflection is a lot slower than directly coding the same operations, there’s not really a noticable delay in most cases, especially on smaller code bases. Delphi’s RTTI is pretty quick.

constructor TCommandRegistry.Create;
var
  Context: TRttiContext;
  RttiType: TRttiType;
  Attribute: TCustomAttribute;
  CommandAttribute: TCommandAttribute;

begin
  FCommands := TDictionary<string, string>.Create(TIStringComparer.Ordinal);
  FHints := TDictionary<string, string>.Create(TIStringComparer.Ordinal);

  for RttiType in Context.GetTypes() do begin
    for Attribute in RttiType.GetAttributes() do begin
      if Attribute is TCommandAttribute then begin
        CommandAttribute := TCommandAttribute(Attribute);
        FCommands.Add(CommandAttribute.Text, RttiType.QualifiedName);
        FHints.Add(CommandAttribute.Text, CommandAttribute.Hint);
      end;
    end;
  end;
end;

Linker Woes

In the above code, I am searching for matching types in the current application and registering them with internal data structures. It’s that simple…or so I thought.

For some reason, some commands were not being identified via RTTI. According to the official documentation I was doing everything correctly:

“To obtain a list of all the types declared in an application, use the TRttiContext.GetTypes method. It enumerates all publicly visible types (the ones declared in the interface section of units) and returns an array of TRttiType objects.”

for RttiType in Context.GetTypes() do
    if RttiType.Name.Contains('Command') then
      Writeln(RttiType.Name);

But my debugging code verified certain commands were not being discovered…

After a bit of head scratching, I realized the Linker must have determined that the class isn’t used, as I am not directly referencing it anywhere, so it gets unceremoniously dropped. This doesn’t happen in C# or Java, which is what threw me off guard in this case. I just assumed the presence of the attribute would be a hint to the compiler.

Compiler Directives

My thoughts turned to perhaps the existence of a compiler directive I could use to tell the compiler not to drop a type during the linking process. It turns out there is such a directive:

{$STRONGLINKTYPES ON}

However, you have to include it in the project .dpr file, and it is global in effect. Effectively, the Linker drops nothing, which unfortunately causes code bloat. So this is not a solution. The idea of so much bloat makes my eyes twitch.

Traditional Approach

The next consideration was to abandon this usage of reflection completely, and use the traditional approach to registering classes with a container or registry in the initialization section of the unit containing the command class. This is actually quite neat, you see it in frameworks like DUnitX.

initialization
  TDUnitX.RegisterTestFixture(TestTCalc);

But what’s the point in having Attributes and RTTI if you can’t use them? There must be a way.

Another Way, Combining the Two Approaches

In the base class TCommand add a public method that does nothing:

class procedure Register;

In each command unit, for example uRosterCommand, add:

initialization
  TRosterCommand.Register;

Success

Now the type is included by the linker, hence detected by RTTI, and it is decoupled from the registry and the registration process. Success!

In fact, it’s probably worth creating a global utility method that can be used anywhere for this kind of purpose, perhaps named RegisterWithLinker or something similar.

initialization
  RegisterWithLinker(TRosterCommand);

Lazy Command Creation

Finally, we create a command on demand. A command may contain state, so it is never shared, a new instance is always created. If required, we could add a property to the attribute and a little logic in our command registry to override this behavior. But that’s not needed in this case:

function TCommandRegistry.GetCmd(AName: String): TCommand;
var
  Context: TRttiContext;
  RttiType: TRttiInstanceType;
begin
  TEnsure.IsTrue(FCommands.ContainsKey(AName), 'Command not found error: ' + AName);
  RttiType := Context.FindType(FCommands[AName]).AsInstance;
  Result := TCommand(RttiType.GetMethod('Create').Invoke(RttiType.MetaclassType, []).AsObject);
end;

Conclusion

In this post I’m not advocating the usage of reflection over any other technique. Indeed, as Delphi is a RAD environment, in a GUI application I may not use this approach, it depends on context.

Delphi has several good initialization mechanisms built-in already, including an Initialization Section in each unit, and Class Constructors for each type. Reflection is just another tool in the toolbox. When reflection may be useful, Delphi’s reflection system is a very capable mechanism. 

I essentially learn on the go, so this post is a future reference for me. I hope it may be helpful to someone else. For more information on Delphi’s RTTI, see here.

Chapter 16 in Marco Cantu’s Object Pascal Handbook is dedicated to reflection and attributes, and there are videos available on Embarcadero Academy, and YouTube.

Daniele Spinetti and Daniele Teti have an interesting example of using attributes for validation of business objects in their Delphi Cookbook.

Chapter 12 of Chris Rolliston’s classic Delphi XE2 Foundations is dedicated to RTTI.

Here is a snippet from the chapter Mind Your Language in Pawel Glowacki’s excellent book, Expert Delphi. Here he demonstrates identifying attributes on methods as well as types:

uses RTTI, uDocAttribute, uMySuperClass;

procedure TFormDemo.Button1Click(Sender: TObject);
var
  ctx: TRttiContext;
  t: TRttiType;
  m: TRttiMethod;
  a: TCustomAttribute;
begin
  ClearLog;

  ctx := TRttiContext.Create;

  try
    t := ctx.GetType(TMySuperClass);

    for a in t.GetAttributes do
      if a is DocAttribute then
        Log(Format('Type = %s; Attribute = %s, URL = %s', 
  	    [TMySuperClass.ClassName, a.ClassName, DocAttribute(a).URL]));

    for m in t.GetMethods do
      for a in m.GetAttributes do
        if a is DocAttribute then
          Log(Format('Type = %s; Method = %s; Attribute = %s, URL = %s',
    	      [TMySuperClass.ClassName, m.Name, a.ClassName, DocAttribute(a).URL]));
  finally
    ctx.Free;
  end;
end;

Sundry

Back to my application, for full context, here’s the main loop which uses the Command Registry:

procedure TKonsole.Execute;
var
  Text: string;
  Cmd: TCommand;
begin
  if not Connect then exit;

  while Text <> 'quit' do begin
    Writeln;
    Write('> ');

    Readln(Text);
    if string.IsNullOrEmpty(Text) then continue;

    Text := Text.Trim();

    Cmd := FParser.Execute(Text);
    try
      Cmd.Execute(FClient);
    finally
      FreeAndNil(Cmd);
    end;
  end;

  Disconnect;
end;

The Parser uses  the Command Registry to identify and create commands, if the user enters an invalid command, a non-registered special purposed TInvalidCommand is returned by the parser, which on execution simply displays “Invalid Command, type help for more information”

Lastly, the structure of the project containing the decoupled commands added to date:

konsole

Advertisement

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s