Run-Time Operations on Types

From RAD Studio
Jump to: navigation, search

Go Up to Working with RTTI

Besides providing full type introspection, RTTI also offers the possibility to operate on variables and instances of the introspected types. This topic is an introduction to the methods and properties exposed by RTTI objects that allow run-time modification of values based on their type information.

Working with Fields

Fields are class or record members that store information. To access field information, you must first obtain a TRttiInstanceType or TRttiRecordType instance that describes the class or the record type. Using the exposed GetField, GetFields, or GetDeclaredFields methods, you must obtain the TRttiField objects that describe the individual fields. TRttiField then exposes a set of methods that allow setting and getting the value of an individual field in an instance of the described type.

The following example demonstrates how to obtain information about a field in a class type. Using this information, the value of the field is read indirectly, using RTTI:

type
    { Define a test class with 2 fields }
    TTest = class
        FInteger: Integer;
        FString: String;
    end;

var
    LContext: TRttiContext;
    LClass: TRttiInstanceType;
    LVar: TTest;

begin
    { Obtain the RTTI context }
    LContext := TRttiContext.Create;

    { Obtain type information for the TTest class }
    LClass := LContext.GetType(TTest) as TRttiInstanceType;

    { Create a new TTest instance and initialize its values }
    LVar := TTest.Create;
    LVar.FInteger := 10;
    LVar.FString := 'Hello RTTI!';

    { Print the values of LVar using RTTI }
    Writeln('The value of FInteger in LVar is ', LClass.GetField('FInteger')
      .GetValue(LVar).ToString());
    Writeln('The value of FString in LVar is ', LClass.GetField('FString')
      .GetValue(LVar).ToString());

end.
#pragma explicit_rtti fields(public)

class TTest : TObject {
public:
	int FInteger;
	String FString;
};

int _tmain(int argc, _TCHAR* argv[]) {
	// Obtain the RTTI context
	TRttiContext context;

	// Obtain type information for the TTest class
	TRttiType *type = context.GetType(__delphirtti(TTest));
	TRttiInstanceType *instanceType = dynamic_cast<TRttiInstanceType*>(type);
	if (instanceType) {
		// Create a new TTest instance and initialize its values
		TTest *var = new TTest();
		var->FInteger = 10;
		var->FString = "Hello RTTI!";

		// Print the values of var using RTTI
		// field FInteger
		TRttiField *field = instanceType->GetField("FInteger");
		if (field) {
			printf("%ls\n", field->GetValue(var).ToString());
		}

		// field FString
		field = instanceType->GetField("FString");
		if (field) {
			printf("%ls\n", field->GetValue(var).ToString());
		}
	}

	return 0;
}

To set the value of the fields, use the SetValue method:

    { Change the value of the fields in LVar instance }
    LClass.GetField('FInteger').SetValue(100);
    LClass.GetField('FString').SetValue('Hello Again!');

Both the GetValue and SetValue methods operate on a type called TValue, which is a light form of the Variant type. Using TValue values makes the code clearer; it requires less typecasts and methods. TValue automatically performs internal typecasts for values it operates on.

Working with Properties

In a way, properties behave exactly the same as fields--they also carry informational value. The essential difference between properties and fields is that properties do not necessarily store informational value, but can use methods to generate it, when requested. To work with properties, you must obtain a TRttiInstanceType object (which will represent the class RTTI) and then use GetProperty, GetProperties, or GetDeclaredProperties to operate on the specific property. TRttiProperty also exposes the GetValue and SetValue methods, which allow easy access to the value of the property.

Assuming that the example class TTest exposes 2 read/write properties, IntProp and StrProp, the following code demonstrates how to read or change the values of those properties:

{ Print the values of properties, using RTTI }
Writeln('The value of IntProp is ', LClass.GetProperty('IntProp').GetValue(LVar).ToString());
Writeln('The value of StrProp is ', LClass.GetProperty('StrProp').GetValue(LVar).ToString());

To set the value of the properties, use the SetValue method:

{ Change the value of the fields in the LVar instance }
LClass.GetField('IntProp').SetValue(100);
LClass.GetField('StrProp').SetValue('Hello Again!');

Instance properties expose additional information specific to classes (for example, Index or Default). To obtain this specific information, typecast the obtained TRttiProperty instance to TRttiInstanceProperty (be sure that the type you are operating on is a class.)

Working with Indexed Properties

Indexed properties can have run-time type information as of XE2 (Pulsar). You can use the following methods to get the associated RTTI:

To get and set the value of an indexed property you have to provide:

  • The specific class instance (as for non-indexed properties).
  • The index information (encapsulated in an array).

See the following examples:

var
  Inst: TStringList;
  RttiContext: TRttiContext;
  ClassRtti: TRttiType;
  IndexedProperty: TRttiIndexedProperty;

begin
  // ..

  ClassRtti := RttiContext.GetType(TStrings);

  Inst := TStringList.Create; // Set up TStrings instance
  Inst.Add('CA');
  Inst.Add('NV');

  // Display indexed property value (index must be specified)
  IndexedProperty := ClassRtti.GetIndexedProperty('Strings');
  WriteLn(IndexedProperty.GetValue(Inst, [0]).AsString); // displays 'CA'

  // ...
  // ...

  TRttiType *type = context.GetType(__delphirtti(TStrings));
  TRttiInstanceType *instanceType = dynamic_cast<TRttiInstanceType*>(type);

  TStringList *inst = new TStringList(); // Set up TStrings instance
  inst->Add("CA");
  inst->Add("NV");

  // Display indexed property value (index must be specified)
  TRttiIndexedProperty *indexedProperty = instanceType->GetIndexedProperty("Strings");
  Rtti::TValue index = TValue::From(0); // index 0
  System::OpenArray<Rtti::TValue>params(index);
  printf("%ls\n", indexedProperty->GetValue(inst, params, params.GetHigh()).AsString().c_str()); // displays "CA"

  // ...

Invoking Methods

One of the most useful features allowed by the RTTI classes is method invocation. Method invocation allows calling a static, interface, class, or instance method dynamically. To be able to invoke methods, first obtain a TRttiMethod object that describes the method you want to invoke. TRttiMethod exposes a method called Invoke, which allows calling the method dynamically, given the instance pointer and a set of parameters.

Assuming you have a TTest class declared as the following:

type
  TTest = class
  public
    procedure WriteString(const AStr: String);
  end;

procedure TTest.WriteString(const AStr: String);
begin
  { Simply write the passed string }
  Writeln(AStr);
end;
class __declspec(delphirtti) TTest : public TObject {
public:
  void WriteString(UnicodeString AStr) {
    // Simply write the passed string
    printf("%ls", AStr.c_str());
  }
};

the code used to invoke the WriteString method dynamically looks like this:

{ Obtain type information for the TTest class }
LClass := LContext.GetType(TTest) as TRttiInstanceType;

{ Create a new TTest instance }
LVar := TTest.Create;

{ Call the WriteString method }
LClass.GetMethod('WriteString').Invoke(LVar, ['Hello World!']);
// Obtain type information for the TTest class
TRttiInstanceType *LClass =
  dynamic_cast<TRttiInstanceType*>(context.GetType(__delphirtti(TTest)));

// Create a new TTest instance
std::unique_ptr<TTest>LVar(new TTest()); // RAII wrapper

// Call the WriteString method
Rtti::TValue msg = TValue::From(UnicodeString("Hello World!"));
System::OpenArray<Rtti::TValue>params(msg);
LClass->GetMethod("WriteString")->Invoke(LVar.get(), params,
  params.GetHigh());

If you change the definition of WriteString, which becomes a class method, instead of an instance method, the parameters required for invocation also change. Now, instead of an instance, the instance parameter must be a class reference:

{ Call the WriteString class method }
LClass.GetMethod('WriteString').Invoke(TTest, ['Hello World!']);
// No equivalent in C++

See Also