zoukankan      html  css  js  c++  java
  • 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
  • 相关阅读:
    拯救祭天的程序员——事件溯源模式
    啥?SynchronousQueue和钟点房一个道理
    程序员应该掌握的一些 Linux 命令
    Linux 环境下使用 sqlplus 访问远程 Oracle 数据库
    我对鸿蒙OS的一些看法
    我对技术潮流的一些看法
    git merge --ff/--no-ff/--ff-only 三种选项参数的区别
    go语言的初体验
    完全使用 VSCode 开发的心得和体会
    重复代码的克星,高效工具 VSCode snippets 的使用指南
  • 原文地址:https://www.cnblogs.com/hezihang/p/3280470.html
Copyright © 2011-2022 走看看