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.
For 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 hint ‘gets 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: