Laufzeitoperationen mit Typen

Aus RAD Studio
Wechseln zu: Navigation, Suche

Nach oben zu Mit RTTI arbeiten

Neben der vollständigen Typprüfung ermöglicht die RTTI auch die Bearbeitung der Variablen und Instanzen der geprüften Typen. Dieses Thema enthält eine Einführung in die von RTTI-Objekten bereitgestellten Methoden und Eigenschaften, die eine Laufzeitbearbeitung von Werten auf Basis ihrer Typinformationen ermöglichen.

Mit Feldern arbeiten

Felder sind Klassen- oder Record-Member, die Informationen speichern. Um auf Feldinformationen zuzugreifen, müssen Sie zuerst eine TRttiInstanceType- oder TRttiRecordType-Instanz abrufen, die den Klassen- oder Record-Typ beschreibt. Mit den bereitgestellten Methoden GetField, GetFields oder GetDeclaredFields ermitteln Sie die TRttiField-Objekte, die die einzelnen Felder beschreiben. TRttiField stellt dann eine Reihe von Methoden zum Setzen oder Ermitteln des Werts eines einzelnen Feldes in einer Instanz des beschriebenen Typs bereit.

Das folgende Beispiel zeigt, wie Informationen über ein Feld in einem Klassentyp ermittelt werden können. Anhand dieser Informationen wird der Wert des Feldes indirekt über die RTTI gelesen:

 type
     { Eine Testklasse mit 2 Feldern definieren }
     TTest = class
         FInteger: Integer;
         FString: String;
     end;
 
 var
     LContext: TRttiContext;
     LClass: TRttiInstanceType;
     LVar: TTest;
 
 begin
     { Den RTTI-Kontext ermitteln }
     LContext := TRttiContext.Create;
 
     { Typinformationen für die Klasse TTest ermitteln }
     LClass := LContext.GetType(TTest) as TRttiInstanceType;
 
     { Eine neue TTest-Instanz erstellen und deren Werte initialisieren }
     LVar := TTest.Create;
     LVar.FInteger := 10;
     LVar.FString := 'Hello RTTI!';
 
     { Die Werte von LVar mittels RTTI ausgeben }
     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[]) {
 	// Den RTTI-Kontext ermitteln
 	TRttiContext context;
 
 	// Typinformationen für die Klasse TTest ermitteln
 	TRttiType *type = context.GetType(__delphirtti(TTest));
 	TRttiInstanceType *instanceType = dynamic_cast<TRttiInstanceType*>(type);
 	if (instanceType) {
 		// Eine neue TTest-Instanz erstellen und deren Werte initialisieren
 		TTest *var = new TTest();
 		var->FInteger = 10;
 		var->FString = "Hello RTTI!";
 
 		// Die Werte von var mittels RTTI ausgeben 
 		// Feld FInteger
 		TRttiField *field = instanceType->GetField("FInteger");
 		if (field) {
 			printf("%ls\n", field->GetValue(var).ToString());
 		}
 
 		// Feld FString
 		field = instanceType->GetField("FString");
 		if (field) {
 			printf("%ls\n", field->GetValue(var).ToString());
 		}
 	}
 
 	return 0;
 }

Der Wert von Feldern wird mit der Methode SetValue gesetzt:

 { Den Wert der Felder in der LVar-Instanz ändern }
     LClass.GetField('FInteger').SetValue(100);
     LClass.GetField('FString').SetValue('Hello Again!');

Die Methoden GetValue und SetValue arbeiten beide mit dem Typ TValue, der eine Sonderform des Typs Variant ist. Mit TValue-Werten wird der Quelltext klarer; es sind weniger Typumwandlungen und Methoden erforderlich. TValue führt automatisch interne Typumwandlungen für die betreffenden Werte durch.

Mit Eigenschaften arbeiten

Eigenschaften verhalten sich in gewisser Weise genauso wie Felder: sie enthalten auch Informationswerte. Der wesentliche Unterschied zwischen Eigenschaften und Feldern besteht darin, dass Eigenschaften nicht notwendigerweise Informationswerte speichern, sondern diese anhand von Methoden bei Bedarf erzeugen. Für die Arbeit mit Eigenschaften müssen Sie ein TRttiInstanceType-Objekt (das die Klasse RTTI repräsentiert) abrufen und dann anhand der Methoden GetProperty, GetProperties oder GetDeclaredProperties die gewünschte Eigenschaft ermitteln. TRttiProperty stellt für einen schnellen Zugriff auf die Eigenschaftswerte auch die Methoden GetValue und SetValue bereit.

Das folgende Beispiel zeigt, wie die Werte der Eigenschaften IntProp und StrProp (der Beispielklasse TTest) gelesen und geändert werden:

 { Werte der Eigenschaften über RTTI ausgeben }
 Writeln('The value of IntProp is ', LClass.GetProperty('IntProp').GetValue(LVar).ToString());
 Writeln('The value of StrProp is ', LClass.GetProperty('StrProp').GetValue(LVar).ToString());

Der Wert der Eigenschaften wird mit der Methode SetValue gesetzt:

 { Den Wert der Felder in der LVar-Instanz ändern }
 LClass.GetField('IntProp').SetValue(100);
 LClass.GetField('StrProp').SetValue('Hello Again!');

Instanzeigenschaften stellen weitere klassenspezifische Informationen (z.B. Index oder Default) bereit. Zum Abrufen dieser Informationen wandeln Sie die ermittelte TRttiProperty-Instanz in TRttiInstanceProperty um (beachten Sie bitte, dass es sich bei dem bearbeiteten Typ um eine Klasse handeln muss).

Mit indizierten Eigenschaften arbeiten

Indizierten Eigenschaften können ab XE2 (Pulsar) Laufzeit-Typinformationen zugeordnet sein. Mit den folgenden Methoden ermitteln Sie die zugehörigen RTTI-Informationen:

Zum Ermitteln und Setzen des Wertes einer indizierten Eigenschaft müssen Sie Folgendes bereitstellen:

  • Die spezifische Klasseninstanz (wie für nicht indizierte Eigenschaften).
  • Die Indexinformationen (gekapselt in einem Array).

Betrachten Sie folgende Beispiele:

 var
   Inst: TStringList;
   RttiContext: TRttiContext;
   ClassRtti: TRttiType;
   IndexedProperty: TRttiIndexedProperty;
 
 begin
   // ..
 
   ClassRtti := RttiContext.GetType(TStrings);
 
   Inst := TStringList.Create; // TStrings-Instanz einrichten
   Inst.Add('CA');
   Inst.Add('NV');
 
   // Indizierten Eigenschaftswert anzeigen (Index muss angegeben werden)
   IndexedProperty := ClassRtti.GetIndexedProperty('Strings');
   WriteLn(IndexedProperty.GetValue(Inst, [0]).AsString); // zeigt 'CA' an
 
   // ...
 // ...
 
   TRttiType *type = context.GetType(__delphirtti(TStrings));
   TRttiInstanceType *instanceType = dynamic_cast<TRttiInstanceType*>(type);
 
   TStringList *inst = new TStringList(); // TStrings-Instaz einrichten
   inst->Add("CA");
   inst->Add("NV");
 
   // Indizierten Eigenschaftswert anzeigen (Index muss angegeben werden)
   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()); // zeigt "CA" an
 
   // ...

Methoden aufrufen

Eine der nützlichsten, von RTTI-Klassen ermöglichten Funktionen ist das Aufrufen von Methoden. Es können statische, Interface-, Klassen- oder Instanzmethoden dynamisch aufgerufen werden. Dazu müssen Sie zunächst ein TRttiMethod-Objekt abrufen, dass die aufzurufende Methode beschreibt. TRttiMethod stellt die Methode Invoke bereit, mit der die Methode mit einem gegebenen Instanzzeiger und Parametern dynamisch aufgerufen werden kann.

Angenommen, es gibt eine TTest-Klasse mit der folgenden Deklaration:

 type
   TTest = class
   public
     procedure WriteString(const AStr: String);
   end;
 
 procedure TTest.WriteString(const AStr: String);
 begin
   { Den übergebenen String einfach schreiben }
   Writeln(AStr);
 end;
 class __declspec(delphirtti) TTest : public TObject {
 public:
   void WriteString(UnicodeString AStr) {
     // Den übergebenen String einfach schreiben
     printf("%ls", AStr.c_str());
   }
 };

Die Methode WriteString würde mit dem folgenden Code dynamisch aufgerufen:

 { Typinformationen für die Klasse TTest ermitteln }
 LClass := LContext.GetType(TTest) as TRttiInstanceType;
 
 { Eine neue TTest-Instanz erstellen }
 LVar := TTest.Create;
 
 { Die Methode WriteString aufrufen }
 LClass.GetMethod('WriteString').Invoke(LVar, ['Hello World!']);
 // Typinformationen für die Klasse TTest ermitteln
 TRttiInstanceType *LClass =
   dynamic_cast<TRttiInstanceType*>(context.GetType(__delphirtti(TTest)));
 
 // Eine neue TTest-Instanz erstellen
 std::unique_ptr<TTest>LVar(new TTest()); // RAII wrapper
 
 // Die Methode WriteString aufrufen
 Rtti::TValue msg = TValue::From(UnicodeString("Hello World!"));
 System::OpenArray<Rtti::TValue>params(msg);
 LClass->GetMethod("WriteString")->Invoke(LVar.get(), params,
   params.GetHigh());

Wenn Sie die Definition von WriteString in eine Klassenmethode anstelle einer Instanzmethode ändern, ändern sich die für den Aufruf erforderlichen Parameter ebenfalls. Dann muss der Instanzparameter eine Klassenreferenz sein:

 { Die Klassenmethode WriteString aufrufen }
 LClass.GetMethod('WriteString').Invoke(TTest, ['Hello World!']);
 // Kein Äquivalent in C++

Siehe auch