Delphi的TValue探索(一)
TValue是Delphi的RTTI系统的重要类型。 经过摸索,发现TValue功能强大,可以实现很多功能。本文章中所有程序采用XE3运行通过。
一、TValue结构
TValue定义在System.Rtti.pas
TValue = record ... private FData: TValueData end;
TValue提供了一些系列方法,几乎都是操作FData.
TValueData描述如下:
TValueData = record FTypeInfo: PTypeInfo; // FValueData vs old FHeapData: // FHeapData doubled as storage for interfaces. However, that was ambiguous // in the case of nil interface values: FTypeInfo couldn't be trusted // because it looked like the structure was uninitialized. Then, DataSize // would be 0. // FValueData is different: interfaces are always stored like strings etc., // as a reference stored in a blob on the heap. FValueData: IValueData; case Integer of 0: (FAsUByte: Byte); 1: (FAsUWord: Word); 2: (FAsULong: LongWord); 3: (FAsObject: Pointer); 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); 16: (FAsPointer: Pointer); end;
TValueData是一个结构体,TValueData可以存储任何类型的数据,经过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
通过调用ExtractRawData(...), ExtractRawDataNoCopy(...)将TValue转换为任意数据类型,两者区别是ExtractRawDataNoCopy转换时在堆中申请内存的数据,而ExtractRawData是安全的。
GetReferenceToRawData返回数据的指针,也是堆内存的指针。
二、类型转换为TValue
下面例子测试Integer和TRect:
1 program Project1; 2 {$APPTYPE CONSOLE} 3 uses SysUtils, Windows, TypInfo,Rtti; 4 5 var 6 IntData : Integer; 7 IntValue : TValue; 8 9 RecData : TRect; 10 RecValue : TValue; 11 12 begin 13 IntData := 1234; 14 TValue.Make(@IntData,TypeInfo(Integer),IntValue); //Integer类型也可以直接调用 IntValue := IntData; 这里演示TValue.Make 15 Writeln(IntValue.ToString); 16 RecData.Left := 10; 17 RecData.Right := 20; 18 TValue.Make(@RecData,TypeInfo(TRect),RecValue); 19 Writeln(RecValue.ToString); 20 readln; 21 end.
运行结果: 1234 (record)
三、TValue转换到类型
在反序列化(反持久化)时,如果知道数据类型,可以调用下面的方法生成一个与此类型相应的TValue空记录:
TValue.Make(nil,TypeInfoVar,OutputTValue);
通过ExtractRawData,可以将TValue数据直接转换某类型数据:
1 program Project2; 2 {$APPTYPE CONSOLE} 3 uses SysUtils, Windows, TypInfo,Rtti; 4 5 var 6 RecData : TRect; 7 RecDataOut : TRect; 8 RecValue : TValue; 9 10 begin 11 RecData.Left := 10; 12 RecData.Right := 20; 13 TValue.Make(@RecData,TypeInfo(TRect),RecValue); //将TRect结构的RecData转换为TValue类型的RecValue 14 15 RecValue.ExtractRawData(@RecDataOut); //将TValue 结构数据转换成TRect类型数据 16 Writeln(RecDataOut.Left); 17 Writeln(RecDataOut.Right); 18 19 readln; 20 end.
运行结果 10 20
四、通过TValue的访问类型的成员变量
TValue转换自某个类型后,可以使用的GetReferenceToRawData()获取数据指针,通过调用SetValue和GetValue读写
某个成员的值。
1 program Project3; 2 {$APPTYPE CONSOLE} 3 uses SysUtils, Windows, TypInfo,Rtti; 4 5 var 6 RecData : TRect; 7 RecValue : TValue; 8 Ctx : TRttiContext; 9 10 begin 11 Ctx := TRttiContext.Create; 12 // 创建空的、与TRect对应的TValue结构体, 13 TValue.Make(nil,TypeInfo(TRect),RecValue); 14 // 设置 Left 、 Right 成员变量值,使用TValue中成员变量的地址指针 15 Ctx.GetType(TypeInfo(TRect)).GetField('Left').SetValue(RecValue.GetReferenceToRawData,10); 16 Ctx.GetType(TypeInfo(TRect)).GetField('Right').SetValue(RecValue.GetReferenceToRawData,20); 17 // 转换为TRect结构体数据 18 RecValue.ExtractRawData(@RecData); 19 Writeln(RecData.Left); 20 Writeln(RecData.Right); 21 readln; 22 Ctx.Free; 23 end.
运行结果: 10 20
五、泛型转换函数
我们上面的例子,通过调用Make函数来转换成TValue,以及通过ExtractRawData转换成需要的类型,
Delphi还提供了泛型转换函数,可以指定已知的类型,直接进行转换:
class function From<T>(const Value: T): TValue; static; function AsType<T>: T; function IsType<T>: Boolean; function TryAsType<T>(out AResult: T): Boolean; function Cast<T>: TValue; overload;
看下面的例子:
1 program Project4; 2 {$APPTYPE CONSOLE} 3 uses SysUtils, Windows, TypInfo,Rtti; 4 5 var 6 RecData : TRect; 7 RecDataOut : TRect; 8 RecValue : TValue; 9 begin 10 RecData.Left := 10; 11 RecData.Right := 20; 12 13 RecValue := TValue.From<TRect>(RecData); //直接转换成 TValue 14 15 Writeln(RecValue.IsType<TRect>); 16 17 RecDataOut := RecValue.AsType<TRect>; //TValue直接转换成TRect 18 19 Writeln(RecDataOut.Left); 20 Writeln(RecDataOut.Right); 21 readln; 22 end.
运行结果: TRUE 10 20
六、数组
如果TValue转换自数组类型,则可以调用一下方法:
function GetArrayLength: Integer; function GetArrayElement(Index: Integer): TValue; procedure SetArrayElement(Index: Integer; const AValue: TValue);
如果用下面的方式定义数组,则不支持转换到TValue:
var IntArray : array of Integer;
我可以先定义数组类型后,再定义变量,则可以转换到TValue:
type TIntArray = array of Integer; var IntArray : TIntArray; // 或者 IntArray : TArray<Integer>; //在 System.pas 定义: TArray<T> = array of T;
七、Variant
Variant与TValue的转换容易产生混淆,调用TValue.FromVariant(),并不是将Varaint转换为TValue:
1 program Project5; 2 {$APPTYPE CONSOLE} 3 uses SysUtils, TypInfo,Rtti; 4 5 var 6 vExample : Variant; 7 Value : TValue; 8 begin 9 vExample := 'Hello World'; 10 Value := TValue.FromVariant(vExample); // 11 writeln(GetEnumName(TypeInfo(TTypeKind),Ord(Value.Kind))); 12 Writeln(value.ToString); 13 14 vExample := 1234; 15 Value := TValue.FromVariant(vExample); 16 writeln(GetEnumName(TypeInfo(TTypeKind),Ord(Value.Kind))); 17 Writeln(value.ToString); 18 19 readln; 20 end.
运行结果: tkUString Hello World tkInteger 1234
如果希望将Variant转换为TValue,可以使用这个方法:
1 program Project6; 2 {$APPTYPE CONSOLE} 3 uses SysUtils, TypInfo,Rtti; 4 5 var 6 vExample : Variant; 7 Value : TValue; 8 begin 9 vExample := 'Hello World'; 10 Value := TValue.From<variant>(vExample); 11 writeln(GetEnumName(TypeInfo(TTypeKind),Ord(Value.Kind))); 12 Writeln(value.AsType<variant>); 13 14 vExample := 1234; 15 Value := TValue.From<variant>(vExample); 16 writeln(GetEnumName(TypeInfo(TTypeKind),Ord(Value.Kind))); 17 Writeln(value.AsType<variant>); 18 19 readln; 20 end.
运行结果: tkVariant Hello World tkVariant 1234