среда, 27 июля 2011 г.

RTTI классов

Это перевод Class RTTI. Автор: David Glassborow.

Этот пост является продолжением моего предыдущего поста, который говорил про RTTI интерфейсов в Delphi и был вдохновлён серией постов Hallvard-а по RTTI. Этот пост рассказывает о некоторых продвинутых возможностях RTTI для классов, описания которых я больше нигде не видел.

$METHODINFO

Когда я игрался с WebSnap в Delphi, пытаясь расширить некоторые из его объектов, доступных для скриптинга, я наткнулся на директиву компилятора METHODINFO.

Online-справка говорит нам:
Директива $METHODINFO эффективна только если была включена генерация RTTI информации директивой {$TYPEINFO ON}. В режиме {$TYPEINFO ON} директива $METHODINFO контролирует генерацию более подробного описания RTTI для методов в интерфейсе. Хотя директива {$TYPEINFO ON} приведёт к генерации какой-то RTTI информации для published методов, но уровень этой информации ограничен. Включение директивы $METHODINFO генерирует намного более детализированную (и объёмную) RTTI информацию для методов, которая описывает, как нужно передавать методу его параметры - в стеке или в регистрах. Необходимость использовать переключатель компилятора $METHODINFO в вашем коде возникает очень редко, если вообще возникает. Подробная информация о методах занимает много места, поэтому включение этого режима не рекомендуется для обычного использования.
Мой предыдущий пост показал, что это не совсем верно: детализированная RTTI доступна для любого интерфейса с $TYPEINFO или $M. Кажется, директива $METHODINFO влияет только на классы, в частности, детализированная информация будет генерироваться не только для published методов, но также для секции public.

Произведя поиск этой директивы в файлах исходных кодов Delphi, я нашёл только одно упоминание - в файле WebSnapObjs.pas:
{$METHODINFO ON}
TScriptableObject = class(TObjectDispatch)
private
  FLookupList: TStringList;
  FLookupValues: TInterfaceList;
protected
  FPreferChild: Boolean;
  function DispatchOfName(const AName: string): IDispatch; virtual;
  function FindObject(const AName: string): TObject; virtual;
public
  constructor Create;
  destructor Destroy; override;
  class function DispatchOfObject(const AObject: TObject): IDispatch;
  function GetIDsOfNames(const IID: TGUID; Names: Pointer; NameCount: Integer; LocaleID: Integer; DispIDs: Pointer): HRESULT; override;
  function Invoke(DispID: Integer; const IID: TGUID; LocaleID: Integer; Flags: Word; var Params; VarResult: Pointer; ExcepInfo: Pointer; ArgErr: Pointer): HRESULT; override;
end;
{$METHODINFO OFF}

WebSnap

WebSnap - это бедный родственник в мире веб-фреймворков для Delphi. Он никогда не имел большой поддержки, и, кажется, теперь заслоняется ASP.NET и IntraWeb. Мне лично он очень нравится, хотя я создаю мои собственные шаблоны в VBScript или JavaScript, а не использую какие-то веб-страницы из design-time.

Под капотом WebSnap использует движок ActiveScripting, предоставляемый Windows. ActiveScripting - это сервер выполнения скриптов, который может выполнять скрипты на любом языке, поддержка которого устанавливается в сервер с помощью COM плагина. В штатной поставке Windows имеется поддержка VBScript и JScript (который, на самом деле, JavaScript). Другие примеры поддержки языков в ActiveScripting включают в себя Python, Perl и PHP (прим.пер.: существуют, но менее распространены реализации движков на языках Basic, Haskell, REXX, Delphi, XSLT, Tcl, Ruby).

Исходный вариант ASP от Microsoft использовал возможности ActiveScripting для выполнения работы. Шаблон ASP превращался в программу на VBScript или JScript, содержащую HTML для вывода и логику страницы. Этот код скармливался движку ActiveScripting и компилировался им для реального выполнения. Движок ActiveScripting имел "объекты", манипулированием которых, программа могла делать что-то полезное. Самым очевидным был объект Response, но были и другие - вроде Session и т.п. Программа запускалась и рендерила страницу, возвращаемую клиенту.

Страницы WebSnap (по крайней мере, использующие TPageProducer) использовали подобный же подход для генерации HTML страниц. Проблема разработчиков Delphi заключалась в том, как связать произвольные объекты Delphi с движком ActiveScripting, который использует позднее связывание через COM-ский IDispatch для связи. Интерфейс IDispatch - один из главных фундаментов COM в Windows, использует единственный метод (Invoke) для вызовов любых методов. Вот где на сцену выходит $METHODINFO - богатое описание методов информацией RTTI позволяет выполнить вызов всего в одной точке (Invoke) для вызова произвольных методов Delphi.

Скрипты VBScript или JavaScript, запущенные в скриптовой части страницы WebSnap, нуждаются в возможности общения с объектами Delphi (вроде Page и Session), и для реализации этой возможности Delphi использует расширенный RTTI. Вы можете увидеть объекты WebSnap, предоставляемые скриптам - посмотрите в WebSnapObjs.pas объекты TResponseObj, TProducerObj и т.д.

Модуль ObjAuto содержит код и заголовок для извлечения RTTI информации, используя такую функцию:
function GetMethodInfo(Instance: TObject; const MethodName: ShortString): PMethodInfoHeader;
В свою очередь, базовый класс TScriptableObject (помеченный $METHODINFO) использует эту RTTI информацию для поиска методов и их вызова в run-time.

ObjAuto.pas

Этот модуль содержит код для поиска RTTI метода. Посмотрев на GetMethodInfo, вы увидите, что он использует vmtMethodTable (объявленное в System.pas) для получения доступа к таблице методов класса. Затем он ищет метод в этой таблице. Модуль также содержит код, который может сделать произвольный вызов, построив передачу параметров по RTTI метода и переходом к коду метода:
function ObjectInvoke(Instance: TObject; MethodHeader: PMethodInfoHeader; const ParamIndexes: array of Integer; const Params: array of Variant): Variant;
Как вы можете видеть, вы можете передать параметры в виде вариантов (variant), а функция преобразует их в нужный тип, упакует их в регистры и/или стек и сделает вызов. Исходный код этой функции показывает всю сложность этой задачи с учётом различных моделей вызова. Вот как объекты на VBScript могут вызывать объекты Delphi внутри WebSnap.

DetailedRTTI.pas

Пока я игрался с мета-данными, я создал несколько вспомогательных helper классов для помощи в разборе этой информации. Вы можете скачать код, если захотите с этого начать. Простой вызов .RTTIMethodsAsString() для любого объекта, чтобы получить список его методов и параметров. Код немного грубоват, но вы вольны использовать его как угодно.

Итоги

Эта и предыдущая статьи показывают возможности расширенной RTTI информации для методов, доступную в Delphi, и рассказывает о вспомогательных подпрограммах для доступа к ней. Мета-данные интерфейсов позволяют VCL поддерживать SOAP, где множество методов соединены в одну точку вызова. Мета-данные классов позволяют VCL поддерживать WebSnap, где также множество методов соединены в одну точку вызова - позволяя автоматически прозрачно проецировать COM-й IDispatch на методы Delphi объектов.

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

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

Можно использовать некоторые HTML-теги, например:

<b>Жирный</b>
<i>Курсив</i>
<a href="http://www.example.com/">Ссылка</a>

Вам необязательно регистрироваться для комментирования - для этого просто выберите из списка "Анонимный" (для анонимного комментария) или "Имя/URL" (для указания вашего имени и ссылки на сайт). Все прочие варианты потребуют от вас входа в вашу учётку (поддерживается OpenID).

Пожалуйста, по возможности используйте "Имя/URL" вместо "Анонимный". URL можно просто не указывать.

Ваше сообщение может быть помечено как спам спам-фильтром - не волнуйтесь, оно появится после проверки администратором.