TValue является одним из ключевых типов в новой RTTI системе. Мы уже рассмотрели некоторые основы во Введении в TValue. Теперь наступило время немного вернуться назад для того, что бы разобрать и исследовать как он был спроектирован, затем, что бы вы могли использовать всю мощь TValue.
Прежде чем мы зайдем слишком далеко, давайте взглянем на поле в интерфейсной части TValue.
TValue = record
... private FData: TValueData; end;
С тех пор, как TValue получил возможность хранить данные из любого типа, я заинтересовался как, это использовать с максимальной пользой, что бы знать, как сохранять данные из различных неизвестных типов и знать, как потом их использовать дальше.
TValueData описан следующим образом:
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;
Это именно вариантная запись (variant record), которая занимает 24 байта в памяти.
Ключевыми частями являются FTypeInfo, и, соответственно, либо FHeapData, либо одна из изменяющихся (variant) частей, которая будет использоваться для хранения данных.
Знание того, как данные хранятся, поможет в понимании низкоуровневых процедур, предназначенных для определения и получения доступа к данным, хранящимся в 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;
По существу, вы можете использовать Make() для размещения любых данных с информацией о типе в TValue.
Здесь приведен пример размещения Integer и TRect.
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.
Результат:
1234 (record)
При рассмотрении вопросов десериализации (Deserialization), которую я осуществил, мне необходимо воссоздать структуры записи, о которой я ничего не знаю, но для которой есть TypeInfo. Это можно сделать с помощью такого вызова:
TValue.Make(nil,TypeInfoVar,OutputTValue);
Извлечение данных может также осуществляться с использованием низкоуровневых подпрограмм, здесь приводится пример использования 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.
Результат:
10 20
Вы можете использовать нечто подобное GetReferenceToRawData() с SetValue и GetValue в записях.
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.
Результат:
10 20
Этот маленький пример демонстрирует, как обращаться с типами, о которых вы ничего не знаете во время компиляции. Однако, иногда вы знаете тип, с которым вы будете работать во время компиляции. Когда он известен, становится значительно проще работать, используя функции типа Generic/Parametrized, которые предосталяет TValue.
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;
Следующий пример демонстрирует, как использовать From<T>() IsType<T>() и AsType<T>().
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.
Результат:
TRUE 10 20
Существуют и другие функции, которые помогут вам с типами массивами.
function GetArrayLength: Integer; function GetArrayElement(Index: Integer): TValue; procedure SetArrayElement(Index: Integer; const AValue: TValue);
Я должен заметить, что динамические массивы, описанные как в данном случае, в настоящее время не поддерживаются:
Var IntArray : Array of Integer;
Но если вы сможете сделать свой код подобным этому, они будут работать прекрасно.
type TIntArray = Array of Integer; var I : TIntArray; // or I : TArray<Integer>; {defined in System.pas as: TArray<T> = array of T;}
Еще один маленький нюанс заключается в обманчивости TValue.FromVariant().
Я думал, это означало то, что я буду брать Variant и запихивать его в TValue. Однако, вы убедитесь в том, что это работает иначе. Здесь сохраняются данные с использованием порождаемого типа. Следующий пример показывает, как это работает.
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.
Результат.
tkUString
Hello World
tkInteger
1234
Если вы хотите хранить Variant в TValue, то вы можете использовать такой метод:
program Project12; {$APPTYPE CONSOLE} uses SysUtils, TypInfo,Rtti; var vExample : Variant; Value : TValue; begin vExample := 'Hello World'; Value := TValue.From<variant>(vExample); writeln(GetEnumName(TypeInfo(TTypeKind),Ord(Value.Kind))); Writeln(value.AsType<variant>); vExample := 1234; Value := TValue.From<variant>(vExample); writeln(GetEnumName(TypeInfo(TTypeKind),Ord(Value.Kind))); Writeln(value.AsType<variant>); readln; end. </variant></variant></variant></variant>
Результат:
tkVariant
Hello World
tkVariant
1234
Также существуют другие способы работы с TValue , на описание которых у меня не хватило времени. Я рекомендую открыть RTTI.pas и исследовать интерфейс, что бы увидеть все возможности. Пункты, которые я не осветил – довольно просты.
Я надеюсь, это позволит вам проделать хороший тест, для того, что бы понять как работает TValue.
вопрос немного не по теме, можете потом коммент удалить.
Как Вы относитесь к тому, что посты Вашего блога копируются вот сюда
(один из примеров):
http://www.interface.ru/home.asp?artId=21554
причем ссылка на Ваш блог вся такая незаметная, да и вообще не прямая, а переадресация.
И, у меня на вопрос заголовка статьи гугл выдает сначала Интерфейс, а потом только Вас.
Или Вы работаете в компании Интерфейс?
2 KDV. Всё еще воюешь с Интерфейс? Думаю, только зря тратить время. Тащили они без спроса статьи к себе, тащат и будут тащить.
Дык, надо их на место приструнить. Шоб разрешения спрашивали.