Визуализаторы в Microsoft Visual C++

Microsoft Visual Studio я считаю лучшей из доступных IDE для разработки C++, которой в основном и занимаюсь. И это при том, что я являюсь идеологическим противником корпорации из Редмонда. Просто IDE для Линукса слабы. Вообще все решения в этой области, основанные на GCC провальны из-за дебаггера. Не знаю, то ли GDB настолько плох, то ли никто не в состоянии нормально встроить его в IDE, то ли он просто идеологически для этого не предназначен. Eclipse c CDT монструозен и тормознут. Vi или emacs с командной строкой и make, которое сразу же посоветует какой-нибудь Линукс-джедай, я лично осилить до того уровня производительности, что имею в MSVS, не в состоянии (а в состоянии ли вообще кто-то?). Впрочем, справедливости ради отмечу, что не сильно-то я и старался.

Альтернативные среды вроде Code::Blocks и Dev-Cpp, когда я последний раз смотрел, были в очень зачаточном состоянии. Вот сегодня скачал первую, собираюсь пересмотреть ещё раз — вдруг что-то изменилось. Но всё это тема для отдельного долгого разговора, перерастающего в холивар. А этот пост именно о Visual C++ и одной её полезной и практически не документированной фиче, на которую я недавно набрёл. Это — кастом визуализаторы (custom visualizers) для переменных — то, что разработчик видит в панельке Watch во время отладки программы. Вещь очень удобная и сильно облегчающая жизнь, если вы, например, как и я, любите писать «велосипеды» в виде собственных библиотек, или вообще часто используете какую-нибудь стабильную библиотеку/фреймворк/движок со своими, сложными для быстрого понимания as is в отладчике, типами данных.

Америку я не открываю, всеобъемлющего мануала тоже не напишу, но, возможно, кому-то нижепреведенная информация окажется полезной. Ещё раз уточню, зачем всё это нужно. Дело в том, что если в отладчике вы посмотрите значение переменной нестандартного типа с достаточной сложной структурой да ещё и, упаси Страуструп, содержащей указатели, то, во-первых, потратите немало времени, просто разбираясь в этой структуре, которая может содержать совершенно не интересные вам детали реализации (например, какие-нибудь умные указатели), во-вторых, регулярно занимаясь пиксель-хантингом по крестикам, раскрывающим бесконечные древообразные списки, и, в-третьих, вообще можете не увидеть там нужные вам данные (к примеру, если какой-то указатель внутри структуры по какой-то причине имеет, скажем, тип void*).

Приведём простой пример. Скажем, мы задались целью написать идеальный класс для текстовой строки, которая умеет хранить как текст в Юникоде, так и однобайтную строку стандартного типа сhar*. При чём пользователь класса не должен заморачиваться насчёт того, что там и как хранится: записал туда однобайтную строку — она и хранится однобайтная, добавил, к примеру, к ней юникодный символ и строка конвертировалась в Юникодную — в таком вот духе. Структура данных для такой строки может выглядеть к примеру так:

class MyString
{
	bool unicode_; // Флаг, true — если хранится строка в Юникоде, false — если однобайтная
	int length_; // Длина строки
	union
	{
		unsigned short* uData_; // Указатель на Юникодную строку
		char* bData_; // Указатель на однобайтную строку
	};
};

Если мы запишем сюда однобайтную строку и честно выставим флажок unicode_ = false, то увидим в отладчике примерно следующее (Microsoft Visual C++ Express 2008):

Рисунок 1

Если нажать крестик слева, то ситуация будет чуть получше:

msvc-visualizers-fig2

Но это же надо нажимать каждый раз, когда надо узнать значение строки, при чём самому «обрабатывать» значение флажка unicode_. Такие вещи в XXI веке, я считаю, должен делать компьютер. Визуализаторы нам в помощь. При разработке на managed языках под .NET, визуализаторы можно писать прямо в коде. Когда-то, когда я программировал на С#, я разбирался с этим, но сейчас уже не помню деталей. В любом случае, эти вещи хорошо документированы в MSDN и особых проблем вызывать не должны. А вот с unmanaged C++ всё по-другому. С незапамятных времён отладчик от MS предоставлял ряд возможностей для настройки через файл autoexp.dat, находящийся по пути:

(Visual Studio Root Directory)\Common7\Packages\Debugger\autoexp.dat

Начиная с Visual Studio 2005, возможности для настройки серьёзно расширились. autoexp.dat состоит из секций, начало каждой отмечается названием в квадратных скобках, как в ini-файлах. Первая секция [AutoExpand] документирована в комментариях внутри самого файла (комментарии начинаются с «;»). Её возможностей может быть достаточно для решения простых задач, но не для нашего класса строки, так как нам требуется возможность выводить значения разных переменных в зависимости от значения флажка. Иными словами, нам нужен условный оператор. Вторая секция [Visualizer] решает эту проблему. Она не документирована, но autoexp.dat содержит кучу визуализаторов для типов из STL и WinAPI. Разобравшись с тем, что там и как, мы видим, что в решении нашей задачи нет ничего сложного. Итак, по порядку.

  1. Начнём новый визуализатор. Для этого необходимо указать название нашего типа и открыть фигурные скобки:
    MyString {
    Важно, чтобы фигурная скобка находилась на той же строке, иначе фокус не сработает. Пробелы между названием типа и скобкой допустимы.
  2. Внутри визуализатора может находится несколько блоков — об этом ниже. Пока нас интересует блок preview. Для отделения блока используются круглые скобки. Определим блок.
    preview
    (
    )
  3. Добавим собственно код визуализатора.
    preview
    (
        #if(($e.unicode_) == true)
        (
            #([$e.uData_,su], " [", $e.length_, "]")
        )
        #else
        (
            #([$e.bData_,s], " [", $e.length_, "]")
        )
    )
    На место $e будет подставлено визуализируемое выражение. Блок #( )выполняет слияние строк (задаются в двойных кавычках) и вычисляемых выражений. Если заключить выражение в квадратные скобки, то можно указать дополнительные параметры для визуализации. Например, в нашем случае [$e.uData_,su] явно указывает, что uData_ следует визуализировать, как строку в Юникод. Блок #if — #else интуитивно понятен.

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

msvc-visualizers-fig3

Теперь работать с нашим типом гораздо удобнее. Детали реализации скрыты от пользователя, и он просто видит то, что и ожидает — значение, хранящееся в строке. А что если пользователя всё-таки заинтересуют эти детали? Например, он всё-таки захочет узнать, является ли данная строка Юникодной. Изучим, какие ещё имеются возможности.

Общий вид визуализатора:

typename[|typename...] {
    preview (
        ; выражение
    )
    children (
        ; выражение
    )
    stringview (
        ; выражение
    )
}

Отметим, что в заголовке визуализатора мы можем через «|» указать несколько типов, для которых будет применён данный визуализатор. Разделы внутри используются следующим образом:

  • preview — собственно, для предпросмотра в окне «Watch»;
  • children — это блок позволяет нам определить, что будет показано в окошке «Watch», если пользователь «раскроет» значение выражения (нажмёт на крестик слева). Можно использовать специальные возможности #array, #list и #tree, позволяющие визуализировать эти структуры данных.
  • stringview — это то, что будет отображаться в «Text Visualizer», окошке, которое можно вызвать кликнув по иконке с изображением увеличительного стекла, справа от значения переменной. Хорошая новость — там можно использовать HTML, плохая новость — в вычисялемых строках нельзя заменять специальные символы HTML на эксейп-последовательности, а также не работают возможности, предлагаемые #array, #list и #tree. Что делает эту возможность мало полезной.

Добавим в children такой код:

children
(
    #(
        unicode: [$e.unicode_]
    )
)

В результате имеем:

msvc-visualizers-fig4

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

children
(
    #(
        unicode: [$e.unicode_],
        #if ($e.unicode_)
        (
            #array (
                expr: [$e.uData_[$i],su],
                size: $e.length_
            )
        )
        #else
        (
            #array (
                expr: $e.bData_[$i],
                size: $e.length_
            )
        )
    )
)

Надеюсь, тут уже всё понятно без подробных пояснений. #array в цикле size раз вычисляет значение в expr и выводит каждое значение, как новый элемент в списке children. В результате, в отладчике видим следующее:

msvc-visualizers-fig5

Полный код для нашего визуализатора:

MyString{
    preview
    (
        #if(($e.unicode_) == true)
        (
            #([$e.uData_,su], " [", $e.length_, "]")
        )
        #else
        (
            #([$e.bData_,s], " [", $e.length_, "]")
        )
    )
    children
    (
        #(
            unicode: [$e.unicode_],
            #if ($e.unicode_)
            (
                #array (
                    expr: [$e.uData_[$i],su],
                    size: $e.length_
                )
            )
            #else
            (
                #array (
                    expr: $e.bData_[$i],
                    size: $e.length_
                )
            )
        )
    )
}

Ссылки

Рекомендую также на заданную тему эти статьи (на английском):

Опубликовать в Google Plus
Опубликовать в LiveJournal

Добавить комментарий

Ваш e-mail не будет опубликован.

Можно использовать следующие HTML-теги и атрибуты: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre lang="" line="" escaped="" highlight="">