Tuesday, September 22, 2009

TValue in Depth

TValue is one of the key Types in the new RTTI System. We already covered some of the basics in the introduction to TValue. It's now time to pull back the covers and explorer how it was designed so you can exploit the entire power of TValue.


Before we get too far, lets take a look at the field in the interface of TValue.


TValue = record
...
private
FData: TValueData;
end;



Since TValue can store data from any type. I was interested in how this accomplished, it proved useful
in determining how to store data from various unknown types I knew I would be throwing at it in the future.

TValueData is defined as:

TValueData = record
FTypeInfo: PTypeInfo;
// If interface, then a hard-cast of interface to IInterface.
// If heap data (such as string, managed record, array, etc.) then IValueData
// hard-cast to IInterface.
// If this is nil, then the value hasn't been initialized and is empty.
FHeapData: IInterface;
case Integer of
0: (FAsUByte: Byte);
1: (FAsUWord: Word);
2: (FAsULong: LongWord);
3: (FAsObject: TObject);
4: (FAsClass: TClass);
5: (FAsSByte: Shortint);
6: (FAsSWord: Smallint);
7: (FAsSLong: Longint);
8: (FAsSingle: Single);
9: (FAsDouble: Double);
10: (FAsExtended: Extended);
11: (FAsComp: Comp);
12: (FAsCurr: Currency);
13: (FAsUInt64: UInt64);
14: (FAsSInt64: Int64);
15: (FAsMethod: TMethod);
end;


It's just a variant record, that takes 24 bytes of memory.

The key parts are FTypeInfo, and then either FHeapData or one of the variant parts
would be use to store the data.

Knowing this how this is stored helps in understanding the low level routines to set and access
the data stored in a TValue.


TValue = record
...
public
...
// Low-level in
class procedure Make(ABuffer: Pointer; ATypeInfo: PTypeInfo; out Result: TValue); overload; static;
class procedure MakeWithoutCopy(ABuffer: Pointer; ATypeInfo: PTypeInfo; out Result: TValue); overload; static;
class procedure Make(AValue: NativeInt; ATypeInfo: PTypeInfo; out Result: TValue); overload; static;

// Low-level out
property DataSize: Integer read GetDataSize;
procedure ExtractRawData(ABuffer: Pointer);
// If internal data is something with lifetime management, this copies a
// reference out *without* updating the reference count.
procedure ExtractRawDataNoCopy(ABuffer: Pointer);
function GetReferenceToRawData: Pointer;
function GetReferenceToRawArrayElement(Index: Integer): Pointer;
...
end;


Basically, you can use Make() to place any data with type information into a TValue.

Here is an example that placed an Integer and TRect in


program Project12;
{$APPTYPE CONSOLE}
uses SysUtils, Windows, TypInfo,Rtti;

var
IntData : Integer;
IntValue : TValue;

RecData : TRect;
RecValue : TValue;

begin
IntData := 1234;
//Granted it's easier to call IntValue := IntData; but this is an example.
TValue.Make(@IntData,TypeInfo(Integer),IntValue);
Writeln(IntValue.ToString);
RecData.Left := 10;
RecData.Right := 20;
TValue.Make(@RecData,TypeInfo(TRect),RecValue);
Writeln(RecValue.ToString);
readln;
end.

Output:

1234
(record)


When dealing with Deserialization issues I realized I had to recreate record structures when I did not know anything but the TypeInfo. This can be done by calling:


TValue.Make(nil,TypeInfoVar,OutputTValue);


Extracting data can also be done using the low level routines, here is an example of using ExtractRawData.


program Project12;
{$APPTYPE CONSOLE}
uses SysUtils, Windows, TypInfo,Rtti;

var
RecData : TRect;
RecDataOut : TRect;
RecValue : TValue;

begin
RecData.Left := 10;
RecData.Right := 20;
TValue.Make(@RecData,TypeInfo(TRect),RecValue);

RecValue.ExtractRawData(@RecDataOut);
Writeln(RecDataOut.Left);
Writeln(RecDataOut.Right);

readln;
end.

Output:

10
20


You can use things like GetReferenceToRawData() with the SetValue and GetValue on records.


program Project12;
{$APPTYPE CONSOLE}
uses SysUtils, Windows, TypInfo,Rtti;

var
RecData : TRect;
RecValue : TValue;
Ctx : TRttiContext;

begin
Ctx := TRttiContext.Create;
// Create empty record structure
TValue.Make(nil,TypeInfo(TRect),RecValue);
// Set the Left and Right Members, using the pointer to the Record
Ctx.GetType(TypeInfo(TRect)).GetField('Left').SetValue(RecValue.GetReferenceToRawData,10);
Ctx.GetType(TypeInfo(TRect)).GetField('Right').SetValue(RecValue.GetReferenceToRawData,20);
// Extract the record to report the results.
RecValue.ExtractRawData(@RecData);
Writeln(RecData.Left);
Writeln(RecData.Right);
readln;
Ctx.Free;
end.

Output:

10
20


These little examples show how to deal with types that you don't know about at compile time. However, sometimes you do know the type you will be working with at compile time. When this is know it becomes much easier to to work with using a the Generic/Parametrized Type functions that TValue Provides:


class function From<T>(const Value: T): TValue; static;
function AsType<T>: T;
function TryAsType<T>(out AResult: T): Boolean;
function Cast<T>: TValue; overload;


The following example shows From<T>() IsType<T>() and AsType<T>() in use.


program Project12;
{$APPTYPE CONSOLE}
uses SysUtils, Windows, TypInfo,Rtti;

var
RecData : TRect;
RecDataOut : TRect;
RecValue : TValue;
begin
RecData.Left := 10;
RecData.Right := 20;

RecValue := TValue.From<TRect>(RecData);

Writeln(RecValue.IsType<TRect>);

RecDataOut := RecValue.AsType<TRect>;

Writeln(RecDataOut.Left);
Writeln(RecDataOut.Right);
readln;
end.

Output:

TRUE
10
20


There are also other function on TValue that help you with Array Types.


function GetArrayLength: Integer;
function GetArrayElement(Index: Integer): TValue;
procedure SetArrayElement(Index: Integer; const AValue: TValue);


It should be noted that dynamic arrays that are declared like this are currently not
supported:


Var
IntArray : Array of Integer;


But if you can your code to be like this they work fine.


type
TIntArray = Array of Integer;

var
I : TIntArray;
// or
I : TArray<Integer>; {defined in System.pas as: TArray<T> = array of T;}


Another small gotcha is that TValue.FromVariant() is misleading.

I thought it meant that I would be taking a Variant and stuffing it into the TValue, however you will find that it really is not doing that, it's storing the data using originating type, the following code shows how it behaves.


program Project12;
{$APPTYPE CONSOLE}
uses SysUtils, TypInfo,Rtti;

var
vExample : Variant;
Value : TValue;
begin
vExample := 'Hello World';
Value := TValue.FromVariant(vExample);
writeln(GetEnumName(TypeInfo(TTypeKind),Ord(Value.Kind)));
Writeln(value.ToString);

vExample := 1234;
Value := TValue.FromVariant(vExample);
writeln(GetEnumName(TypeInfo(TTypeKind),Ord(Value.Kind)));
Writeln(value.ToString);

readln;
end.

Output:

tkUString
Hello World
tkInteger
1234


If you want to store a Variant into a TValue you can use this method

program Project12;
{$APPTYPE CONSOLE}
uses SysUtils, TypInfo,Rtti;

var
vExample : Variant;
Value : TValue;
begin
vExample := 'Hello World';
Value := TValue.From(vExample);
writeln(GetEnumName(TypeInfo(TTypeKind),Ord(Value.Kind)));
Writeln(value.AsType);

vExample := 1234;
Value := TValue.From(vExample);
writeln(GetEnumName(TypeInfo(TTypeKind),Ord(Value.Kind)));
Writeln(value.AsType);

readln;
end.

Output:

tkVariant
Hello World
tkVariant
1234


There are also other ways to work with TValue that I just don't have the time to cover. I recommend opening up Rtti.pas and exploring the interface to see everything that is available. The items I failed to cover are fairly straight forward.

I hope this give's you a good taste of how TValue works.

RTTI Article List

Thursday, September 17, 2009

Exploring TRttiMember Descendants in depth (Part II) Methods

Today I will be covering TRttiMethod. it is the biggest reason I am so happy with the new RTTI in Delphi 2010. In prior versions of Delphi dynamic method invocation was a black art, that had many limitations. It was painful and was dependent on how your code was compiled. The default behavior of the VCL classes had it disabled. Prior to Delphi 2010 you had to know way too much about the internals of method information structure {$METHODINFO}, to invoke a method dynamically.

Now in Delphi 2010 with default behavior is that RTTI Information for methods is generated for the public and published sections. I don't need to know a thing about how the RTTI information is being stored. There is a very elegant and easy to use API to dynamically query and invoke methods. Which works for all the existing VCL classes.

You must understand TRttiMethod and TValue you have the ability to dynamically invoke most any method.

TRttiMethod descends from TRttiMember, besides the Name & Visibility which was defined in TRttiMember, we may need to know many other aspects about a given method.

A few key properties are defined to allow easy access to this information.


TypInfo.pas
TMethodKind = (mkProcedure, mkFunction, mkConstructor, mkDestructor,
mkClassProcedure, mkClassFunction, mkClassConstructor, mkClassDestructor,
mkOperatorOverload,
{ Obsolete }
mkSafeProcedure, mkSafeFunction);

TCallConv = (ccReg, ccCdecl, ccPascal, ccStdCall, ccSafeCall);

Rtti.pas

TDispatchKind = (dkStatic, dkVtable, dkDynamic, dkMessage, dkInterface);

In TRttiMember
property MethodKind: TMethodKind read GetMethodKind;
property DispatchKind: TDispatchKind read GetDispatchKind;

property CodeAddress: Pointer read GetCodeAddress;
property IsConstructor: Boolean read GetIsConstructor;
property IsDestructor: Boolean read GetIsDestructor;
property IsClassMethod: Boolean read GetIsClassMethod;
// Static: No 'Self' parameter
property IsStatic: Boolean read GetIsStatic;

// Vtable slot for virtual methods.
// Message index for message methods (non-negative).
// Dynamic index for dynamic methods (negative).
property VirtualIndex: Smallint read GetVirtualIndex;
property CallingConvention: TCallConv read GetCallingConvention;
property CodeAddress: Pointer read GetCodeAddress;


Now this information is fairly useless if you don't know the parameters of a given method and a possible result type, there is a property and a function to provide you with this information.


function GetParameters: TArray<TRttiParameter>; virtual; abstract;
property ReturnType: TRttiType read GetReturnType;


Here is an example of looking at these, it looks at the TStringList.AddObject() Method


program Project12;
{$APPTYPE CONSOLE}
uses
Classes, Rtti;
var
ctx : TRttiContext;
t : TRttiType;
Param : TRttiParameter;
AddObjectMethod : TRttiMethod;
begin
ctx := TRttiContext.Create;
t := ctx.GetType(TStringList.ClassInfo);
AddObjectMethod := t.GetMethod('AddObject');
for Param in AddObjectMethod.GetParameters do
begin
Writeln(Param.ToString);
end;
Writeln('Returns:', AddObjectMethod.ReturnType.ToString );
readln;
ctx.Free;
end.

Output:

S: string
AObject: TObject
Returns:Integer


TRttiParameter is contains the information you need to know about a given parameter.


typInfo.pas
...
TParamFlag = (pfVar, pfConst, pfArray, pfAddress, pfReference, pfOut, pfResult);
{$EXTERNALSYM TParamFlag}
TParamFlags = set of TParamFlag;
...

Rtti.pas

TRttiNamedObject
...
property Name: string read GetName;
...

TRttiParameter = class(TRttiNamedObject)
...
function ToString: string; override;
property Flags: TParamFlags read GetFlags;
// ParamType may be nil if it's an untyped var or const parameter.
property ParamType: TRttiType read GetParamType;
...



Now that you have access to the information for a given method you can call it.

In this example you can see two calls, on to constructor and another to the Add() method of TStringList.


program Project12;
{$APPTYPE CONSOLE}
uses
Classes, Rtti, TypInfo;
var
ctx : TRttiContext;
t : TRttiType;
Param : TRttiParameter;
AddMethod : TRttiMethod;
SL : TValue; // Contains TStringList instance

begin
ctx := TRttiContext.Create;
t := ctx.GetType(TStringList.ClassInfo);
// Create an Instance of TStringList
SL := t.GetMethod('Create').Invoke(t.AsInstance.MetaclassType,[]);
// Invoke "Add" and return string representatino of result.
Writeln(t.GetMethod('Add').Invoke(SL,['Hello World']).ToString);
// Write out context.
Writeln((sl.AsObject as TStringList).Text);
readln;
ctx.Free;
end.

Output:

0
Hello World


There are three overloaded versions of Invoke

function Invoke(Instance: TObject; const Args: array of TValue): TValue; overload;
function Invoke(Instance: TClass; const Args: array of TValue): TValue; overload;
function Invoke(Instance: TValue; const Args: array of TValue): TValue; overload;


In the above example you can see I used the TValue and TClass versions, now lets look at a more complex situation using the last overload.

When dealing with method calls that update the parameters, such as those declared with the "var" syntax, the the original array you pass in of parameters is updated with the correct changes.

This program demonstrates how this works:


program Project12;
{$APPTYPE CONSOLE}
uses
Classes, Rtti, TypInfo;
const
AreYouMyMotherISBN = '0-679-89047-5';
type
TBookQuery = class(TObject)
public
function FindBook(ISBN : String;var Title : String) : Boolean;
end;

function TBookQuery.FindBook(ISBN : String;var Title : String) : Boolean;
begin
Writeln('Checking:',ISBN);
// Find one of the books, I get to read every night :-)
if ISBN = AreYouMyMotherISBN then
begin
result := true;
Title := 'Are you my Mother?'
end
else
begin
Title := '';
result := false;
end;
end;

var
ctx : TRttiContext;
BQ : TBookQuery;
Args : Array Of TValue;
Param : TRttiParameter;
FindBook : TRttiMethod;
SL : TValue; // Contains TStringList instance

begin
ctx := TRttiContext.Create;
BQ := TBookQuery.Create;
FindBook := Ctx.GetType(TBookQuery.ClassInfo).GetMethod('FindBook');

SetLength(args,2);
Args[0] := '123'; // an ISBN that won't be found
Args[1] := '';


// Invoke the Method
if FindBook.Invoke(BQ,Args).AsBoolean then
writeln(args[1].ToString)
else
writeln('Not Found');

SetLength(args,2);
Args[0] := AreYouMyMotherISBN; // an ISBN that will be found
Args[1] := '';

// Invoke the Method
if FindBook.Invoke(BQ,Args).AsBoolean then
writeln(args[1].ToString)
else
writeln('Not Found');


readln;
BQ.Free;
ctx.Free;
end.

Output:

Checking:123
Not Found
Checking:0-679-89047-5
Are you my Mother?


Updated:
Well I neglected to be complete when it came to how you can query and access TRttiMethods. I only showed "GetMethod()" on TRttiType but there are four
ways to get information.


// Get's all of the methods on a given class, with the declared ones first.
function GetMethods: TArray<TRttiMethod>; overload; virtual;
// Will return the first method it finds with the given name
function GetMethod(const AName: string): TRttiMethod; virtual;
// Will return all of the methods it finds with a given method, so you can deal with overloads.
function GetMethods(const AName: string): TArray<TRttiMethod>; overload; virtual;
// Will only get methods declared on the given class, and not on parents.
function GetDeclaredMethods: TArray<TRttiMethod>; virtual;


That's really all there is to using TRttiMethod, if you tried to do this in a prior version of Delphi I am sure you will be very happy with the changes. If you never tried don't, just move to Delphi 2010 and use the new functionality. Now you might be why would I use this, well hopefully you will get a taste of that in the practical application articles that are coming soon. However, in my next Article I will cover TValue in Depth.


RTTI Article List

Wednesday, September 16, 2009

TRttiContext.Create() & TRttiContext.Free()

I thought I needed to take a break from the normal articles to explain why I call
TRttiContext.Create() and TRttiContext.Free() when in fact you don't need too.

Yes you don't need to do it, but yet I do... Why?

First off lets look at the implementation of both.


class function TRttiContext.Create: TRttiContext;
begin
Result.FContextToken := nil;
end;

procedure TRttiContext.Free;
begin
FContextToken := nil;
end;


At first glance there is nothing special, FContextToken is set to NIL in both cases.

So what does setting FContextToken to NIL really do?

We all know that Delphi currently does not have any Garbage collection mechanism. As such having a rich RTTI library that is based on Objects could be problematic.

In my code an I able to such cool things as...

c.GetType().GetField().FieldType.ToString

Without having to set temporary values for each returned object, to free them.
How is this possible without a garbage collector?

Well under the hood you will find TRttiPool that contains all of the RTTI object that are created.
When this pool is freed all of the objects created during RTTI calls are then freed.

The construction and destruction of this pool is controlled by TPoolToken which is the Interface that is stored in FContextToken.

When the TPoolToken is created and freed, the PoolRefCount is maintained, when it reaches zero the TRttiPool is freed.

So why call .Create() and .Free()

So lets start with .Create()

So it's not like calling this code is a huge overhead, yes sure a record is managed and as such it will be initialized, making the call to set FContextToken redundant. well sort of... Although I don't intend every create a pointer to TRttiContext, its possible have the memory space not be initialized as expected, unless you are calling .Create.

I also can't be sure that the implementation will remain as simple. It is very possible that additional code could be called at this point. Keeping my code safe in the future is always critical to me in my designs.

Moving on to .Free()

Well, I like to keep my memory foot print clean. Sure when the TRttiContext variable goes out of scope the exact same behavior will occur.

Very early on when playing with new RTTI I inadvertently caused an access violation in a TCustomAttribute Descendant destructor. With the call to .free() it was easier to see what had caused the problem. Instead of having it occur after the destruction of the Object that had the TRttiContext declared as a field.

And just to stop the obvious comment.... Yes, I know the documentation say's you don't have to call .Free() And implies that the technique is important because it insures that all RTTI Objects are cached and reused, well this would only be true of TRttiContext was declared as a global variable, which I think is a very BAD idea.

Instead I think you should keep your TRttiContext around as long as you need it, but free it when the time is correct.

In short... I find it a matter of personal preference.

Tuesday, September 15, 2009

Exploring TRttiMember Descendants in depth (Part I) Properties and Fields

Some types such as classes and records contain "members" such as field, properties and methods.

RTTI information starts with the TRttiMember which provides 4 bits of information about each Member.


  1. The name

  2. Visibility (private,protected,public,published)

  3. The type that the member is associated with

  4. Attributes associated with that member



This example code shows how to access each of these.


program Project4;

{$APPTYPE CONSOLE}

uses
SysUtils, Rtti, TypInfo;

type
TTest = class(TCustomAttribute)
end;

TBook = class(TObject)
private
FTitle: String;
public
[TTest]
property Title : String read FTitle write FTitle;
end;

var
c : TRttiContext;
m : TRttiMember;

{ TExample }


begin
c := TRttiContext.Create;
m := c.GetType(TBook).GetProperty('Title');
Writeln('Name:',m.Name);
Writeln('Visibility:',GetEnumName(TypeInfo(TMemberVisibility),ord(m.Visibility)));
WriteLn('Parent:',m.Parent.ToString);
Writeln('First Attribute:',m.GetAttributes[0].ToString);
writeln;
m := c.GetType(TBook).GetField('FTitle');
WriteLn('Name:',m.Name);
WriteLn('Visibility:',GetEnumName(TypeInfo(TMemberVisibility),ord(m.Visibility)));
WriteLn('Parent:',m.Parent.ToString);
readln;
c.Free;
end.

Output:

Name:Title
Visibility:mvPublic
Parent:TBook
First Attribute:TTest

Name:FTitle
Visibility:mvPrivate
Parent:TBook



The two basic TRttiMember descendants I want to cover in this blog post is TRttiField and TRttiPropery. Both descendants allow you to get and set the values on an Instance of the given type. They both provide SetValue() and GetValue() methods.

The following code demonstrates how to use both. If you missed my Introduction to TValue
I suggest you take some time to read it as it explains some of the "Magic" behind what is going on here.


program Project10;
{$APPTYPE CONSOLE}

uses
SysUtils, Rtti, TypInfo;

type

TBook = class(TObject)
private
FTitle: String;
public
property Title : String read FTitle write FTitle;
end;

var
c : TRttiContext;
p : TRttiProperty;
f : TRttiField;
book : TBook;
v : TValue;
begin
book := TBook.Create;
try
c := TRttiContext.Create;

p := c.GetType(TBook).GetProperty('Title');
p.SetValue(Book,'Go, Dog, Go!');
v := p.GetValue(Book);
Writeln('Title:',v.ToString);

f := c.GetType(TBook).GetField('FTitle');
f.SetValue(Book,'Green Eggs and Ham');
v := f.GetValue(Book);
Writeln('FTitle:',v.ToString);

readln;

c.Free;

finally
Book.Free;
end;
end.

Output:

Title:Go, Dog, Go!
FTitle:Green Eggs and Ham


With Properties you have to worry about if they are readable or writable, as attempting to call SetValue on a property that is not writable will produce an EPropReadOnly Exception. TRttiProperty has two properties "isReadable" and 'isWritable" that allow you to discover how the property was declared. The following code demonstrates how it works.


program Project10;
{$APPTYPE CONSOLE}

uses
SysUtils, Rtti, TypInfo;

type

TBook = class(TObject)
private
FTitle: String;
FUgly: String;
FAuthor: String;
public
property Title : String read FTitle write FTitle;
property Author : String read FAuthor;
property Ugly : String write FUgly;
end;

var
c : TRttiContext;
p : TRttiProperty;

begin
c := TRttiContext.Create;
try
p := c.GetType(TBook).GetProperty('Title');
WriteLn('Name:',p.Name);
Writeln('IsReadable:',p.IsReadable);
Writeln('IsWritable:',p.IsWritable);
writeln;
p := c.GetType(TBook).GetProperty('Author');
WriteLn('Name:',p.Name);
Writeln('IsReadable:',p.IsReadable);
Writeln('IsWritable:',p.IsWritable);
writeln;
p := c.GetType(TBook).GetProperty('Ugly');
WriteLn('Name:',p.Name);
Writeln('IsReadable:',p.IsReadable);
Writeln('IsWritable:',p.IsWritable);
readln;
finally
c.Free;
end;
end.

Output:

Name:Title
IsReadable:TRUE
IsWritable:TRUE

Name:Author
IsReadable:TRUE
IsWritable:FALSE

Name:Ugly
IsReadable:FALSE
IsWritable:TRUE



That pretty much wraps it up for TRttiField and TRttiProperty, in the next
post I will cover TRttiMethod.

RTTI Article List

Monday, September 14, 2009

Introduction to TValue

TValue is a new record structure defined in Rtti.pas, it provides the ability to store the value, and type information of an instance of any type. This can be seen in the following code.

For example, in the following program, you can see that the TypeInfo property on
a TValue is the same as the what the TypeInfo() function would return for that type.


program Project10;
{$APPTYPE CONSOLE}

uses
SysUtils, Rtti;

var
v : TValue;
i : Integer;

begin
i := 10;
v := I;
Writeln('Address of TypeInfo on TValue :', IntToStr(Integer(v.TypeInfo)));
Writeln('Address of TypeInfo on Integer:', IntToStr(Integer(TypeInfo(Integer))));
Writeln('Value of I:',I);
Writeln('Value of V:',v.AsInteger);
readln;
end.

Output:

Address of TypeInfo on TValue :4198560
Address of TypeInfo on Integer:4198560
Value of I:10
Value of V:10


There are several implicit operators defined that allow assignment of these types easy. If you have a type that is not on this list, don't worry there is a way to handle these, it just want to dedicate a single blog post to how to do this.


class operator Implicit(const Value: string): TValue;
class operator Implicit(Value: Integer): TValue;
class operator Implicit(Value: Extended): TValue;
class operator Implicit(Value: Int64): TValue;
class operator Implicit(Value: TObject): TValue;
class operator Implicit(Value: TClass): TValue;
class operator Implicit(Value: Boolean): TValue;


There are as set of matching functions to get the data out.


function AsString: string;
function AsInteger: Integer;
function AsExtended: Extended;
function AsInt64: Int64;
function AsObject: TObject;
function AsClass: TClass;
function AsBoolean: Boolean;


Although, this may make you think about the Variant type, conversions from one type to another do not automatically occur. The following code generates an
EInvalidCast Exception, as the stored type is an Integer and not a string.


program Project10;
{$APPTYPE CONSOLE}

uses
Rtti;

var
v : TValue;
i : Integer;

begin
i := 10;
v := I;
// Generates and Invalid Type Cast
Writeln('Value of V:',v.asString);
readln;
end.


To help you in determining what type is in a given TValue you have several properties and functions.


// in TypInfo.pas
TTypeKind = (tkUnknown, tkInteger, tkChar, tkEnumeration, tkFloat,
tkString, tkSet, tkClass, tkMethod, tkWChar, tkLString, tkWString,
tkVariant, tkArray, tkRecord, tkInterface, tkInt64, tkDynArray, tkUString,
tkClassRef, tkPointer, tkProcedure);

// part of TValue in rtti.pas
property Kind: TTypeKind
function IsObject: Boolean;
function IsType<T>: Boolean; overload;



The following shows each of these:


program Project10;
{$APPTYPE CONSOLE}

uses
Rtti,TypInfo;

var
v : TValue;
i : Integer;

begin
i := 10;
v := I;
Writeln('V.Kind =',GetEnumName(TypeInfo(TTypeKind),ord(v.Kind)));
Writeln('V.IsType<Integer> = ',v.IsType<Integer>);
Writeln('V.IsType<TObject> = ',v.IsType<TObject>);
Writeln('V.IsObject = ',v.IsObject);
readln;
end.

Output:

V.Kind =tkInteger
V.IsType<Integer> = TRUE
V.IsType<TObject> = FALSE
V.IsObject = FALSE


TValue has a way more functionality to explore, that I will cover in an in depth article later, but the basics needed to be covered before
moving on to TRttiMember for Properties and Fields which is the next article.

RTTI Article List

Saturday, September 12, 2009

Exploring TRttiType and descendants in Depth

TRttiType provides us with an easy to use interface that allows us to the access all of the RTTI Information associated with that type. For the most part, I find this very intuitive, and self documenting, I recommend opening Rtti.pas and looking at the declaration for TRttiType.

I introduced TRttiType in the previous articles.

Like I stated in a prior article, if your type supports, Fields, Properties, and/or Methods. There are a few easy access method to get access to these.

These methods provide access to all of the Fields, Properties, and Methods that have RTTI information, in up coming articles I will go in depth on TRttiField, TRttiProperty, and TRttiMethod.

function GetMethods: TArray<TRttiMethod>; overload; virtual;
function GetFields: TArray<TRttiField>; virtual;
function GetProperties: TArray<TRttiProperty>; virtual;

function GetMethod(const AName: string): TRttiMethod; virtual;
function GetMethods(const AName: string): TArray<TRttiMethod>; overload; virtual;
function GetField(const AName: string): TRttiField; virtual;
function GetProperty(const AName: string): TRttiProperty; virtual;

However, sometimes you one want to access what was declared on that specific type and not in the parent types, this can be done with the following functionality.


function GetDeclaredMethods: TArray<TRttiMethod>; virtual;
function GetDeclaredProperties: TArray<TRttiProperty>; virtual;
function GetDeclaredFields: TArray<TRttiField>; virtual;


If your type supports ancestors, you can get the BaseType through the base type property.
The following code shows how you can walk back up the type tree to find the parent types.


program Project10;

{$APPTYPE CONSOLE}

uses
SysUtils, RTTI;
type
TOneObject = Class(TObject)
end;

TTwoObject = Class(TOneObject)
end;

TThreeObject = Class(TTwoObject)
end;

var
c : TRttiContext;
t : TRttiType;

begin
c := TRttiContext.Create;
try
t := c.GetType(TThreeObject);
writeln(t.Name);
while Assigned(t.BaseType) do
begin
t := t.BaseType;
writeln(t.Name);
end;
finally
c.Free
end;
readln;
end.

Output:

TThreeObject
TTwoObject
TOneObject
TObject



There are 3 sets of properties that give you more information about the given type.


property AsInstance: TRttiInstanceType read GetAsInstance;
property IsInstance: Boolean read GetIsInstance;
property AsOrdinal: TRttiOrdinalType read GetAsOrdinal;
property IsOrdinal: Boolean read GetIsOrdinal;
property IsSet: Boolean read GetIsSet;
property AsSet: TRttiSetType read GetAsSet;


Instance Types are Classes, and the TRttiInstanceType provides the property "MetaclassType" which returns the TClass for the given type. The following example shows how to use this type to construct an instance of an object.


program Project10;

{$APPTYPE CONSOLE}

uses
SysUtils, RTTI;
type
TOneObject = Class(TObject)
end;

TTwoObject = Class(TOneObject)
end;

TThreeObject = Class(TTwoObject)
end;

var
c : TRttiContext;
t : TRttiType;
o : TObject;
begin
c := TRttiContext.Create;
try
t := c.GetType(TThreeObject);
o := t.AsInstance.MetaclassType.Create;
Writeln(o.ClassName);
o.Free;
finally
c.Free
end;
readln;
end.

Output:

TThreeObject


TRttiOrdinalType handles Ordinal Types, such as Integer, Enumerated Type, etc...
It exposes 3 new properties to help you when working with Ordinal types.


TOrdType = (otSByte, otUByte, otSWord, otUWord, otSLong, otULong);

...

property OrdType: TOrdType read GetOrdType;
property MinValue: Longint read GetMinValue;
property MaxValue: Longint read GetMaxValue;


The following example code shows how they behave.


program Project10;

{$APPTYPE CONSOLE}

uses
SysUtils, RTTI,TypInfo;
type
TMyEnum = (enOne,enTwo,enThree);
var
c : TRttiContext;
t : TRttiType;
begin
c := TRttiContext.Create;
try
t := c.GetType(TypeInfo(TMyEnum));
writeln(GetEnumName(TypeInfo(TOrdType),ord(t.AsOrdinal.OrdType)));
writeln(t.AsOrdinal.MinValue);
writeln(t.AsOrdinal.MaxValue);
finally
c.Free
end;
readln;
end.

Output:

otUByte
0
2


Notice, I dipped back into TypInfo.pas to call GetEnumName(), it and it's partner function GetEnumValue(), allow you to work with the names of an enumerated type instead of the ordinal values.

The previous RTTI information that was available in prior versions of Delphi used a pointer to the type information i.e "pTypeInfo"

This is now stored in the .Handle property of the TRttiType, I mention this as the routines in TypInfo.pas still work if you need drop to some lower level functionality.

The TRttiSetType provides one new property "ElementType" which allows you to get the type of the elements of the set. The following code shows an example of this in action.


program Project10;

{$APPTYPE CONSOLE}

uses
SysUtils, RTTI,TypInfo;
type
TMyEnum = (enOne,enTwo,enThree);
TMySet = set of TMyEnum;
var
c : TRttiContext;
t : TRttiType;
begin
c := TRttiContext.Create;
try
t := c.GetType(TypeInfo(TMySet));
Writeln('Element Type:');
writeln(t.AsSet.ElementType.ToString);
finally
c.Free
end;
readln;
end.

Output:

Element Type:
TMyEnum



To be complete the following are provided on TRttiType, but TRttiRecordType only provides one new property, ManagedFields, however I have not found a reason I to need this property.


property AsRecord: TRttiRecordType read GetAsRecord;
property IsRecord: Boolean read GetIsRecord;


But, don't overlook IsRecord as it's very useful.

There are also several other descendant that you can use the standard "is" and "as" functions on get access to additional information associated with the given type.

TRttiInterfaceType
TRttiInt64Type
TRttiMethodType
TRttiClassRefType
TRttiEnumerationType
TRttiStringType
TRttiAnsiStringType
TRttiFloatType
TRttiArrayType
TRttiDynamicArrayType
TRttiPointerType
TRttiProcedureType

The following code shows how you type cast a TRttiType to a TRttiStringType,
to determine what type of string it's associated with.


program Project10;

{$APPTYPE CONSOLE}

uses
SysUtils, RTTI,TypInfo;
type
TmyRecord = record
UniStr : String;
AnsiStr : AnsiString;
WideStr : WideString;
end;
var
c : TRttiContext;
t : TRttiType;
field : TRttiField;
begin
c := TRttiContext.Create;
try
for field in c.GetType(TypeInfo(TMyRecord)).GetFields do
begin
t := field.FieldType;
writeln('Field:',field.Name);
writeln('RttiType:',t.ClassName);
if (t is TRttiStringType) then
Writeln('String Kind:',GetEnumName(TypeInfo(TRttiStringKind),ord((t as TRttiStringType).StringKind)));
Writeln;
end;
finally
c.Free
end;
readln;
end.

Output:

Field:UniStr
RttiType:TRttiStringType
String Kind:skUnicodeString

Field:AnsiStr
RttiType:TRttiAnsiStringType
String Kind:skAnsiString

Field:WideStr
RttiType:TRttiStringType
String Kind:skWideString




In conclusion, there are several other small features of TRttiType but I am not trying to reinvent the documentation, so I will leave that for you to discover.

RTTI Article List

Friday, September 11, 2009

Using Attributes and TCustomAttribute descendants

With Delphi 2010 attributes have been added as a language feature. They have been available in Delphi Prism (Targets .NET) and this now adds similar functionality to Win32.

Attributes are a way of associating additional metadata information with a given type or member of a type.

They can be applied in many places, the following code shows several of the places you can place attributes.


// Declaring an attribute.
TAttrTest = class(TCustomAttribute)
end;

// Places you can use an attribute.
[TAttrTest]
TRec = Record
[TAttrTest]
value : String;
[TAttrTest]
procedure DoThis([TAttrTest]arg1: String);
End;

[TAttrTest]
TMyEnum = (enOne,enTwo,enThree);

[TAttrTest]
TMySet = set of TMyEnum;

[TAttrTest]
TObj = class(TObject)
private
[TAttrTest]
FID: Integer;
public
[TAttrTest]
FName : String;
[TAttrTest]
property Id : Integer read FID write FID;
[TAttrTest]
constructor Create;
[TAttrTest]
destructor Destroy; override;
[TAttrTest]
procedure DoThis;
end;

var
[TAttrTest]
I : Integer;


So how do attributes work in Delphi 2010?

Attributes must descend from TCustomAttribute, if you look at the declaration of TCustomAttribute you will find that there is nothing special.


{ The base class for all custom attributes. Attribute
instances created by the RTTI unit are owned by those
members to which they apply. }
TCustomAttribute = class(TObject)
end;


Passing just the name of the new attribute is only practical in a few cases, usually
you need additional data associated. This is done through the constructor. The following example shows how to setup the call to the constructor in the attribute.


Type
TAttrTest2 = class(TObject)
private
FId : Integer;
public
constructor Create(aID : Integer);
property ID : Integer read FID write FID;
end;

[TAttrTest2(123)]
TMyObject = Class(TObject)
end;


So its simple to declare an Attribute and decorate your types with them. Accessing the attributes stored in a given type involves using rtti.pas, I covered some of the basics of how this works in the previous post

Anything that can have attributes has an associated .GetAttributes() method that returns
the array of the attributes associated with that code.

The following code shows how to access the attributes.


program Project10;

{$APPTYPE CONSOLE}

uses
SysUtils, RTTI;
type
TAttrTest2 = class(TCustomAttribute)
private
FId : Integer;
public
constructor Create(aID : Integer);
property ID : Integer read FID write FID;
end;

[TAttrTest2(1)]
[TAttrTest2(2)]
[TAttrTest2(3)]
TMyObject = Class(TObject)
end;

{ TAttrTest2 }

constructor TAttrTest2.Create(aID: Integer);
begin
FID := aId;
end;

var
c : TRttiContext;
t : TRttiType;
a : TCustomAttribute;
begin
c := TRttiContext.Create;
try
t := c.GetType(TMyObject);
for a in t.GetAttributes do
begin
Writeln((a as TAttrTest2).ID);
end;
finally
c.Free
end;
readln;
end.

Output:

1
2
3



Attributes also have a few other special items that the compiler implements.

If you have an attribute named like this...


type
TestAttribute = class(TCustomAttribute)
end;

It can be refered to in two different ways

[TestAttribute]
TExample = class(Tobject)
end;

[Test]
TExample2 = class(TObject)
end;


The compiler will look for the type, if it is not found it will automatically append "Attribute" to the name and search again. This is done to mimic the .NET behavior.

The compiler also has some special support for types allow you to get TRttiType
easily from the pTypeInfo pointer. The following code segment shows how that pTypeInfo can be interchanged for TRttitype in attributes.


uses
SysUtils, Rtti;

type
TestAttribute = class(TCustomAttribute)
public
constructor Create(aType : TRttiType);
end;

[Test(typeinfo(Integer))]
TEmployee = class(TObject)
end;


There are many practical applications for Attributes, I will explore many of these in later articles.

RTTI Article List

Thursday, September 10, 2009

Delphi 2010 RTTI - The basics

In Delphi 2009 and Prior RTTI was limited to items in the published section.
You had access to pointers for properties, fields and Methods on objects. If you spent some time learning, the old RTTI system was powerful. However, the power of the RTTI in prior versions seem very small when compared to what is now available in Delphi 2010.

In Delphi 2010 you have the option to have RTTI information for almost everything. The choice of what to include is yours, it is controlled by the new $RTTI directive. The default behavior is defined in System.pas shows that, properties and methods are now available via RTTI in both public and published sections, and Fields are available in all of the sections.


Section of System.pas:
{ RTTI Visibility }
type
TVisibilityClasses = set of (vcPrivate, vcProtected, vcPublic, vcPublished);

const
{ These constants represent the default settings built into the compiler.
For classes, these settings are normally inherited from TObject. }
DefaultMethodRttiVisibility = [vcPublic, vcPublished];
DefaultFieldRttiVisibility = [vcPrivate..vcPublished];
DefaultPropertyRttiVisibility = [vcPublic, vcPublished];

type
{ Default RTTI settings }
{$RTTI INHERIT
METHODS(DefaultMethodRttiVisibility)
FIELDS(DefaultFieldRttiVisibility)
PROPERTIES(DefaultPropertyRttiVisibility)}


Raw RTTI Information would be worthless unless you have good way to access the information. The new Unit RTTI.Pas provides a simple and elegant way to access this data. Flexibility of classes, with out the headaches of Memory Management was a key concern of the new design. As such RTTI access is done through a context, once that context is freed all the RTTI objects created are freed.


var
c : TRttiContext;
begin
c := TRttiContext.Create;
try
// RTTI Access code here
finally
c.free;
end;
end;


If you open up RTTI.pas you will need notice that TRttiContext is not an object it is a Record, so don't get confused you should still call .Create and .Free as you would with an object. The reason for this is to free the pool of RTTI objects that may have been created. Atlhough the help file tells you not to free it, I personally like to clean up. Update:
I have had enough questions on this alone, I thought I would explain in more detail.

TRttiContext offers several key methods, which allow you to get access, to the types in the system.


function GetType(ATypeInfo: Pointer): TRttiType; overload;
function GetType(AClass: TClass): TRttiType; overload;
function GetTypes: TArray<TRttiType>;
function FindType(const AQualifiedName: string): TRttiType;


For example all of the following will return the TRttiType class representing, TButton.


var
c : TRttiContext;
t : TRttiType;
begin
c := TRttiContext.Create;
try
// Via a String
t := c.FindType('StdCtrls.TButton');

// Via the pTypeInfo Pointer
t := c.GetType(TButton.ClassInfo);

// Via the class type
t := c.GetType(TButton);
finally
c.Free;
end;
end;


The TRttiType has many functions that allow you to query the members of that type.


function GetMethods: TArray<TRttiMethod>; overload; virtual;
function GetFields: TArray<TRttiField>; virtual;
function GetProperties: TArray<TRttiProperty>; virtual;

function GetMethod(const AName: string): TRttiMethod; virtual;
function GetMethods(const AName: string): TArray<TRttiMethod>; overload; virtual;
function GetField(const AName: string): TRttiField; virtual;
function GetProperty(const AName: string): TRttiProperty; virtual;


So for example the following console application would show all of the methods of TButton.


program Project10;
{$APPTYPE CONSOLE}
uses
StdCtrls, TypInfo, Rtti;

var
c : TRttiContext;
m : TRttiMethod;
begin
c := TRttiContext.Create;
for m in c.GetType(TButton).GetMethods do
begin
Writeln(m.ToString);
end;
c.Free;
readln;
end.

Output:

constructor Create(AOwner: TComponent)
class destructor Destroy
procedure Click
... (Many Lines Removed) ...
procedure AfterConstruction
procedure BeforeDestruction
procedure Dispatch(var Message)
procedure DefaultHandler(var Message)
class function NewInstance: TObject
procedure FreeInstance
class destructor Destroy



Taking this to the next level the following code creates TStringList using the RTTI System, invokes the Add Method and accesses the Text Property. Granted this is not a practical example, it is just designed to show you some of the functionality available. You will notice that values are stored using the type TValue. TValue can store and retrieve any type.


program Project11;
{$APPTYPE CONSOLE}
uses
StdCtrls, TypInfo, Classes, Rtti;

var
c : TRttiContext;
m : TRttiMethod;
t : TRttiInstanceType;
SL : TValue;
Lines : TValue;
begin
c := TRttiContext.Create;
t := (c.FindType('Classes.TStringList') as TRttiInstanceType);
SL := t.GetMethod('Create').Invoke(t.MetaclassType,[]);
t.GetMethod('Add').Invoke(SL,['Hello Do you like my hat?']);
t.GetMethod('Add').Invoke(SL,['I like that hat, what a party hat!']);
Lines := t.GetProperty('Text').GetValue(SL.AsObject);
Writeln(Lines.ToString);
c.Free;
readln;
end.

Output:

Hello Do you like my hat?
I like that hat, what a party hat!



Although it appears that TValue may act like a variant, TValue is not a replacement for Variant. Specifically, If you assign a specific type to a TValue you must retrieve it as that specific type. For example you can't assign an Integer to a TValue and retrieve it as a String, doing so results in an Invalid Type Cast.

This is just taste, future articles will cover this in more detail.

RTTI Article List

Wednesday, September 9, 2009

CodeRage IV - BDE to DBX

Well I just finished my session on CodeRage IV Session BDE to DBX, and so I am reposting a messsage I posted at the end of DelphiLive with the same information.

My slides and code are available in SVN at my Google Code Site

The BDEtoDBXDataPump was a quick and dirty application but it should work, let
me know if you have a problem and I will update the code in SVN, I had to update it once since DelphiLiver version of this session.

The ComponentConverter will convert TQuery and TTable to TdbxQuery components.
It will also convert TDatabase to TSqlConnection, and few other things to help in the conversion, such as removing the BDE Units and adding in the DBX units.

The ComponentCoverter contains a few little gems such as a DFM Parser.

Tuesday, September 1, 2009

CodeRage Sessions

Although, I just posted a fairly big teaser of my RTTI session, I neglected the other sessions I will be covering at CodeRage.

  • Building and Consuming Web Services in Delphi and Delphi Prism

  • This is a introduction to the topic, if you have done web services you won't learn anything new :-)
  • Building Unit Tests with DUnit

  • This is a introduction to the topic, I will be covering the XML test runner and other features that not visible on the surface. So it may be useful even if you use basic DUnit Tests.
  • Converting from BDE to DBX

  • This is a refined version of my DelphiLive material. This has been my most popular session I have ever given, I have recieved more feedback on this than any other. It does not hurt that Andreano Lanusse repeated parts of the session on his various stops in Brazil I will be showing the DBX framework and some code/dfm parsers that do much of the work to convert a BDE application to DBX. I will also show a BDE to DBX datapump.
  • Practical Application of RTTI and Attributes

  • I have never been more exicited about a feature in Delphi. After seeing this session and the associated blog posts, I hope you realize what I am talking about :-)


I hope to be able to chat with many of you next week at CodeRage.

Delphi 2010 - RTTI & Attributes

So what is RTTI? RTTI is an acronym for Run Time Type Information. It allows you interact with the type system at Run Time. I like to compare RTTI to meta data information stored in a database. If I execute the following SQL statement "select * from employee" how does the database know what to return? How does the application know what will be returned? It all boils down to "MetaData Information" which allows you to look up what database fields and there associated types will be returned. With RTTI, you have this same access to types defined in your Delphi code.

Delphi has always had RTTI, but Delphi 2010 has taken RTTI to the next level.

CodeRage is next week, there are two session that will be covering the RTTI system in Delphi 2010.

The first is Barry Kelly's presentation on "Delphi Compiler RTTI Enhancements" if you have time to only see one, then see this one. Barry is the engineer behind the Compiler RTTI Enhancements. His presentation is currently scheduled for Tuesday.

The second, is mine on "Practical Application of RTTI and Attributes" my presentation is currently scheduled for Thursday.

Both are only 40 minutes long, it is enough to get your tips of your toes wet, and I want to jump right in :-)

As such I have prepared a series of blog posts, I will start posting these after my CodeRage Session. Most likely one each day. Although, I tend to hate teasers, I decided to post one :-P

Here is what to expect:

  1. Delphi 2010 RTTI - The basics

  2. Using Attributes and TCustomAttribute descendants

  3. Exploring TRTTIType in depth

  4. Introduction to TValue

  5. Exploring TRttiMember Descendants in depth (Part I) Properties and Fields

  6. Why I call TRttiContext.Create() and TRttiContext.Free()

  7. Exploring TRttiMember Descendants in depth (Part II) Methods

  8. TValue in Depth

  9. INI persistence the RTTI way

  10. Xml Serialization - Basic Usage

  11. Xml Serialization - Control via Attributes

  12. Attributes: Practical Example- Object to Client Dataset

  13. Types by Package... Dynamic plug-in systems.



The above list may change a bit as I am still editing the material.

I will update this post with links to the blog posts, as the become available if you want to bookmark this page.

I hope to see you at CodeRage, to unleash the Chaos :-)