
Роббинс Д. - Отладка приложений для Microsoft .NET и Microsoft Windows - 2004
.pdf
ГЛАВА 7 Усложненные технологии неуправляемого кода в Visual Studio .NET |
251 |
|
|
потоке. (В главе 8 вы увидите, что в WinDBG такая возможность встроена). При работе с большими серверными приложениями, такими как ISAPI фильтры, час то встречаются методы, вызываемые из многих потоков, но вы не хотите снаши вать указательный палец, нажимая GO миллион раз потому, что отладчик останав ливается на каждом вхождении в каждом потоке. Первый шаг к решению этой проблемы — остановиться в отладчике и вызвать окно Threads, чтобы увидеть все идентификаторы потоков. Определите, какой поток вы хотите останавливать, и запомните его идентификатор. Второй шаг — установить точку прерывания по месту в общей подпрограмме и вызвать диалоговое окно New Breakpoint, щелк нув точку прерывания правой кнопкой и выбрав Properties. Щелкните кнопку Condition и введите выражение *(long*)($TIB+0x24) == <thread id>, где thread id —
идентификатор потока. Идентификатор потока располагается в блоке информа ции потока по смещению 0x24. (Это можно узнать, разобрав GetCurrentThreadId, что мы сделаем ниже.)
И последнее: поскольку в выражениях нельзя вызывать функции, остановить ся по строке с заданным значением непросто. В таком случае просто установите выражение, проверяющее каждый символ, вроде этого:
(szBuff[0]=='P')&&(szBuff[1]=='a')&&(szBuff[2]=='m')
Стандартный вопрос отладки
Можно ли присвоить имя потоку в неуправляемом коде?
Если вы прочли главу 6, то знаете, что можно легко присвоить потоку имя, отображаемое в окне Threads. Microsoft задокументировала способ, которым можно сделать то же самое в неуправляемых приложениях. По умолчанию неуправляемые приложения показывают имя функции, в которой был от крыт поток. Чтобы отображать реальное имя, можно использовать специ альное значение исключения для передачи нового имени, а класс записи отладчика (debugger writer) должен считывать память по адресу, передан ному как часть исключения. Я заключил необходимый код в оболочку из функций в BUGSLAYERUTIL.DLL: BSUSetThreadName и BSUSetCurrentThreadName. Код для BSUSetThreadNameA показан ниже. После вызова этой функции окно Threads будет отображать то, что указано в качестве имени. В отличие от управля емого кода в неуправляемом можно менять имя потока как хочется. И еще: в отладчике показывается только первый 31 символ имени.
typedef struct tagTHREADNAME_INFO
{ |
|
|
|
|
|
DWORD |
dwType |
; // Должен быть 0x1000. |
|
||
LPCSTR |
szName |
; // Указатель на имя (в адресном пр ве |
пользователя). |
||
DWORD |
dwThreadID |
; |
// |
Идентификатор потока ( 1=вызывающий поток). |
|
DWORD |
dwFlags |
; |
// |
Зарезервировано на будущее, должно |
равняться 0. |
} THREADNAME_INFO ;
void BUGSUTIL_DLLINTERFACE __stdcall
BSUSetThreadNameA ( DWORD dwThreadID ,
LPCSTR szThreadName )
см. след. стр.
252 |
ЧАСТЬ II Производительная отладка |
|
|
|
|
|
|
|
|
|
|
{ |
|
|
|
|
THREADNAME_INFO stInfo ; |
|
|
|
stInfo.dwType |
= 0x1000 ; |
|
|
stInfo.szName |
= szThreadName ; |
|
|
stInfo.dwThreadID |
= dwThreadID ; |
|
|
stInfo.dwFlags |
= 0 ; |
|
|
__try |
|
|
|
{ |
|
|
|
RaiseException ( 0x406D1388 |
, |
|
|
|
0 |
, |
|
|
sizeof ( THREADNAME_INFO ) / |
|
|
|
sizeof ( DWORD ) |
, |
|
|
(DWORD*)&stInfo |
) ; |
|
} |
|
|
|
__except ( EXCEPTION_CONTINUE_EXECUTION ) |
|
|
|
{ |
|
|
|
} |
|
|
} |
|
|
|
|
|
|
|
Точки прерывания по данным
Точки прерывания по данным, также называемые глобальными точками преры вания для памяти (global memory breakpoints), — один из самых мощных инстру ментов. Благодаря им, когда что либо меняет определенный участок памяти, от ладчик сразу останавливается в той точке, перед которой память изменилась. Область действия точек прерывания по данным глобальна, и они не привязаны к конкретному местоположению за исключением того, где производится измене ние памяти. Как вы понимаете, точки прерывания по данным — это именно то, что нужно для отслеживания всех видов проблем нарушения целостности и пе резаписи памяти.
В Visual C++ 6 точки прерывания по данным устанавливались весьма затейли во, но теперь они очень просты в использовании. Главная проблема была в том, что, если в Visual C++ 6 точка прерывания устанавливалась неправильно, отлад чик просто пошагово выполнял каждую машинную команду в приложении и про верял память после каждого выполнения. Не нужно говорить, что наличие исклю чений и нескольких переходов между процессами в каждом исключении мучи тельно замедляло работу. Если вы попадались в эту ловушку, то все, что можно было сделать, — завершить работу отладчика. К счастью, теперь в Visual Studio .NET при неправильной установке точек прерывания по данным вы получаете предуп реждающее сообщение.
Прелесть точек прерывания по данным в том, что вся связанная с ними тяже лая работа выполняется не отладчиком, а процессором. Процессоры Intel содер жат четыре специальных отладочных регистра (DR0–DR3), к которым может об ращаться процессор для установки аппаратных точек прерывания на доступ к памяти. Эти отладочные регистры ограничиваются обзором адреса и 1, 2 или 4

ГЛАВА 7 Усложненные технологии неуправляемого кода в Visual Studio .NET |
253 |
|
|
байт по этому адресу. Это значит, что в своей программе в каждый момент време ни вы можете контролировать не более 16 байт.
Хитрость установки точек прерывания по данным заключается в использова нии адреса памяти, за которым следует наблюдать. Диалоговое окно New Breakpoint предполагает использование имени переменной, но часто это приводит к появ лению информационного окна (рис. 7 1). Увидев его, всегда щелкайте No, по тому что, если вы щелкните Yes, отладчик установит точку прерывания для об наружения изменения памяти путем пошагового выполнения каждой команды ассемблера.
Рис. 7 1. Точка прерывания по данным приведет к пошаговому выполнению
Когда вы получили адрес памяти, за которым надо наблюдать, установить точ ку прерывания довольно просто. Вызовите диалоговое окно New Breakpoint и перейдите на вкладку Data (рис. 7 2). В поле ввода Variable введите наблюдаемый адрес. При вводе адреса важно помнить о выравнивании данных. Поле Items ука зывает, сколько байт наблюдать по этому адресу. Если вы хотите отслеживать 4 байта (двойное слово), введенный адрес должен оканчиваться на 0, 4, 8 или C для соответствия выравниванию. Так же, если вы хотите отслеживать только 2 байта (слово), адрес должен заканчиваться на 0, 2, 4, 6, 8, A, C или E. Если вы попытае тесь установить точку прерывания по данным, не соблюдая выравнивания, ска жем, введя адрес памяти, заканчивающийся на 7, и установив в поле Items значе ние 4, точка прерывания будет казаться установленной, но при ее появлении от ладчик не остановится. Интересно, что при попытке установить 4 байтовую точ ку прерывания по данным по адресу, заканчивающемуся, например, на A, отлад чик выровняет ее, сдвинув вперед на 2 байта.
Как видно на рис. 7 2, при установке точки прерывания по данным доступны еще два дополнительных поля. Поле Context используется, когда вы указываете имя переменной в поле Variable, если область видимости переменной отличается от текущего местоположения. Поскольку гораздо лучше использовать адреса, поле Context можно игнорировать. То же относится и к полю Language, поскольку при использовании адресов язык также игнорируется.
Замечательное усовершенствование точек прерывания по данным в Visual Studio
.NET по сравнению с Visual C++ 6 в том, что теперь им можно сопоставлять число выполнений и условия. Это позволяет подстраивать нужное состояние для оста новки в отладчике.
Введя точку прерывания по данным и проверив окно Breakpoints, чтобы убе диться, что точка прерывания полностью допустима, можете запустить приложе ние. Когда данные по указанному адресу изменяются, в отладчике происходит нечто интересное (рис. 7 3). Появляется информационное окно, указывающее на попа дание точки прерывания по данным. Меня часто спрашивали, почему для точек

254 ЧАСТЬ II Производительная отладка
прерывания по данным созданы такие специальные условия. Причина связана с весьма сложной проблемой пользовательского интерфейса. Поскольку точки пре рывания по данным могут инициироваться откуда угодно, остановка отладчика в месте, где на полях нет красной точки, обозначающей точку прерывания, сбива ла бы с толку. Увидев информационное окно, вы будете знать, почему отладчик остановился.
Рис. 7 2. Установка точки прерывания по данным
Рис. 7 3. Попадание точки прерывания по данным
При использовании точек прерывания по данным вам по завершении сеанса отладки, наверное, захочется их очистить. Раз я рекомендовал использовать для них адреса, весьма вероятно, что область памяти, за которой следует наблюдать, будет перемещаться от запуска к запуску. Это особенно актуально при наблюде нии за памятью стека.
Улучшенные точки прерывания по данным
Это прекрасное усовершенствование отладки неуправляемого кода в Visual Studio
.NET. Однако, если вы заглядывали в Intel Architecture Software Developer’s Manuals (Руководство разработчика ПО для Intel архитектуры), согласно документации по отладочным регистрам они могут быть установлены так, что каждый доступ к памяти для чтения или чтения/записи по указанному адресу может инициировать аппаратную точку прерывания. Точки прерывания по данным в Visual Studio .NET
ГЛАВА 7 Усложненные технологии неуправляемого кода в Visual Studio .NET |
255 |
|
|
инициируются только когда по указанному адресу изменяются данные. Так что, если записанное по адресу значение не меняет данных по этому адресу, останов ки не будет.
Иногда все равно надо знать, кто записывает или читает адрес памяти. Сколь ко раз я отслеживал проблемы с производительностью, подсчитывая число обра щений к памяти! Visual Studio .NET скрывает часть возможностей аппаратных отладочных регистров, но я решил, что должен быть способ заставить отладчик работать в полную мощность.
Обдумывая решение, я получил по электронной почте сообщение от Майка Мориарти (Mike Morearty) о том, что он хотел того же и нашел решение. Впо следствии Майк разработал код, делающий именно то, что мне было нужно, так что я мог даже не думать больше об этой проблеме. Все, что вам надо сделать, — это зайти на http://www.morearty.com/code/breakpoint и прочитать об этом. Ре шение Майка, которое на самом деле является единственно возможным, заклю чается в добавлении к проекту небольшого класса C++, применяемого для уста новки улучшенных точек прерывания по данным в собственном коде. Web страница Майка прекрасно описывает использование его класса CBreakpoint, поэтому не буду повторяться. Отмечу, что, раз для создания точек прерывания требуется вручную добавить код в проект, будьте очень осторожны, возвращая код. Если вы оставите класс CBreakpoint активным, ваши компоновки за день не запустятся, и вы сразу поймете значение слов «удар по карьере»!
Окно Watch
Как видно из последних двух глав, я очень люблю окно Watch. Для отладки неуправ ляемого кода оно предлагает больше возможностей, чем когда либо. Одно из улуч шений, которое вы уже могли заметить в отладке неуправляемого кода в Visual Studio
.NET, в том, что теперь окно Watch автоматически распознает типы HRESULTS, wchar_t (символы UNICODE) и bool. Вы также могли заметить, что подсказки для дан ных, появляющиеся в окнах исходного кода, кажется, получили ударную дозу стероидов.
Форматирование данных и вычисление выражений
Первый трюк, который вам следует освоить на пути к мастерству владения окном Watch, — запомнить символы форматирования из табл. 7 2 и 7 3, взятых из доку ментации Visual Studio .NET. Окно Watch обладает потрясающей гибкостью ото бражения данных, и путь к этой гибкости — в применении форматирующих кодов из этих таблиц. Применять форматирование просто: поставьте за переменной запятую и укажите формат. Самый полезный спецификатор формата для COM программирования — это ,hr. Если вы будете держать в окне Watch выра жение @EAX,hr, то, проходя через вызов COM метода, сможете видеть результаты вызова в понятной форме. (EAX — это регистр процессоров Intel, в котором со храняются возвращаемые значения.) Спецификаторы формата позволяют легко управлять представлением данных, так что вы сэкономите массу времени при их интерпретации.

256 |
ЧАСТЬ II Производительная отладка |
|
|
|
|||
Табл. 7-2. Символы форматирования переменных в окне Watch |
|||
|
|
|
|
Символ |
Описание формата |
Пример |
Отображение |
d, i |
Десятичное целое со знаком |
(int)0xF000F065,d |
–268373915 |
u |
Десятичное целое без знака |
0x0065,u |
101 |
o |
Восьмеричное целое без знака |
0xF065,o |
0170145 |
x, X |
Шестнадцатеричное целое |
61541,X |
0x0000F065 |
l, h |
Длинный или короткий префикс |
0x00406042,hx |
0x0c22 |
|
для d, i, u, o, x, X |
|
|
f |
Число с плавающей точкой со знаком |
3./2.,f |
1.500000 |
e |
Число со знаком в экспоненциаль |
3./2,e |
1.500000e+000 |
|
ном представлении |
|
|
g |
Число со знаком с плавающей |
3./2,g |
1.5 |
|
точкой или со знаком в экспонен |
|
|
|
циальном представлении, что короче |
|
|
c |
Одиночный символ |
0x0065,c |
‘e’ |
s |
Строка ANSI |
szHiWorld,s |
«Hello world» |
su |
Строка Unicode |
szWHiWorld,su |
«Hello world» |
hr |
HRESULT или код ошибки Win32 |
0x00000000,hr |
S_OK |
wc |
Флаг класса Windows |
0x00000040,wc |
WC_DEFAULTCHA R |
|
|
|
(Хотя он доку |
|
|
|
ментирован, этот |
|
|
|
формат не рабо |
|
|
|
тает в Visual |
|
|
|
Studio .NET.) |
wm |
Числа сообщений Windows |
0x0010,wm |
WM_CLOSE |
|
|
|
|
Табл. 7-3. Символы форматирования дампов памяти в окне Watch
Символ |
Описание формата |
Пример |
Отображение |
ma |
64 ASCII символа |
0x0012ffac,ma |
0x0012ffac |
|
|
|
.4...0...».0W&.......1W&. |
|
|
|
0.:W..1....»..1.JO&.1.2..» ..1...0y....1 |
m |
16 байт в шестнадцатеричном |
0x0012ffac,m |
0x0012ffac b3 34 cb 00 84 30 |
|
формате, продолжающиеся |
|
94 80 ff 22 8a 30 57 26 00 00 |
|
16 ASCII символами |
|
.4...0...».0W&.. |
mb |
16 байт в шестнадцатеричном |
0x0012ffac,mb |
0x0012ffac b3 34 cb 00 84 30 |
|
формате, продолжающиеся |
|
94 80 ff 22 8a 30 57 26 00 00 |
|
16 ASCII символами |
|
.4...0...».0W&.. |
mw |
8 слов |
0x0012ffac,mw |
0x0012ffac 34b3 00cb 3084 |
|
|
|
8094 22ff 308a 2657 0000 |
md |
4 двойных слова |
0x0012ffac,md |
0x0012ffac 00cb34b3 |
|
|
|
80943084 308a22ff 00002657 |
mq |
4 учетверенных слова |
0x0012ffac,mq |
0x0012ffac |
|
|
|
8094308400cb34b3 |
|
|
|
00002657308a22ff |
mu |
2 байтовые символы (Unicode) |
0x0012ffac,mu |
0x0012ffac 34b3 00cb 3084 |
|
|
|
8094 22ff 308a 2657 0000 |
|
|
|
?.?????. |
ГЛАВА 7 Усложненные технологии неуправляемого кода в Visual Studio .NET |
257 |
|||
|
|
|||
Табл. 7-3. Символы форматирования дампов памяти … (продолжение) |
|
|||
|
|
|
|
|
Символ |
Описание формата |
Пример |
Отображение |
|
# |
Разворачивает указатель |
pCharArray,10 |
Развернутый массив из 10 |
|
|
на адрес памяти в указанное |
|
символов с использованием |
|
|
число значений |
|
расширителей +/ |
|
|
|
|
|
|
Числовой спецификатор формата — ,# — позволяет развернуть указатель на адрес памяти в указанное количество значений. Если у вас есть указатель на мас сив из 10 чисел типа long, в окне Watch будет показано только первое значение. Чтобы увидеть массив целиком, укажите за переменной число значений, которые вы хотите увидеть. Например, pLong,10 покажет развернутый массив из 10 элементов. Если у вас большой массив, можно составить указатель на его середину и развер нуть только нужные элементы, например, (pBigArray+100),20, для отображения 20 элементов, начиная со смещения 99. Вы заметите, что при вводе подобного зна чения индексы всегда начинаются с 0 независимо от позиции первого отобража емого элемента. В примере pBigArray первый индекс будет отображаться как 0, хоть это и сотый элемент массива. Второй индекс — 101 й элемент массива — будет показан как 1 и т. д.
В дополнение к тому, что в окне Watch можно как угодно форматировать дан ные, оно также позволяет приводить и склонять данные переменных, чтобы уви деть именно то, что нужно. Так, можно использовать выражения BY, WO и DW для задания смещений в указателях. Чтобы увидеть идентификатор текущего потока в окне Watch, можно использовать DW($TIB+0x24). Также допускаются оператор вычисления адреса (&) и оператор указателя (*), и оба позволяют получить значе ния по адресам памяти и увидеть результаты приведения типов в коде.
Отличный фокус, который я люблю использовать в отладке неуправляемого кода, — наблюдение за значениями переменных в стеке. Иногда встречаются ло кальные переменные, за которыми хотелось бы следить при прохождении через другие функции. С помощью контекстной части усложненного синтаксиса точек прерывания, о которой я рассказывал в разделе «Усложненный синтаксис точек прерывания», можно наблюдать за значением. Так, если в функции CopyDatabaseValue, расположенной в исходном файле FOO.CPP из модуля DB.DLL объявлена перемен ная szBuff, ее точное значение указывается как {CopyDatabaseValue,FOO.CPP,DB.DLL}szBuff. Теперь, где бы вы ни находились в функциях, вызванных из CopyDatabaseValue, вы легко сможете следить за szBuff.
Хронометраж кода в окне Watch
Это еще один изящный трюк. Псевдорегистр $CLK может служить простым тайме ром. Часто требуется лишь примерное представление о времени между двумя точ ками, например, сколько времени занимает обращение к базе данных. $CLK позволяет легко выяснить длительность вызова. Помните, что это время включает издержки отладчика. Фокус во введении двух контрольных выражений для $CLK: первое — просто $CLK, а второе — $CLK=0 — обнуляет таймер после запуска. Хоть $CLK и не идеальный таймер, его достаточно для приблизительных оценок.

258 ЧАСТЬ II Производительная отладка
Недокументированные псевдорегистры
В Visual Studio .NET появились два новых псевдорегистра. Поскольку в заголовке этого раздела есть слово «недокументированные», я должен предупредить вас, что в будущих версиях отладчика эти значения могут исчезнуть. Первый псевдорегистр
— это $HANDLES. Он показывает число открытых описателей в текущем процессе. Умопомрачительная идея, позволяющая при отладке следить за утечками описа телей. Если сообщаемое $HANDLES число постоянно увеличивается, у вас утечка. $HANDLES,d занимает постоянную позицию в моем окне Watch из за своей чрезвы чайной полезности.
Второй недокументированный псевдоописатель — $VFRAME — прекрасный ин струмент для контроля стека в финальных сборках. $VFRAME — короткая дорога к виртуальному указателю фреймов (virtual frame pointer). На машинах IA32 $VFRAME указывает на следующий фрейм стека, так что он может пригодиться при просмотре стека вручную. Если вы используете стандартные фреймы стека, то $VFRAME указы вает на значение EBP предыдущего элемента.
Автоматическое разворачивание собственных типов
Хотя управляемый C++ и отладка C# позволяют разворачивать ваши собственные типы в окне Watch, авторазвертывание (autoexpansion), предлагаемое в отладке неуправляемого кода, поднимает эту возможность на новую высоту. Вообще, на чиная с Visual Studio .NET 2003, окно Watch и подсказки по данным теперь авто матически пытаются показать несколько первых компонентов структур и классов. Но вы, наверное, видели, как несколько стандартных типов, таких как CObject, RECT и некоторые типы STL, разворачиваются в окне Watch гораздо подробнее, что достигается правилами авторазвертывания (autoexpand rules). Чудеса творятся в текстовом файле AUTOEXP.DAT, расположенном в подкаталоге <установочный ка талог Visual Studio .NET>\COMMON7\PACKAGES\DEBUGGER. Вы вправе добавлять в список авторазвертывания собственные типы, вводя их в файл AUTOEXP.DAT. (К сожалению, AUTOEXP.DAT должен оставаться в этом каталоге, так что вам при дется настроить рабочий каталог вашего ПО контроля версий для внесения AUTO EXPAND.DAT в этот каталог.)
В качестве примера я добавлю запись аторазвертывания для структуры PRO CESS_INFORMATION, которая передается функции API CreateProcess. Сначала надо про верить, что отладчик Visual Studio .NET распознает как тип. В созданной для при мера программе я поместил переменную PROCESS_INFORMATION в окно Watch и по смотрел колонку Type в правой части окна и увидел тип _PROCESS_INFORMATION. Если взглянуть на это определение структуры, он соответствует тэгу структуры:
typedef struct _PROCESS_INFORMATION {
HANDLE hProcess;
HANDLE hThread;
DWORD dwProcessId;
DWORD dwThreadId;
} PROCESS_INFORMATION
Согласно документации на AUTOEXP.DAT запись авторазвертывания имеет формат type=[text]<member[,format]>.... В табл. 7 4 дано описание полей. Заметь те: при авторазвертывании может отображаться несколько компонентов.

ГЛАВА 7 |
Усложненные технологии неуправляемого кода в Visual Studio .NET |
259 |
|
|
|
Табл. 7-4. Записи авторазвертывания AUTOEXP.DAT |
|
|
|
|
|
Поле |
Описание |
|
Type |
Имя типа. Для типов шаблонов это поле может завершаться символом <*>, |
|
|
объединяя все производные типы. |
|
Text |
Любой буквенный текст. Как правило, в этом поле указывается имя компо |
|
|
нента данных или его сокращенная версия. |
|
member |
Отображаемый компонент данных. В поле можно указывать выражения, |
|
|
так что, если вам надо добавить смещение к указателям, вы вправе вклю |
|
|
чить их в вычисления. Также допускаются операторы приведения типов. |
|
format |
Дополнительные спецификаторы формата для переменных. Это те же |
|
|
спецификаторы, что показаны в табл. 7 2. |
|
|
|
|
В структуре PROCESS_INFORMATION меня интересуют значения hProcess и hThread, так что мое правило авторазвертывания будет таким: _PROCESS_INFORMATION =hPro cess=<hProcess,X> hThread=<hThread,X>. Я применяю спецификаторы формата ,X, потому что всегда хочу видеть значения в шестнадцатеричном формате. На рис. 7 4 по казано правило авторазвертывания для _PROCESS_INFORMATION в подсказке для дан ных в окне исходного кода.
Рис. 7 4. Авторазвертывание в подсказке для данных
Вводя свое новое правило аторазвертывания, я должен поместить его в разде ле файла AUTOEXP.DAT, обозначенном [AutoExpand]. Лучшее решение — размещать свои значения сразу после [AutoExpand], чтобы их можно было легко найти и не смешивать с приемами, которые я опишу в следующем разделе. Хорошая новость: в отличие от авторазвертывания в отладке управляемого кода, считываемого только при запуске Visual Studio .NET, файл AUTOEXP.DAT читается при каждой отладке, так что создавать правила авторазвертывания для неуправляемого кода намного проще.
В файле вы встретите специальный код форматирования <,t>. Он предписы вает отладчику внести имя производного типа. Например, если у вас есть базо вый класс A с производным классом B и правило авторазвертывания есть только у A, то авторазвертывание переменной типа B будет представлять собой имя клас са B, за которым следует правило авторазвертывания для класса A. Формат <,t> хорошо помогает поддерживать порядок в классах.
Добавление собственных значений HRESULT
Вдополнение к раскрытию ваших типов окно Watch в Visual Studio .NET теперь способно показывать ваши собственные значения HRESULT в виде текста вместо каких то запутанных цифр. Волшебный AUTOEXP.DAT также хранит эти значения.
Вконце файла AUTOEXP.DAT добавьте новый раздел [hresult] и внесите туда каж дый собственный HRESULT, используя шаблон: «<десятичное значение без знака>= <текст HRESULT >». Следующий код — пример, включающий некоторые значения, не обрабатываемые отладчиком автоматически. Чтобы увидеть реальные значе ния HRESULT для одного из встроенных преобразований или добавленных вами в

260 ЧАСТЬ II Производительная отладка
раздел [hresult], возьмите переменную HRESULT и добавьте к ней ,u или ,x. Это приведет к отображению переменной в виде целого числа без знака или шест надцатеричного значения, соответственно.
[hresult] 2147500051=CO_E_CANT_REMOTE
2147500056=CO_E_CREATEPROCESS_FAILURE 2147500059=CO_E_LAUNCH_PERMSSION_DENIED
Супернастройка отображения в окне Watch
Серьезным усовершенствованием окна Watch в отладке неуправляемого кода стала надстройка Expression Evaluator Add In (EEAddIn). EEAddIn позволяет отладчику вызывать одну из ваших DLL когда окно Watch оценивает определенный тип. Это открывает прекрасную возможность производить вычисления, отображающие данные в более подходящем виде. Так, окно Watch отображает структуру SYSTEMTIME (представляющую дату и время в Win32) группой шестнадцатеричных чисел, ли шая вас возможности определить время. При использовании EEAddIn окно Watch вместо этого отображает читаемую строку, вроде {5/13/2002 12:51 AM}.
Чтобы сообщить окну Watch, что у вас есть DLL для EEAddIn, которую вы хо тите загрузить, надо поместить запись для каждого оцениваемого типа в вездесу щий файл AUTOEXP.DAT. В разделе [AutoExpand] расширение для типа указывается в следующем синтаксисе:
имя типа=$ADDIN(имя dll,экспортируемая функция)
Имя типа, как и в правилах авторазвертывания, — это имя, отображаемое для переменной в окне Watch в столбце Type. Имя DLL — имя файла библиотеки DLL. Согласно документации к EEAddIn, которая представляет собой просто пример проекта Visual Studio .NET, названный соответственно EEAddIn, имя DLL должно содержать только имя файла DLL, так как предполагается, что все надстройки EEAddIn хранятся в том же каталоге, что и AUTOEXP.DAT. Однако я обнаружил, что для правильной загрузки надо в имени DLL указывать полный путь к DLL. Экспор тируемая функция указывает функцию, которую надо вызывать для обработки пользовательского отображения данного типа.
Так как надстройки запускаются в адресном пространстве отладчика, следует обеспечить корректную обработку возможных исключений, иначе это приведет к краху отладчика. Индивидуальные экспортируемые функции должны соответ ствовать прототипу CUSTOMVIEWER (листинг 7 1). При вызове функции в качестве параметров она получит адрес типа; указатель на вспомогательную структуру DEBUGHELPER; текущую систему счисления (десятичная или шестнадцатеричная); значение типа Boolean, указывающее, ожидает ли отладчик строки UNICODE (ко торое в Visual Studio .NET игнорируется, так как здесь всегда ожидается возврат ANSI символов); строковый буфер (string buffer) для записи результата и макси мальную длину строкового буфера. Вспомогательная структура (листинг 7 1) со держит несколько указателей на функции, вызвав которые можно получить све дения о значениях, расположенных по адресу типа. Самые важные — GetRealAddress и ReadDebuggeeMemoryEx. Передав адрес, полученный вашей экспортируемой функ