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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM