Delphi 2010 RTTI - Основы

Источник: delphi2010
Александр Божко

В Delphi 2009 как и в предыдущих версиях, RTTI был ограничен элементами, размещенными в Published секции.

У вас был доступ к указателям на свойства, поля и методы объекта. Если потратить немного времени на обучение, то старая версия RTTI превращалась в довольно мощный механизм. Однако, мощь RTTI предыдущих версий смотрится весьма скромно по сравнению с тем, что стало возможным в Delphi 2010.

В Delphi 2010 вы можете извлекать RTTI информацию практически отовсюду. Выбор, какая информация должна быть доступна, остается за вами, этим управляет новая директива $RTTI. Поведение по умолчанию определено в System.pas, фрагмент которого приведен ниже, свойства и методы теперь доступны посредствам RTTI как в Published, так и в Public секциях, а поля доступны во всех секциях.

Section of System.pas:
{ RTTI Visibility }
type
  TVisibilityClasses = set of (vcPrivate, vcProtected, vcPublic, vcPublished);

const
  { Эти константы отображают текущие установки, встроенные в компилятор.
    Обычно для классов они наследуются от TObject.
    }
  DefaultMethodRttiVisibility = [vcPublic, vcPublished];
  DefaultFieldRttiVisibility = [vcPrivate..vcPublished];
  DefaultPropertyRttiVisibility = [vcPublic, vcPublished];

type
  { Настройки RTTI по-умолчанию}
  {$RTTI INHERIT
      METHODS(DefaultMethodRttiVisibility)
      FIELDS(DefaultFieldRttiVisibility)
      PROPERTIES(DefaultPropertyRttiVisibility)}

Сама по себе RTTI информация не будет представлять ценности до тех пор, пока у вас не будет удобного доступа к ней.  Новый модуль RTTI.pas предоставляет простой и элегантный метод доступа к этим данным. Гибкость классов, без головной боли с управлением памятью (Memory Management) стала ключевым моментом для нового проекта. Поскольку RTTI доступ реализован через контекст, вместе с высвобождением контекста высвобождаются все RTTI объекты, созданные в рамках контекста.

var
 c : TRttiContext;
begin
  c := TRttiContext.Create;
  try
     // RTTI Access code here
  finally
   c.free;
  end;
end;

Если вы откроете RTTI.pas, вам следует обратить внимание на то, что TRttiContext  не является объектом, это - запись (Record), но не смущайтесь по поводу того, что вы должны вызывать .Create и .Free так же как и для объекта. Это обусловлено необходимостью высвобождать пул RTTI объектов, которые могут быть созданы. Хотя help файл рекомендует не высвобождать их, лично я предпочитаю делать это.

Ввиду большого числа возникших вопросов, я дам более детальное разъяснение.

TRttiContext предоставляет несколько ключевых методов, которые позволяют вам получать доступ к типам в системе.

function GetType(ATypeInfo: Pointer): TRttiType; overload;
function GetType(AClass: TClass): TRttiType; overload;
function GetTypes: TArray<TRttiType>;
function FindType(const AQualifiedName: string): TRttiType;

Например, следующий код  вернет TRttiType класс, репрезентующий, TButton.

var
  c : TRttiContext;
  t : TRttiType;
begin
  c := TRttiContext.Create;
  try
    // Via a String
    t := c.FindType('StdCtrls.TButton');

    // Via the pTypeInfo Pointer
    t := c.GetType(TButton.ClassInfo);

    // Via the class type
    t := c.GetType(TButton);
  finally
    c.Free;
   end;
end;

TRttiType содержит много функций, которые позволяют вам запрашивать члены класса на предмет их типа.

var
  c : TRttiContext;
  t : TRttiType;
begin
  c := TRttiContext.Create;
  try
    // Via a String
    t := c.FindType('StdCtrls.TButton');

    // Via the pTypeInfo Pointer
    t := c.GetType(TButton.ClassInfo);

    // Via the class type
    t := c.GetType(TButton);
  finally
    c.Free;
   end;
end;

Так, например, следующее консольное приложение будет показывать все методы TButton.

program Project10;
{$APPTYPE CONSOLE}
uses
  StdCtrls, TypInfo, Rtti;

var
 c : TRttiContext;
 m : TRttiMethod;
begin
   c := TRttiContext.Create;
   for m in c.GetType(TButton).GetMethods do
   begin
     Writeln(m.ToString);
   end;
   c.Free;
   readln;
end.

Результат.

constructor Create(AOwner: TComponent)
class destructor Destroy
procedure Click
... (Many Lines Removed) ...
procedure AfterConstruction
procedure BeforeDestruction
procedure Dispatch(var Message)
procedure DefaultHandler(var Message)
class function NewInstance: TObject
procedure FreeInstance
class destructor Destroy

Перейдем на следующий уровень.  Приведенный ниже код создает TStringList используя RTTI, вызывая метод Add и получая доступ к свойству Text. Здесь приведен не совсем практичный пример, он создан только для того, что бы показать отдельные возможности. Вы должны обратить внимание на то, что для сохранения значений используется тип TValue. TValue может как сохранять, так и извлекать тип.

 
program Project11;
{$APPTYPE CONSOLE}
uses
  StdCtrls, TypInfo, Classes, Rtti;

var
 c : TRttiContext;
 m : TRttiMethod;
 t : TRttiInstanceType;
 SL : TValue;
 Lines : TValue;
begin
   c := TRttiContext.Create;
   t := (c.FindType('Classes.TStringList') as TRttiInstanceType);
   SL := t.GetMethod('Create').Invoke(t.MetaclassType,[]);
   t.GetMethod('Add').Invoke(SL,['Hello Do you like my hat?']);
   t.GetMethod('Add').Invoke(SL,['I like that hat, what a party hat!']);
   Lines := t.GetProperty('Text').GetValue(SL.AsObject);
   Writeln(Lines.ToString);
   c.Free;
   readln;
end.

Результат.

Hello Do you like my hat?
I like that hat, what a party hat!

Хотя может показаться, что TValue работает подобно variant, TValue не является заменой Variant. Характерно, что если вы назначаете TValue  определенный тип, вы должны извлекать его как этот назначенный тип. Например, вы не сможете назначить TМalue  integer, а извлечь string, в результате вы получите Invalid Type Cast.

Попробуйте пока разобраться с этим, а в следующих статьях мы детальней углубимся в этот вопрос.


Страница сайта http://test.interface.ru
Оригинал находится по адресу http://test.interface.ru/home.asp?artId=22057