суббота, 18 сентября 2010 г.

Обзор возможностей Delphi 2010 - визуализаторы для отладчика (Debugger Visualizers)

Это перевод Delphi 2010 Feature Highlight - Debugger Visualizers из декабрьского выпуска 2009-го журнала Blaize Pascal Magazine. Автор: Jeremy North.

Эта статья описывает новую возможность Delphi 2010 - визуализаторы для отладчика (Debugger Visualizers), а также подробно описывает, как вам создать ваш собственный визуализатор.

Что такое визуализатор для отладчика

Визуализатор для отладчика позволяет данным, показываемым в отладчике для конкретного типа данных, быть представленными в другом формате. К примеру, тип TDateTime при просмотре в Watch List или подсказке Evaluator Tooltip будет показан как простое число с плавающей точкой. В большинстве случае это значение вам не нужно. Тут на сцену и выходят визуализаторы для отладчика, которые показывают переменную типа TDateTime (равно как и только TDate или TTime) в простом для чтения формате. Т.е. вместо значения '40105.900806' вы видите '19.10.2009 21:37:09'.

Типы визуализаторов

Есть два типа визуализаторов. Основной тип - это Value-Replacer ("заменитель значения"). Визуализатор этого типа просто подменяет отображаемую строку на отформатированное значение выражения. Для этого типа визуализаторов есть ограничение: только один визуализатор этого типа может быть зарегистрирован для конкретного типа.

Более сложным вариантом является визуализатор типа External-Viewer ("внешний просмотрщик"). Визуализатор этого типа позволяет пользователю показать внешнее окно для отображения дополнительной информации или расширенного GUI для выбранного типа. На визуализаторы этого типа ограничений нет: вы можете зарегистрировать их сколько угодно на один тип.

Встроенные визуализаторы

Delphi 2010 поставляется с двумя встроенными визуализаторами для отладчика (по-умолчанию они включены). Визуализатор TDateTime показывает отформатированное значение "на месте". Это означает, что теперь там, где раньше везде показывалось число с плавающей запятой - теперь будет нормальное отображение даты и времени. Вам не нужно делать никаких дополнительных телодвижений, чтобы активировать этот визуализатор. Визуализатор TDateTime - это пример визуализатора типа Value-Replacer. Визуализатор для TStrings - это пример визуализатора типа External-Viewer. Этот визуализатор показывает всплывающее окно, которое показывает содержимое любого значения типа TStrings.

Вызов визуализатора типа External-Viewer

Визуализаторы с внешним просмотрщиком показываются в IDE несколько иначе, чтобы подсказать, что переменная может быть просмотрена в отдельном окне. Следующие примеры показывают интерфейс пользователя для внешнего визуализатора для Watch List и Evaluator Tooltip:

Визуализатор типа External-Viewer в Watch List
Рисунок 1: Watch list

Визуализатор типа External-Viewer в Evaluator Tooltips
Рисунок 2: Установка точки останова

Щелчок по иконке с раскрывающей стрелкой вниз покажет вам меню со всеми визуализаторами, ассоциированными с переменной этого типа. Да, это так: у вас может быть несколько визуализаторов типа External-Viewer.

Где можно использовать визуализаторы для отладчика?

Визуализаторы доступны во многих местах в IDE. Они работают в таких отладочных окнах как:
  • Watch List
  • Local Variables
  • Debug Inspector
  • Evaluate / Modify
  • Evaluator Tooltips
Хотя вы можете включить/выключить визуализатор в свойствах Watch-а, вы также можете отключить визуализатор в диалоге опций. Зайдите в пункт Debugger Options | Visualizers в окне настроек IDE, чтобы посмотреть список установленных визуализаторов. Этот диалог также показан в этой статье несколько ниже, когда мы будем обсуждать установку своего визуализатора для отладчика. Вы можете захотеть отключить встроенный визуализатор типа Value-Replacer, чтобы предпочесть ему какой-то другой визуализатор - потому что у вас может быть только один визуализатор типа Value-Replacer для заданного типа.

Отключение визуализаторов

Если вы хотите увидеть представление переменной по-умолчанию (без визуализаторов), то у вас есть три способа отключения визуализаторов для конкретного типа:
  1. Откройте свойства Watch-а и сбросьте флажок 'Use visualizer':


    Рисунок 3: баг подкрадывается к свойствам Watch-а.

  2. Отключите визуализатор в списке доступных визуализаторов для отладчика (Tools | Options | Debugger Options | Visualizers).
  3. Сделайте приведение типов для Watch-а. Для примера выше это будет Double(FDateTime). Примечание: это работает не всегда.

Создание своих собственных визуализаторов для отладчика

Надеюсь, что теперь вы имеете представление о том, что такое визуализаторы для отладчика и как они могут упростить отладку. Теперь мы создадим два визуализатора разных типов для типа TColor. Мы начнём с базовых вещей для тех, кто никогда раньше не писал IDE-экспертов.

Написание IDE эксперта - обзор

Для экспертов IDE у вас есть два варианта: пакеты или DLL. Оба варианта имеют свою плюсы и минусы. Пакеты включаются мгновенно: вы устанавливаете их в IDE и они работают сразу же. Вы можете тестировать их во время разработки. DLL же требуют от вас перезапуска IDE (и ручного обновления реестра) или же запуск второй копии IDE для отладки эксперта. В нашей статье визуализаторы для отладчика будут располагаться в пакете.

Примечание: вам следует быть в курсе, что имена пакетов, используемые в IDE, должны быть уникальными среди всех загруженных пакетов.

Настройка группы проектов

Запустите Delphi 2010 и создайте новый проект пакета. Сохраните проект в новой папке с уникальным именем. Я назвал проект ColorVisualizer2010.

Примечание: я не использую свойства Suffix или Prefix (которые в Delphi 2010 называются “Shared object name”).

Сначала мы создадим визуализатор типа Value-Replacer. Для этого добавьте новый модуль в проект пакета и сохраните его как CVAddin.pas. Также добавьте в пакет новый frame и сохраните его как CVViewerFrame.pas. Щёлкните правой кнопкой по узлу Requires нашего пакета в менеджере проектов и выберите команду "Add Reference...". В поле Package Name напечатайте “designide” и нажмите OK. Это пакет, который даёт нам функциональность для IDE эксперта. Сославшись на него, мы теперь можем использовать различные интерфейсы Open Tools API.

Добавьте новый проект в группу проектов. На этот раз добавьте VCL Forms Application - это будет приложение, которое мы будем отлаживать с целью проверки работы наших визуализаторов. Сохраните проект как VisualizerTestProj. Сохраните его главный модуль как MainForm. И, наконец, сохраните всю группу проектов как ColorVisualizer2010Group.

Снимок экрана ниже показывает, как должен выглядеть вам менеджер проекта к этому моменту:

Менеджер проекта с открытой группой проектов для визуализатора
Рисунок 4: вид менеджера проектов

Регистрация визуализатора для отладчика

Open Tools API построено на интерфейсах. Практически любая функциональность требует от вас регистрации класса, который реализует конкретные интерфейсы.

Чтобы визуализатор мог работать, он должен быть зарегистрирован в IDE. Это делается с помощью вызова метода RegisterDebugVisualizer интерфейса IOTADebuggerServices. Метод принимает параметр интерфейсного типа IOTADebuggerVisualizer. Это означает, что мы должны создать класс, который реализует этот интерфейс. Вероятно, вы захотите наследовать класс от TInterfacedObject. Ну а поскольку мы собираемся реализовывать ещё и интерфейс IOTADebuggerVisualizer, то нам также необходимо реализовать методы этого интерфейса.
type
  TColorVisualizer = class(TInterfacedObject, IOTADebuggerVisualizer)
    procedure GetSupportedType(Index: Integer; var TypeName: String; var AllDescendents: Boolean);
    function GetSupportedTypeCount: Integer;
    function GetVisualizerDescription: String;
    function GetVisualizerIdentifier: String;
    function GetVisualizerName: String;
  end;
Этот класс должен возвращать такие данные:
GetVisualizerName – используется для получения имени визуализатора в диалогах настроек.
GetVisualizerDescription – описание визуализатора в нём же.
GetVisualizerIdentifier – возвращает уникальный идентификатор визуализатора. Вероятно, строковое представление GUID будет неплохим выбором для этого поля. Если же вы используете читабельную строку - то добавьте к ней хотя бы префикс вашей компании.
GetSupportedTypeCount – возвращает число поддерживаемых вашим визуализатором типов.
GetSupportedType – этот метод вызывается GetSupportedTypeCount число раз для получения информации о поддерживаемых вами типах. Параметр Index указывает индекс итерации (от 0 до GetSupportedTypeCount - 1). Для каждого вызова вы должны вернуть имя поддерживаемого вами типа в параметре TypeName. В нашем примере у нас будет только один тип: 'TColor'.

Примечание: параметр AllDescendents игнорируется в Delphi 2010 для визуализаторов, поэтому вы должны всегда ставить его в False. Если вы хотите зарегистрировать визуализатор для типа-класса и всех его подклассов - вам придётся регистрировать визуализатор для каждого дочернего класса.

Хотя мы создали класс, реализующий интерфейс IOTADebuggerVisualizer, а его регистрация даже покажет визуализатор в спике доступных визуализаторов для отладчика, но реально он не будет работать, потому что для этого нам нужно реализовать ещё как минимум один интерфейс: IOTADebuggerVisualizerValueReplacer или IOTADebuggerVisualizerExternalViewer.

Регистрация визуализатора

Прежде чем мы будем реализовывать эти интерфейсы, давайте сначала зарегистрируем наш наполовину готовый визуализатор.

Когда IDE загружает пакет, она проверяет каждый модуль в пакете на наличие процедуры с именем Register (имя чувствительно к регистру). Если таковая находится, то IDE вызывает её. Обычно в этой процедуре пакеты размещают код регистрации компонент, экспертов и других IDE-вещей.

Код ниже показывает регистрацию визуализатора:
var
  _Color: IOTADebuggerVisualizer;

procedure Register;
var
  LServices: IOTADebuggerServices;
begin
  if Supports(BorlandIDEServices, IOTADebuggerServices, LServices) then
  begin
    _Color := TColorVisualizer.Create;
    LServices.RegisterDebugVisualizer(_Color);
  end;
end;

procedure RemoveVisualizer;
var
  LServices: IOTADebuggerServices;
begin
  if Supports(BorlandIDEServices, IOTADebuggerServices, LServices) then
    LServices.UnregisterDebugVisualizer(_Color);
  _Color := nil;
end;

initialization

finalization
  RemoveVisualizer;

end.
Когда вы пишете код, который будет работать в IDE, я рекомендую вам использовать защитное программирование и добавлять побольше проверок. Помните: один элементарный баг может привести к вылету среды - и вы точно этого не хотите.

Вместо использования Supports на глобальной переменной BorlandIDEServices (объявленной в модуле ToolsAPI), мы могли бы использовать конструкцию as, чтобы получить тот же результат. Ну или почти тот же. Потому что использование Supports не генерирует исключения, если BorlandIDEServices не реализует интерфейс IOTADebuggerServices.

Реализация рабочих интерфейсов визуализатора

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

Value-Replacer

Чтобы создать визуализатор типа Value-Replacer, вам нужно реализовать интерфейс IOTADebuggerVisualizerValueReplacer:
type
  IOTADebuggerVisualizerValueReplacer = interface(IOTADebuggerVisualizer)
  ['{6BBFB765-E76F-449D-B059-A794FA06F917}']
    function GetReplacementValue(const Expression, TypeName, EvalResult: String): String;
  end;
Добавьте интерфейс и метод GetReplacementValue в наш класс. Сейчас он должен выглядеть примерно так:
type
  TColorVisualizer = class(TInterfacedObject, TOTADebuggerVisualizer, IOTADebuggerVisualizerValueReplacer)
    function GetReplacementValue(const Expression, TypeName, EvalResult: String): String;
    procedure GetSupportedType(Index: Integer; var TypeName: String; var AllDescendents: Boolean);
    function GetSupportedTypeCount: Integer;
    function GetVisualizerDescription: String;
    function GetVisualizerIdentifier: String;
    function GetVisualizerName: String;
  end;
Для нашего визуализатора-примера реализация метода GetReplacementValue будет:
function TColorVisualizer.GetReplacementValue(const Expression, TypeName, EvalResult: String): String;
begin
  Result := ColorToString(StrToInt(EvalResult));
end;
Expression - это имя переменной.
TypeName - имя типа параметра выражения.
EvalResult - представление результата выражения по-умолчанию.

Функция ColorToString объявлена в модуле Graphics.pas. Она возвращает строковое представление некоторых стандартных цветов VCL и Windows - типа clNavy и clBtnFace. Если цвет не является одним из стандартных, то возвращается HEX-представление цвета.

External-Viewer

Чтобы создать визуализатор типа External-Viewer, реализуйте интерфейс IOTADebuggerVisualizerExternalViewer:
type
  IOTADebuggerVisualizerExternalViewer = interface(IOTADebuggerVisualizer)
    function GetMenuText: String;
    function Show(const Expression, TypeName, EvalResult: String; SuggestedLeft, SuggestedTop: Integer): IOTADebuggerVisualizerExternalViewerUpdater;
  end;
Первое, что вам нужно заметить в этом объявлении - что этот интерфейс возвращает новый интерфейс. Да, это потому что визуализаторы типа External-Viewer более сложны, чем Value-Replacer.

Нам нужно реализовать методы:
GetMenuText – вспомните, что визуализаторы типа External-Viewer вызываются из всплывающего меню. Этот метод должен вернуть заголовок элемента меню для вызова нашего визуализатора.
Show – IDE вызывает этот метод, когда пользователь щёлкает по нашему пункту меню.

Параметры Expression, TypeName и EvalResult имеют тот же смысл, что и в методе GetReplacementValue интерфейса IOTADebuggerVisualizerValueReplacer. SuggestedLeft и SuggestedTop указывают рекомендуемое положение на экране для окна визуализатора.

Ваш метод показа визуализатора должен создать окно, которое показывает выражение и вернуть интерфейс IOTADebuggerVisualizerExternalViewerUpdater. Это означает, что вы должны создать класс, который будет его (интерфейс) реализовывать. В нашем примере для простоты интерфейс будет реализован фреймом:
type
  IOTADebuggerVisualizerExternalViewerUpdater = interface
    procedure CloseVisualizer;
    procedure MarkUnavailable(Reason: TOTAVisualizerUnavailableReason);
    procedure RefreshVisualizer(const Expression, TypeName, EvalResult: String);
    procedure SetClosedCallback(ClosedProc: TOTAVisualizerClosedProcedure);
  end;
CloseVisualizer – когда вызывается этот метод, вам нужно закрыть ваше окно. Это означает, что поток, ответственный за ваш вызов, завершился.
MarkUnavailable – этот метод вызывается, когда значение становится недоступным. Сейчас это может быть из-за “Out of scope” или “Process not accessible”.
RefreshVisualizer – этот метод вызывается, когда значение нуждается в обновлении. Он вызывается, когда пользователь использует диалог Evaluate/Modify для изменения значения, которое показывает ваш визуализатор. Если визуализатор невидим, то он ничего и не показывает.
SetClosedCallback – устанавливает функцию обратного вызова (параметр ClosedProc), которую вы должны вызывать, когда ваше окно закрывается, так что IDE может перестать вызывать RefreshVisualizer. Это означает, что вам надо сохранить этот параметр для дальнейшего использования.

Ниже показана реализация метода Show. Этот код вызывает классовый метод фрейма. Я специально скрыл реализацию метода, потому что она не имеет отношения к статье. Всё, что вам нужно знать, - что IDE фрейм, спроектированный для визуализатора, является дочерним контролом dockable-формы IDE.
function TColorVisualizer.Show(const Expression, TypeName, EvalResult: String; SuggestedLeft, SuggestedTop: Integer): IOTADebuggerVisualizerExternalViewerUpdater;
begin
  Result := TfmCVViewer.CreateAndShow(Expression, TypeName, EvalResult, SuggestedLeft, SuggestedTop);
end;

Вызов методов для выражений

Когда вы создаёте визуализатор типа External-Viewer, вы можете захотеть вызывать методы для выражений, которые вы показываете. Например, у вас может быть визуализатор TTable, и вы хотите показать содержимое текущей записи и количество записей всего. Вы можете сделать это, вызывая методы TTable - для этого есть специальная техника, которую я не буду описывать здесь. Она будет описана в следующем выпуске журнала.

Поддержка C++

Сначала я должен сказать, что я НЕ являюсь разработчиком C++.

Чтобы поддерживать C++ в визуализаторе, вам нужно изменить две вещи. Во-первых, вам нужно возвращать 2 как число поддерживаемых визуализатором типов данных, а также возвращать полное имя типа в параметре TypeName метода GetSupportedType. Оба этих изменения были сделаны в исходном коде, прилагающемся к статье. Благодарю тех двух разработчиков C++, которые указали на этот момент.

Создание интерфейса пользователя для визуализатора

Фрейм, который мы добавили в проект - вот то, что будет видеть пользователь. Мы сделаем в нём самый примитивный пользовательский интерфейс - панель, меню и action list. Цвет фрейма будет показывать цвет выражения, а имя/значение цвета будет показано на панели. Action list и меню будут содержать действия для копирования выражения и результата вычисления в буфер обмена.

Снимок экрана ниже показывает фрейм в режиме проектирования:

Фрейм визуализатора в режиме проектирования
Рисунок 5: Фрейм.

Установка визуализатора

Чтобы установить визуализатор, щёлкните правой кнопкой мыши на менеджере проектов и выберите команду "Install". После установки вы можете найти наш визуализатор в секции Debugger Options | Visualizers диалога Tools | Options dialog.

Готовый визуализатор в работе

Осталось показать, как визуализатор работает в IDE.

Запустите тестовый проект, прикладываемый к примеру - это простое приложение с одной формой и тремя кнопками:

Пользовательский интерфейс тестового проекта
Рисунок 6: Форма тестового приложения.

Каждая кнопка меняет свойство Color формы.

Установите точки останова на обработчики событий всех кнопок и нажмите на первую кнопку. После остановки активируйте визуализатор, выбором команды "Show Color" из меню визуализаторов:

Вызов визуализатора
Рисунок 7: Вызов тестового визуализатора из списка визуализаторов.

На фоне вы также можете видеть работу визуализатора Value-Replace, который показывает clBtnFace.

После нажатия же на "Show Color" вы увидите окно визуализатора типа External-Viewer:

Рисунок 8: Визуализатор до выполнения команды.

Рисунок 9: Визуализатор после выполнения команды.

Если вы выберите пользовательский цвет по третьей кнопке, который не совпадает со стандартными цветами, то визуализатор будет выглядеть примерно так:

Рисунок 10: HEX-представление пользовательского цвета.

Заключение

У меня нет сомнений, что визуализаторы для отладчика упростят ваши сессии отладки. Это особенно так, когда вам нужно закапываться и вызывать методы для визуализируемого выражения (напомню, эта тема следующей статьи).

Исходный код и тестовый проект доступны для всех подписчиков журнала Blaise Pascal. Не забудьте прочитать следующий номер с продолжением этой статьи.

2 комментария:

  1. Рульная вещь, ни хрена про неё не знал, хоть уже и полгода на ксе. Все бахал шоумессаджес и стринглисты в файл...)))

    ОтветитьУдалить
  2. А зачем было переводить неполную статью?

    ОтветитьУдалить

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

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

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

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

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