Сегодня я приоткрою завесу над 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 nightif 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.
Оставить комментарий