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

Оригинал.

Сегодня я приоткрою завесу над TRttiMethod. Это основная причина того, почему мне нравится новый RTTI в Delphi 2010. В предыдущих версиях Delphi вызов динамических методов был темным искусством, и имел множество ограничений. Он был тяжелым и зависел от того, как скомпилирован ваш код. По умолчанию эта возможность была отключена для VCL классов. В предшествующих Delphi 2010 версиях вам требовалось слишком много знать о внутренней структуре метода {$METHODINFO}, для того, что бы вызвать метод динамически.

Теперь, в Delphi 2010, по умолчанию генерируется RTTI информация для методов в public и published секциях. Мне не нужно ничего знать о как хранится RTTI информация. Существует очень элегантный и легкий способ использования API для динамического запрашивания и вызова методов, который работает для всех VCL классов.
Вы должны понимать, что с помощью TRttiMethod и TValue вы имеете возможность динамически вызывать практически любой метод.
TRttiMethod наследуется от TRttiMember, при том в TRttiMember были определены Name & Visibility. И возможно нам понадобится знать много других аспектов данных методов.
Несколько ключевых свойств заданы для того, что бы позволить нам легко получить доступ к этой информации.

TypInfo.pas
  TMethodKind = (mkProcedure, mkFunction, mkConstructor, mkDestructor,
    mkClassProcedure, mkClassFunction, mkClassConstructor, mkClassDestructor,
    mkOperatorOverload,
    { Obsolete }
    mkSafeProcedure, mkSafeFunction);

  TCallConv = (ccReg, ccCdecl, ccPascal, ccStdCall, ccSafeCall);

Rtti.pas

  TDispatchKind = (dkStatic, dkVtable, dkDynamic, dkMessage, dkInterface);

In TRttiMember
    property MethodKind: TMethodKind read GetMethodKind;
    property DispatchKind: TDispatchKind read GetDispatchKind;

    property CodeAddress: Pointer read GetCodeAddress;
    property IsConstructor: Boolean read GetIsConstructor;
    property IsDestructor: Boolean read GetIsDestructor;
    property IsClassMethod: Boolean read GetIsClassMethod;
    // Static: No 'Self' parameter
    property IsStatic: Boolean read GetIsStatic;

    // Vtable slot for virtual methods.
    // Message index for message methods (non-negative).
    // Dynamic index for dynamic methods (negative).
    property VirtualIndex: Smallint read GetVirtualIndex;
    property CallingConvention: TCallConv read GetCallingConvention;
    property CodeAddress: Pointer read GetCodeAddress;

Теперь эта информация абсолютно бесполезна, если вы не знаете параметров данных методов и возможных типов результата.  Существуют свойства и функции для получения такой информации.

function GetParameters: TArray<TRttiParameter>; virtual; abstract;
property ReturnType: TRttiType read GetReturnType;

Здесь пример, демонстрирующий метод  TStringList.AddObject(),  выглядит следующим образом:

program Project12;
{$APPTYPE CONSOLE}
uses
  Classes, Rtti;
var
  ctx : TRttiContext;
  t : TRttiType;
  Param : TRttiParameter;
  AddObjectMethod : TRttiMethod;
begin
  ctx := TRttiContext.Create;
  t := ctx.GetType(TStringList.ClassInfo);
  AddObjectMethod := t.GetMethod('AddObject');
  for Param in AddObjectMethod.GetParameters do
  begin
    Writeln(Param.ToString);
  end;
  Writeln('Returns:', AddObjectMethod.ReturnType.ToString );
  readln;
  ctx.Free;
end.

Результат

S: string
AObject: TObject
Returns:Integer

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

typInfo.pas
...
  TParamFlag = (pfVar, pfConst, pfArray, pfAddress, pfReference, pfOut, pfResult);
  {$EXTERNALSYM TParamFlag}
  TParamFlags = set of TParamFlag;
...

Rtti.pas

TRttiNamedObject
...
    property Name: string read GetName;
...

TRttiParameter = class(TRttiNamedObject)
...
    function ToString: string; override;
    property Flags: TParamFlags read GetFlags;
    // ParamType may be nil if it's an untyped var or const parameter.
    property ParamType: TRttiType read GetParamType;
...

Теперь, когда вы имеете доступ к информации о данном методе, вы можете вызвать его.
В примере показано два вызова, посредствам конструктора, и второй  – с помощью метода Add() класса TStringList.

program Project12;
{$APPTYPE CONSOLE}
uses
  Classes, Rtti, TypInfo;
var
  ctx : TRttiContext;
  t : TRttiType;
  Param : TRttiParameter;
  AddMethod : TRttiMethod;
  SL : TValue; // Contains TStringList instance

begin
  ctx := TRttiContext.Create;
  t := ctx.GetType(TStringList.ClassInfo);
  // Create an Instance of TStringList
  SL := t.GetMethod('Create').Invoke(t.AsInstance.MetaclassType,[]);
  // Invoke "Add" and return string representatino of result.
  Writeln(t.GetMethod('Add').Invoke(SL,['Hello World']).ToString);
  // Write out context.
  Writeln((sl.AsObject as TStringList).Text);
  readln;
  ctx.Free;
end.

Результат

0
Hello World

Существует три перегружаемых (overloaded) версии Invoke

function Invoke(Instance: TObject; const Args: array of TValue): TValue; overload;
function Invoke(Instance: TClass; const Args: array of TValue): TValue; overload;
function Invoke(Instance: TValue; const Args: array of TValue): TValue; overload;

В предыдущем примере вы можете заметить, что я использовал TValue и TClass версии, а теперь давайте посмотрим на более сложную ситуацию, используя последний вариант вызова.
При использовании методов, которые меняют параметры при вызове, (они имеют в декларации “Var”), в оригинальный массив, который вы передаете в качестве параметра, вносятся изменения.
Данная программа показывает, как это работает.

program Project12;
{$APPTYPE CONSOLE}
uses
  Classes, Rtti, TypInfo;
const
 AreYouMyMotherISBN = '0-679-89047-5';
type
  TBookQuery = class(TObject)
  public
    function FindBook(ISBN : String;var Title : String) : Boolean;
  end;

  function TBookQuery.FindBook(ISBN : String;var Title : String) : Boolean;
  begin
    Writeln('Checking:',ISBN);
    // Find one of the books, I get to read every night :-)
    if ISBN = AreYouMyMotherISBN then
    begin
      result := true;
      Title := 'Are you my Mother?'
    end
    else
    begin
      Title := '';
      result := false;
    end;
  end;

var
  ctx : TRttiContext;
  BQ : TBookQuery;
  Args : Array Of TValue;
  Param : TRttiParameter;
  FindBook : TRttiMethod;
  SL : TValue; // Contains TStringList instance

begin
  ctx := TRttiContext.Create;
  BQ := TBookQuery.Create;
  FindBook := Ctx.GetType(TBookQuery.ClassInfo).GetMethod('FindBook');

  SetLength(args,2);
  Args[0] := '123';  // an ISBN that won't be found
  Args[1] := '';

  // Invoke the Method  
  if FindBook.Invoke(BQ,Args).AsBoolean then
   writeln(args[1].ToString)
  else
   writeln('Not Found');

  SetLength(args,2);
  Args[0] := AreYouMyMotherISBN;  // an ISBN that will be found
  Args[1] := '';

  // Invoke the Method
  if FindBook.Invoke(BQ,Args).AsBoolean then
   writeln(args[1].ToString)
  else
   writeln('Not Found');

  readln;
  BQ.Free;
  ctx.Free;
end.

Результат

Checking:123
Not Found
Checking:0-679-89047-5
Are you my Mother?

Обновлено:
Я не полностью рассказал о том, как запросить и получить доступ к TRttiMethods. Я только показал “GetMethod()” для TRttiType, но существует четыре способа получения информации.

// Get's all of the methods on a given class, with the declared ones first.
function GetMethods: TArray<TRttiMethod>; overload; virtual;
// Will return the first method it finds with the given name
function GetMethod(const AName: string): TRttiMethod; virtual;
// Will return all of the methods it finds with a given method, so you can deal with overloads.
function GetMethods(const AName: string): TArray<TRttiMethod>; overload; virtual;
// Will only get methods declared on the given class, and not on parents.
function GetDeclaredMethods: TArray<TRttiMethod>; virtual;

Теперь это действительно все, что нужно для использования TRttiMethod. Если вы попробуете сделать это в предыдущих версиях Delphi, я уверен, что вас очень порадуют изменения. Если же вы до этого не пробовали воспроизвести нечто подобное,  сейчас же переходите на Delphi 2010 и используйте новую функциональность.  Сейчас вы, возможно, спросите, почему я использую его. Я надеюсь, что вы найдете ответ на этот вопрос в практических статьях, которые скоро последуют. Однако, в следующую статью я посвящу углубленному исследованию TValue.


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

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

Ваш 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
Яндекс.Метрика