апреля 15, 2010

RTTI

Тут я расскажу про некоторые возможности RTTI (run-time type information, информация о типах во время выполнения), которыми сам активно пользуюсь.

Самое простое и всем хорошо известное - это оператор is для проверки принадлежности класса определенному типа, и функция ClassName, возвращающая строку с именем класса.

Чуть по-сложнее - метаклассы:


type
  TMyClass = class
    constructor Create; virtual;
    class function GetValue: Integer;
  end;

  constructor TMyClass.Create;
  begin
  end;

  class function TMyClass.GetValue: Integer;
  begin
    Result := 5050;
  end;

type
  TMyClass2 = class(TMyClass)
  end;

  CMyClass = class of TMyClass;

var
  MyClass: CMyClass;

begin
  // присваиваем наш базовый класс
  MyClass := TMyClass;
  // можем обращаться ко всем классовым функциям
  Writeln(MyClass.GetValue);
  // можем создать экземпляр класса TMyClass
  Writeln(MyClass.Create.ClassName);
  // можем присвоить наследника
  MyClass := TMyClass2;
  // т.к. конструктор сделан виртуальным, следующий вызов приведет к созданию
  // экземпляра TMyClass2
  Writeln(MyClass.Create.ClassName);
end.


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

В классах помимо секций private, protected и public есть еще и четвертая - published. Все, что в ней содержится (методы и свойства), при компиляции сохраняется вместе с названиями. (Модуль для этого нужно компилировать с ключем $M+, иначе published не будет отличаться от public.) Для доступа к этой информации предусмотрено два метода (взято из док-ции на fpc):


public function TObject.MethodAddress(const name: shortstring): pointer;
public function TObject.MethodName(address: pointer): shortstring;


Указатель, который возвращает первая функция, нельзя сразу использовать, нужно сперва заполнить им структуру TMethod:


{$M+}
type
  TMyClass = class
   published
    function GetValue: Integer;
  end;

  TGetValue = function (): Integer of object;

function TMyClass.GetValue: Integer;
begin
  Result := 5050;
end;

var
  I: Integer;
  Obj: TMyClass;
  Method: TMethod;

begin
  Obj := TMyClass.Create;
  Method.Code := Obj.MethodAddress('GetValue');
  Method.Data := Obj;
  I := TGetValue(Method);
  Writeln(I);
  Obj.Free;
end.


Этих двух функций мне оказалось недостаточно. Представим себе, что нужно прочитать xml-файл и на каждый тэг мы прописали метод чтения, и хочется, чтобы эти методы регистрировались автоматически. Для этого нужно как-то извлечь массив со всеми published-методами и их названиями. После изучения модуля System, я написал следующий код


type
TMethodName = record
  Name: TString;
  Method: TMethod;
end;
TArrayOfMethodName = array of TMethodName;

{$IFDEF FLAG_FPC}
  // Версия для Free Pascal (2.2.4)
  // Отличается от Delphi только форматом хранения RTTI
  function GetMethodArray(Obj: TObject): TArrayOfMethodName;
    type
      tmethodnamerec = packed record
        name : pshortstring;
        addr : pointer;
      end;
      tmethodnametable = packed record
        count : Cardinal;
        entries : packed array[0..0] of tmethodnamerec;
      end;
      pmethodnametable =  ^tmethodnametable;
      var
        I: Integer;
        Table: PMethodNameTable;
  begin
    Table := pmethodnametable((Pointer(Obj.ClassType) + vmtMethodTable)^);
    SetLength(Result, Table^.Count);
    for I := 0 to High(Result) do begin
      Result[I].Name := Table^.entries[I].name^;
      Result[I].Method.Code := Table^.entries[I].addr;
      Result[I].Method.Data := Obj;
    end;
  end;
{$ELSE}
  // Версия для Delphi (2009)
  function GetMethodArray(Obj: TObject): TArrayOfMethodName;
    type
      TMethodtableEntry = packed Record
        len: Word;
        addr: Pointer;
        name: ShortString;
      end;
    var
      pp: ^Pointer;
      pMethodTable: Pointer;
      pMethodEntry: ^TMethodTableEntry;
      i, numEntries: Word;
  begin
    pp := Pointer(Integer(Obj.ClassType) + vmtMethodtable);
    pMethodTable := pp^;
    if pMethodTable = nil then begin
      SetLength(Result, 0);
      Exit;
    end;
    SetLength(Result, PWord(pMethodTable)^);
    pMethodEntry := Pointer(Integer( pMethodTable ) + 2);
    for I := 0 to High(Result) do begin
      Result[I].Name := pMethodEntry.name;
      Result[I].Method.Code := pMethodEntry.addr;
      Result[I].Method.Data := Obj;
      pMethodEntry := Pointer(Integer( pMethodEntry ) + pMethodEntry^.len);
    end;
  end;
{$ENDIF}


Функция GetMethodArray возвращает список из уже почти готовых для вызова методов и их названий. После этого можно, например, найти все те, которые содержат в начале слово XML и использовать их для обработки xml-файла.

TString - это псевдокод на обычную ascii-строку (не unicode).


Комментариев нет:

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

Постоянные читатели

Обо мне

Моя фотография
Мой e-mail: vitek_03(at)mail(dot)ru