Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Роббинс Д. - Отладка приложений для Microsoft .NET и Microsoft Windows - 2004

.pdf
Скачиваний:
322
Добавлен:
13.08.2013
Размер:
3.3 Mб
Скачать

ГЛАВА 3 Отладка при кодировании

111

 

 

из второго ряда копирует в буфер обмена весь текст из поля ввода Failure, а также информацию из всех потоков для которых есть данные из стека. Последняя кноп ка — More>> или Less<< — разворачивает и сворачивает диалоговое окно.

Кнопки Create Mini Dump и Email Assertion требуют некоторого пояснения. Если загруженная в пространство процесса версия DBGHELP.DLL содержит экспорти руемые функции Minidump, то кнопка Create Mini Dump доступна. Если функции Minidump недоступны, кнопка отключена. Чтобы лучше сохранить состояние приложения, SUPERASSERT приостанавливает все прочие потоки приложения. Это значит, что SUPERASSERT не может использовать стандартный диалог для работы с файлами, так как этот диалог инициирует некоторые фоновые потоки, остающи еся после его закрытия. Когда SUPERASSERT приостанавливает все потоки, код стан дартного файлового диалога зависает, поскольку его работа зависит от приоста новленного потока. Следовательно, я не могу задействовать стандартный диалог. Поэтому диалог, появляющийся после щелчка Create Mini Dump, — это простое диалоговое окно с полем ввода, предлагающее вам ввести полный путь и имя для минидампа.

Кнопка Email Assertion активна, только если вы внесли в свой исходный файл специальное определение, указывающее адрес электронной почты, на который следует отправлять информацию утверждения. Эта потрясающая функция служит тестерам для отправки соответствующего утверждения нужному разработчику. Письмо содержит всю информацию, включая все данные о стеке, поэтому разра ботчики смогут точно определить причины инициации утверждения. Чтобы ав томатически получить возможность отправки сообщения по электронной почте, в начале каждого исходного файла включите показанный ниже код. Для опреде ления SUPERASSERT_EMAIL не обязательно использовать SUPERASSERT, но я вам это на стоятельно советую.

#ifdef SUPERASSERT_EMAIL #undef SUPERASSERT_EMAIL

// Пожалуйста, укажите собственный адрес электронной почты! #define SUPERASSERT_EMAIL "john@wintellect.com"

#endif

Развернутое диалоговое окно SUPERASSERT (рис. 3 4) включает все виды полез ных компонентов. Группа Ignore позволяет управлять пропуском утверждений. Первая кнопка — Ignore Assertion Always — отмечает данное утверждение как про пускаемое всегда. Вы также можете указать число пропусков, набрав его в поле ввода. Чтобы установить число пропусков нужному утверждению, щелкните This Assertion Only. Для пропуска всех последующих утверждений, где бы они ни воз никли, щелкните All Assertions. Сначала я не думал, что утверждения вообще сле дует пропускать, но, получив возможность пропускать утверждение, иницииро вавшееся на каждой итерации цикла, я не представляю, как жил без этого.

Последняя часть диалогового окна посвящена стеку вызова. Стек вызова имелся и в первой версии SUPERASSERT, но, приглядевшись к стеку в поле ввода, вы увидите все локальные переменные и их текущие значения для каждой функции! Библио тека SymbolEngine из BugslayerUtil.DLL способна расшифровывать все основные типы, структуры, классы и массивы. Она также достаточно умна для расшифров ки важных значений, таких как символьные указатели и символьные массивы. Как

112 ЧАСТЬ I Сущность отладки

видно на рис. 3 4, отображаются как строки ANSI, так и Unicode, а также расшиф рованные структуры RECT. Правильная расшифровка символов стала одним из сложнейших фрагментов кода, которые я писал! Если вам интересны грязные подробности, то в главе 4 вы сможете больше узнать о библиотеке SymbolEngine. Хорошая новость в том, что я уже сделал все самое трудное, так что, если не хо тите, можете даже не думать об этом!

Первая кнопка группы Stack — Walk Stack — позволяет просматривать стек. На рис. 3 4 Walk Stack неактивна, так как стек уже просмотрен. Раскрывающийся список Thread ID позволяет выбрать поток, который вы хотите просмотреть. Если при ложение содержит единственный поток, список Thread ID в диалоговом окне не показывается. Раскрывающийся список Locals Depth позволяет выбрать, насколь ко глубоко будут раскрываться локальные переменные. Этот список сродни стрел кам с плюсами в окне Watch отладчика рядом с раскрывающимися элементами. Чем больше число, тем больше информации о соответствующих локальных пе ременных будет отображаться. Так, если у вас есть локальная переменная типа int**, то, чтобы увидеть целочисленное значение, на которое она указывает, следует установить глубину раскрытия равной 3. Флажок Expand Arrays предписывает SUPERASSERT раскрывать все встречающиеся типы массивов. Вычисление указате лей или типов с глубокой вложенностью, а также крупных массивов — операция ресурсоемкая, поэтому вам вряд ли захочется раскрывать массивы без необходи мости. Конечно, SUPERASSERT поступает правильно, на лету переоценивая все ло кальные переменные при изменении глубины раскрытия или по запросу, так что вы можете получить нужную информацию, когда она нужна.

SUPERASSERT включает несколько глобальных параметров, которые также мож но менять по ходу работы. Диалоговое окно Global SUPERASSERT Options (рис. 3 5) открывается при выборе Options из системного меню.

Рис. 3 5. Диалоговое окно Global SUPERASSERT Options

Группа Stack Walking определяет объем информации о стеке, получаемой при появлении диалогового окна SUPERASSERT. По умолчанию просматривается только поток, в котором содержится утверждение, но, если нужна максимальная скорость появления окна, вы можете установить просмотр стека только вручную. Группа Additional Mini Dump Information определяет объем информации, записываемой во все минидампы. (Подробнее см. документацию к перечислению MINIDUMP_TYPE.)

ГЛАВА 3 Отладка при кодировании

113

 

 

Установка Play Sounds On Assertions приводит к проигрыванию стандартного зву ка сообщения при появлении диалогового окна SUPERASSERT. Если установлен Force Assertion To Top, диалоговое окно SUPERASSERT появляется поверх остальных окон. Если ведется отладка процесса, появление поверх других окон не устанавливает ся, так как SUPERASSERT может заблокировать отладчик. Все глобальные параметры SUPERASSERT хранятся в разделе реестра HKCU\Software\Bugslayer\SUPERASSERT. Кроме того, в реестре сохраняются координаты последнего отображения диалогового окна и его состояние (свернутое/развернутое), так что SUPERASSERT каждый раз появля ется там, где вы ожидаете его увидеть.

Хочу отметить еще некоторые детали SUPERASSERT. Во первых, как видно на рис. 3 3 и 3 4, SUPERASSERT содержит область захвата в нижнем правом углу для изменения размеров диалогового окна. Кроме того, SUPERASSERT поддерживает ра боту с несколькими мониторами, поэтому в системном меню есть команда, кото рая центрирует диалоговое окно на текущем мониторе, чтобы вы могли вернуть SUPERASSERT в нужную позицию. Но из рисунков не видно, что SUPERASSERT можно не отображать вовсе. Может показаться, что такой вариант непродуктивен, но уверяю вас, это не так! Если вы последовали моим рекомендациям из главы 2 и начали тестировать свои отладочные сборки с помощью инструмента регресси онного тестирования, то знаете, что обработка случайных сообщений от утверж дений практически невозможна. Из за проблем с обработкой сообщений от ут верждений ваши тестировщики с меньшей вероятностью согласятся применять отладочные сборки. В моем коде утверждений вы вправе указать, что вывод сле дует направить в OutputDebugString, описатель файла, журнал событий или любую комбинацию этих вариантов. Такая гибкость позволяет запускать код и получать все данные от утверждений, не теряя возможности автоматизировать отладочные сборки. Наконец, код SUPERASSERT чрезвычайно умен в отношении выбора условий появления. Он всегда проверяет, зарегистрирован ли на рабочей станции инте рактивный пользователь. Если на этой рабочей станции не зарегистрирован ни один интерактивный пользователь, SUPERASSERT не станет появляться и останавливать ваше приложение.

Благодаря информации, предоставляемой SUPERASSERT, я пользуюсь отладчиком реже, чем когда либо, что является огромной победой в борьбе за скорость от ладки. При срабатывании утверждения я размещаю диалоговое окно SUPERASSERT на втором мониторе. Я просматриваю информацию о локальных переменных и начинаю читать исходный код на главном мониторе. Я обнаружил, что способен исправить примерно на 20% больше ошибок, не запуская отладчик. Первая редакция была весьма полезна, вторая — потрясает!

Использование SUPERASSERT

Интегрировать SUPERASSERT в приложения довольно легко. Для этого надо просто включить BUGSLAYERUTIL.H, который, вероятно, лучше всего включается в пре компилируемый заголовок, и подключиться к BUGSLAYERUTIL.LIB, чтобы внести BUGSLAYERUTIL.DLL в адресное пространство. Это предоставляет вам макрос ASSERT и автоматически перенаправляет все существующие вызовы ASSERT и assert моим функциям. Мой код не перехватывает макросы _ASSERT и _ASSERTE, так как, исполь зуя отладочную библиотеку исполняющей среды, вы можете выполнять дополни

114 ЧАСТЬ I Сущность отладки

тельные действия или особый вывод, и я не хочу нарушать существующие реше ния. Мой код также не затрагивает ASSERT_KINDOF и ASSERT_VALID.

Перенаправить вывод, скажем, в журнал событий или текстовый файл, позво ляет макрос SETDIAGASSERTOPTIONS, который принимает несколько очевидных мак росов битовых полей, определяющих место вывода. Все эти макросы битовых полей определены в DIAGASSERT.H.

Два слова об игнорировании утверждений

Всегда плохо, когда другой разработчик или тестер тащат вас к своей машине, чтобы обвинить ваш код в аварии. Еще хуже, когда вы начинаете поиск проблемы с во проса, не нажимал ли он кнопку Ignore в появившемся окне утверждения. Он кля нется вам, что не делал этого, но вы знаете, что эта авария не могла произойти без инициации определенного утверждения. Когда вы, наконец, заставляете его сознаться в том, что он все таки нажимал кнопку Ignore, вы готовы оторвать ему голову. Если бы он сообщил об этом утверждении, вы легко устранили бы проблему!

Кнопка Ignore, если вы еще не догадались, может представлять большую опас ность: людей так и тянет ее нажать! Хоть это было бы слегка сурово, я серьезно раздумывал над тем, чтобы не создавать кнопку Ignore в SUPERASSERT и заставить вас разбираться с утверждениями и вызвавшими их причинами. В некоторых ком паниях разработчики добавляют простой способ проверки, не игнорировались ли утверждения в текущем прогоне. Это позволяет им проверить, не нажималась ли кнопка Ignore, прежде чем тратить время на разбор аварии.

Если вы используете SUPERASSERT и хотите увидеть, сколько всего было иници ировано утверждений, проверьте глобальную переменную g_iTotalAssertions из SUPERASSERT.CPP. Конечно, для этого надо подключить к аварийной программе отладчик или иметь дамп памяти аварийной программы. Чтобы получить общее число утверждений программно, вызовите GetSuperAssertionCount, экспортируемый из BUGSLAYERUTIL.DLL.

Возможно, вы захотите подумать о том, чтобы добавить в SUPERASSERT ведение журнала для записи всех инициировавшихся утверждений. Тогда вы будете авто матически получать текущее число нажатий пользователями кнопки Ignore и смо жете утверждать, что к аварии привели действия пользователей. Некоторые ком пании автоматически записывают утверждения в центральную базу данных, что бы отслеживать частоту их появления и обнаруживать неоправданное использо вание кнопки Ignore разработчиками и тестерами.

Поскольку я заговорил о вашей защите от рефлекторного стремления пользо вателей нажать кнопку Ignore, будет справедливо упомянуть о том, что, возмож но, так поступаете и вы сами. Утверждения никогда не должны появляться при нормальной работе — только если что то неверно. Вот прекрасный пример не правильного применения утверждения, с которым я столкнулся, помогая отлажи вать приложение. Когда я выбрал элемент из часто используемого меню, которо му не был сопоставлен объект воздействия (target item), утверждение иницииро валось перед обычной обработкой ошибок. В этом случае обычной обработки ошибок было более чем достаточно. Если вы получаете жалобы о слишком частой инициации утверждений, тщательно проверьте, нужны ли они на своих местах.

ГЛАВА 3 Отладка при кодировании

115

 

 

Главное в реализации SUPERASSERT

Код SUPERASSERT может показаться вам слегка «закрученным», так как состоит из двух отдельных наборов кода: SUPERASSERT.CPP и DIAGASSERT.CPP. На самом деле DIAGASSERT.CPP — это первая версия SUPERASSERT. Я оставил его потому, что в со здании нового SUPERASSERT было довольно много UI кода, а поскольку я не мог применять SUPERASSERT на нем самом, мне потребовался второй набор утвержде ний, чтобы сделать разработку SUPERASSERT чище и устойчивей. Наконец, посколь ку для SUPERASSERT требуется больше вспомогательного кода, в одном случае вам понадобится старый код (там, где нельзя использовать SUPERASSERT) для создания утверждений в RawDllMain, до того как инициализируется что либо в вашем модуле.

Первая интересная часть SUPERASSERT — это макрос, интерпретирующий вызов функции SuperAssertion (листинг 3 5). Так как SUPERASSERT должен отслеживать количество пропущенных локальных утверждений, макрос при каждом исполь зовании создает новую область видимости. В пределах этой области он объявля ет две статических целочисленных переменных для отслеживания числа нарушений данного утверждения и количества раз, когда пользователь захотел проигнори ровать данное утверждение. После проверки результата выражения оставшаяся часть макроса получает текущий указатель стека и фрейма, и вызывает настоящую функцию SuperAssertion.

Листинг 3-5. Главный макрос ASSERT

 

#ifdef _M_IX86

 

 

 

 

 

 

#define NEWASSERT_REALMACRO( exp , type )

 

\

 

 

{

 

 

 

\

 

 

/* Локальный экземпляр количества

пропусков и общего числа

\

 

 

срабатываний */

 

 

 

\

 

 

static int sIgnoreCount

= 0 ;

 

 

\

 

 

static int sFailCount

= 0 ;

 

 

\

 

 

/* Локальный стек и кадр в месте утверждения */

 

\

 

 

DWORD dwStack ;

 

 

 

\

 

 

DWORD dwStackFrame ;

 

 

 

\

 

 

/* Проверяем выражение */

 

 

\

 

 

if ( ! ( exp ) )

 

 

 

\

 

 

{

 

 

 

\

 

 

/* Хьюстон, у нас проблемы */

 

 

\

 

 

_asm { MOV dwStack , ESP }

 

 

\

 

 

_asm { MOV dwStackFrame , EBP

}

 

\

 

 

if ( TRUE == SuperAssertion (

TEXT ( type )

,

\

 

 

 

 

TEXT ( #exp )

,

\

 

 

 

 

TEXT ( __FUNCTION__ ) ,

\

 

 

 

 

TEXT ( __FILE__ )

,

\

 

 

 

 

__LINE__

,

\

 

 

 

 

SUPERASSERT_EMAIL

,

\

 

 

 

 

(DWORD64)dwStack

,

\

 

 

 

 

(DWORD64)dwStackFrame

,

\

 

 

 

 

&sFailCount

,

\

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

см. след. стр.

116

ЧАСТЬ I Сущность отладки

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

&sIgnoreCount

) )

\

 

 

 

{

 

\

 

 

 

__asm INT 3

 

\

 

 

 

}

 

\

 

 

}

 

 

\

 

 

}

 

 

 

 

 

#endif

// _M_IX86

 

 

 

 

 

 

 

 

 

Главная часть обработки утверждений заключается в SUPERASSERT.CPP (листинг 3 6). Основную работу выполняют функции RealSuperAssertion и PopTheFancyAssertion. Первая определяет, игнорируется ли данное утверждение, создает само сообще ние утверждения и выясняет, куда направить вывод. Вторая поинтереснее. Для минимизации влияния на приложение я приостанавливаю все остальные потоки приложения при появлении диалогового окна SUPERASSERT. Так, в числе прочего я могу получить достоверную информацию о стеке. Чтобы приостановить все по токи и прекратить выполнение, я повышаю приоритет потока утверждения до значения THREAD_PRIORITY_TIME_CRITICAL. Оказывается, при остановке всех остальных потоков приложения проявляются некоторые тонкости! Главной про блемой стала невозможность выделения памяти перед приостановкой потоков. Вполне возможно, что другой поток содержится в критической секции кучи ис полняющей среды C (C run time heap critical section), и если я приостановлю его, а затем мне потребуется память, то я не смогу получить доступ к критической секции. Хотя я мог бы пройти насквозь и сосчитать потоки перед повышением приоритета, это дало бы больше времени на выполнение остальных потоков, пока поток, инициировавший утверждение, обрабатывал бы эту массой мелочей. По этому я решил, что лучше всего будет просто создать фиксированный массив на максимальное число потоков, с которым я хочу работать. Если в вашем приложе нии больше 100 потоков, измените значение k_MAXTHREADS в начале SUPERASSERT.CPP.

Листинг 3-6. SUPERASSERT.CPP

/*——————————————————————————————————————————————————————————————————————

Debugging Applications for Microsoft .NET and Microsoft Windows

Copyright (c) 1997 2003 John Robbins — All rights reserved.

——————————————————————————————————————————————————————————————————————*/ #include "PCH.h"

#include "BugslayerUtil.h" #include "SuperAssert.h" #include "AssertDlg.h" #include "CriticalSection.h" #include "resource.h" #include "Internal.h"

/*//////////////////////////////////////////////////////////////////////

//Синонимы типов, константы и определения масштаба файла.

//////////////////////////////////////////////////////////////////////*/

//Максимальное число потоков, обрабатываемых одновременно.

const int k_MAXTHREADS = 100 ;

ГЛАВА 3 Отладка при кодировании

117

 

 

// Синоним типа GetProcessHandleCount.

typedef BOOL (__stdcall *GETPROCESSHANDLECOUNT)(HANDLE , PDWORD) ;

/*//////////////////////////////////////////////////////////////////////

//Прототипы масштаба файла.

//////////////////////////////////////////////////////////////////////*/

//Выполняет действия по отображению диалога утверждения.

static INT_PTR PopTheFancyAssertion ( TCHAR *

szBuffer

,

LPCSTR

szEmail

,

DWORD64

dwStack

,

DWORD64

dwStackFrame

,

DWORD64

dwIP

,

int *

piIgnoreCount

) ;

// Пытается получить модуль, вызвавший утверждение. static SIZE_T GetModuleWithAssert ( DWORD64 dwIP ,

TCHAR * szMod ,

DWORD dwSize ) ;

//Да, это встраиваемая функция, но, чтобы ее

//задействовать, надо создать для нее прототип. extern "C" void * _ReturnAddress ( void ) ; #pragma intrinsic ( _ReturnAddress )

//Функция, скрывающая махинации по получению

//открытых потоков в процессе.

static BOOL SafelyGetProcessHandleCount ( PDWORD pdwHandleCount ) ;

/*//////////////////////////////////////////////////////////////////////

//Глобальные объявления масштаба файла.

//////////////////////////////////////////////////////////////////////*/

//Глобальное число игнорируемых утверждений.

int g_iGlobalIgnoreCount = 0 ; // Общее число утверждений.

static int g_iTotalAssertions = 0 ;

//Критическая секция, защищающая все. static CCriticalSection g_cCS ;

//Указатель на функцию GetProcessHandleCount. static GETPROCESSHANDLECOUNT g_pfnGPH = NULL ;

/*//////////////////////////////////////////////////////////////////////

//Реализация!

//////////////////////////////////////////////////////////////////////*/

//Отключаем ошибку "unreachable code" для этой функции,

//вызывающей ExitProcess.

#pragma warning ( disable : 4702 )

BOOL RealSuperAssertion ( LPCWSTR

szType

,

LPCWSTR

szExpression

,

LPCWSTR

szFunction

,

см. след. стр.

118 ЧАСТЬ I Сущность отладки

LPCWSTR

szFile

,

int

iLine

,

LPCSTR

szEmail

,

DWORD64

dwStack

,

DWORD64

dwStackFrame

,

DWORD64

dwIP

,

int *

piFailCount

,

int *

piIgnoreCount

)

{

// В начале всегда увеличиваем общее число утверждений,

//инициированных на данный момент. g_iTotalAssertions++ ;

//Увеличиваем количество сбоев данного конкретного экземпляра. if ( NULL != piFailCount )

{

*piFailCount = *piFailCount + 1 ;

}

//Смотрим, нельзя ли быстро завершить работу с диалогом.

//Значение " 1" значит "игнорировать все".

if ( (

g_iGlobalIgnoreCount < 0

)

||

(

( NULL != piIgnoreCount ) && *piIgnoreCount < 0 )

)

{

return ( FALSE ) ;

}

// Если число игнорирований всех утверждений // еще не исчерпано, можно выходить.

if ( g_iGlobalIgnoreCount > 0 )

{

g_iGlobalIgnoreCount— ; return ( FALSE ) ;

}

// Надо ли пропустить это локальное утверждение?

if ( ( NULL != piIgnoreCount ) && ( *piIgnoreCount > 0 ) )

{

*piIgnoreCount = *piIgnoreCount 1 ; return ( FALSE ) ;

}

//Содержит возвращаемое значение функций обработки строк (STRSAFE). HRESULT hr = S_OK ;

//Сохраняем код последней ошибки, чтобы не сбить

//его при работе с диалогом утверждения.

DWORD dwLastError = GetLastError ( ) ;

ГЛАВА 3 Отладка при кодировании

119

 

 

 

 

 

 

 

 

TCHAR szFmtMsg[ MAX_PATH ] ;

 

 

 

DWORD dwMsgRes = ConvertErrorToMessage ( dwLastError

,

 

 

szFmtMsg

,

 

 

sizeof ( szFmtMsg ) /

 

 

sizeof ( TCHAR ) ) ;

 

if ( 0 == dwMsgRes )

 

 

 

{

 

 

 

hr = StringCchCopy ( szFmtMsg

,

 

 

sizeof ( szFmtMsg ) / sizeof ( TCHAR ) ,

 

 

_T ( "Last error message text not available\r\n" ) ) ;

 

ASSERT ( SUCCEEDED ( hr ) ) ;

 

 

 

}

 

 

 

// Получаем информацию о модуле.

 

 

 

TCHAR szModuleName[ MAX_PATH ] ;

 

 

 

if ( 0 == GetModuleWithAssert ( dwIP , szModuleName , MAX_PATH ))

 

 

{

 

 

 

hr = StringCchCopy ( szModuleName

 

,

 

sizeof ( szModuleName ) / sizeof (TCHAR) ,

 

_T ( "<unknown application>" )

);

 

ASSERT ( SUCCEEDED ( hr ) ) ;

 

 

 

}

 

 

 

// Захватываем синхронизирующий объект,

 

 

 

// чтобы не дать другим потокам достигнуть этой точки.

 

 

EnterCriticalSection ( &g_cCS.m_CritSec ) ;

 

 

 

// Буфер для хранения сообщения с выражением.

 

 

 

TCHAR szBuffer[ 2048 ] ;

 

 

 

#define BUFF_CHAR_SIZE ( sizeof ( szBuffer ) / sizeof ( TCHAR ) )

 

 

if ( ( NULL != szFile ) && ( NULL != szFunction ) )

 

 

 

{

 

 

 

// Выделяем базовое имя из полного имени файла.

 

 

 

TCHAR szTempName[ MAX_PATH ] ;

 

 

 

LPTSTR szFileName ;

 

 

 

LPTSTR szDir = szTempName ;

 

 

 

hr = StringCchCopy ( szDir

 

,

 

sizeof ( szTempName ) / sizeof ( TCHAR ) ,

 

szFile

 

);

 

ASSERT ( SUCCEEDED ( hr ) ) ;

 

 

 

szFileName = _tcsrchr ( szDir , _T ( '\\' ) ) ;

 

 

 

if ( NULL == szFileName )

 

 

 

{

 

 

 

szFileName = szTempName ;

 

 

 

szDir = _T ( "" ) ;

 

 

 

}

 

 

 

else

 

 

 

{

 

 

 

 

 

 

 

см. след. стр.

120 ЧАСТЬ I Сущность отладки

*szFileName = _T ( '\0' ) ; szFileName++ ;

}

DWORD dwHandleCount = 0 ;

if ( TRUE == SafelyGetProcessHandleCount ( &dwHandleCount ) )

{

//Используем новые функции STRSAFE,

//чтобы не выйти за пределы буфера. hr = StringCchPrintf (

szBuffer

 

,

BUFF_CHAR_SIZE

 

,

_T ( "Type

: %s\r\n"

)\

_T ( "Expression

: %s\r\n"

)\

_T ( "Module

: %s\r\n"

)\

_T ( "Location

: %s, Line %d in %s (%s)\r\n")\

_T ( "LastError

: 0x%08X (%d)\r\n"

)\

_T ( "

%s"

)\

_T ( "Fail count

: %d\r\n"

)\

_T ( "Handle count : %d"

),

szType

 

,

szExpression

 

,

szModuleName

 

,

szFunction

 

,

iLine

 

,

szFileName

 

,

szDir

 

,

dwLastError

 

,

dwLastError

 

,

szFmtMsg

 

,

*piFailCount

 

,

dwHandleCount

 

);

ASSERT ( SUCCEEDED ( hr ) ) ;

 

}

 

 

else

 

 

{

 

 

hr = StringCchPrintf (

 

 

szBuffer

 

,

BUFF_CHAR_SIZE

 

,

_T ( "Type

: %s\r\n"

) \

_T ( "Expression

: %s\r\n"

) \

_T ( "Module

: %s\r\n"

) \

_T ( "Location

: %s, Line %d in %s (%s)\r\n")\

_T ( "LastError

: 0x%08X (%d)\r\n"

) \

_T ( "

%s"

) \

_T ( "Fail count : %d\r\n"

) ,

szType

 

,

szExpression

 

,

szModuleName

 

,

szFunction

 

,

iLine

 

,

Соседние файлы в предмете Программирование на C++