Блог Александра Божко
Архивы
Рубрики
Поделись с другими!
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  

Оригинал.

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.


Поделись с другими!
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  

3 комментария: Детально о TValue

  • вопрос немного не по теме, можете потом коммент удалить.
    Как Вы относитесь к тому, что посты Вашего блога копируются вот сюда
    (один из примеров):
    http://www.interface.ru/home.asp?artId=21554
    причем ссылка на Ваш блог вся такая незаметная, да и вообще не прямая, а переадресация.
    И, у меня на вопрос заголовка статьи гугл выдает сначала Интерфейс, а потом только Вас.
    Или Вы работаете в компании Интерфейс?

  • 2 KDV. Всё еще воюешь с Интерфейс? Думаю, только зря тратить время. Тащили они без спроса статьи к себе, тащат и будут тащить.

  • Дык, надо их на место приструнить. Шоб разрешения спрашивали.

Оставить комментарий

Ваш email не будет опубликован. Обязательные поля отмечены *

Вы можете использовать это HTMLтеги и атрибуты: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Продукты DevArt
Купить онлайн:



Читай русскоязычные Delphi блоги
Каталог блогов Blogdir.ru
Яндекс.Метрика