Отладка приложений

         

Комбинирование обработки SEH- и С++-исключений


Как было сказано выше, существует возможность сочетания обработки SEH- и С++-исключений. В результате программист получает возможность использовать в своем коде только обработку исключений языка C++. Функция _set_se_transiator исполнительной библиотеки языка C++ позволяет установить специальную транслирующую функцию, которая будет вызываться, когда случается структурированное исключение, так что вы можете возбуждать только исключения C++. Мощь этой функции не видна с первого взгляда. Следующий отрывок кода показывает, что должна делать функция-транслятор:

void SEHToCPPException ( UINT uiEx,

EXCEPTION_POINTERS * pExp)

{

// CSEHException — это класс, производный от MFC-класса CException



throw CSEHException ( uiEx, pExp); 

}

Первый параметр — это SEH-код, возвращаемый при вызове функции GetExceptionCode, а второй — состояние исключения из вызова функции GetExceptionlnformation.

При использовании функции _set_se_transiator необходимо отлавливать классы исключений, возбуждаемых этой функцией только в том случае, если ожидается авария. Например, если вы разрешаете пользователям расширять приложение с помощью DLL, то для обработки потенциальных аварий вызовы соответствующих DLL можно разместить в блоках try.. .catch. Однако, если в ходе нормальной обработки происходит тяжелая SEH-авария, следует завершить выполнение приложения. В одной из своих программ я случайно обработал нарушение доступа вместо обычного аварийного останова. В результате, вместо того чтобы просто оставить пользовательские данные в покое, я стер несколько файлов.

Причина, по которой необходимо соблюдать осторожность, обрабатывая тяжелые SEH-исключения как исключения языка C++, заключается в том, что процесс находится в нестабильном состоянии. В ходе обработки вы можете открывать диалоговые окна и записывать аварийную информацию в файл. Однако следует помнить, что стек может быть переполнен, так что места для вызова большого количества функций может быть явно недостаточно. Поскольку код исключения вам передает функция-транслятор, то необходимо проверить, не имеет ли он значение EXCEPTION_STACK_OVERFLOW, и плавно снизить интенсивность обработки ошибок, если места в стеке недостаточно.


Как видно по предшествующему фрагменту кода (который переводит SEH-исключение в исключение языка C++), вы можете возбуждать исключения любого типа (класса), какие пожелаете. Реализация класса исключений тривиальна; интерес представляет лишь та ее часть, где выполняется перевод информации EXCEPTION_POINTERS в удобочитаемую форму. Но перед подробным описанием этого кода рассмотрим кратко асинхронную и синхронную обработку исключений в языке C++.

Асинхронная и синхронная обработка исключений в C++

Используя обработку исключений языка C++ необходимо понимать различие между асинхронной и синхронной обработкой исключений. К сожалению, термины асинхронная и синхронная не вполне адекватно описывают различие между этими двумя типами обработки исключений в C++. Реальное различие между асинхронной и синхронной обработкой исключений в C++ сводится к следующему: компилятор генерирует код обработки исключений для программы в зависимости от того, как, по его предположению, будут возбуждаться исключения.

При асинхронной обработке исключений в C++ компилятор предполагает, что каждая инструкция может генерировать исключение, и что код должен быть готов обрабатывать исключения где угодно. В Visual C++ 5 модель обработки исключений по умолчанию была асинхронной. Проблема с асинхронной обработкой исключений заключается в том, что компилятор должен прослеживать время жизни объектов и быть готовым к "раскрутке" исключений в любой точке кода. Весь дополнительно сгенерированный код может быть достаточно объемным (и при этом нередко бесполезным).

При синхронной обработке исключений, которая используется по умолчанию в Visual C++ 6, компилятор ожидает, что программа возбуждает исключения только с помощью явного оператора throw. Таким образом, компилятор не должен прослеживать продолжительность жизни объекта и не должен генерировать код, необходимый для обработки "раскручивания", если время жизни объекта не перекрывает вызова функции или оператора throw. Понятия асинхронности и синхронности в данном контексте можно сформулировать примерно так: "Асинхронность имеет место тогда, когда все функции программы отслеживают время жизни объектов, а синхронность — когда лишь некоторые функции отслеживают это время".



Влияние ключевого умалчиваемого параметра компилятора на синхронную обработку исключений заключается в том, что в финальных построениях (релизах) Программы Тщательно сконструированная функция _set_se_translator никогда не вызывается, код не отлавливает транслированных исключений, а приложение завершается аварийно (как и положено нормальному приложению!). По умолчанию ключ /GX отображается в /EHSC (синхронные исключения), поэтому, чтобы включить асинхронные исключения, необходимо явно указать ключ /ЕНa (асинхронные исключения). К счастью, разработчик не должен активизировать асинхронные исключения на уровне всего проекта — можно без проблем компилировать разные исходные файлы с различной обработкой исключений и связывать их вместе.

Если требуется компилировать программу с асинхронной обработкой исключений (с ключом /ЕНa), но без издержек на отслеживание времени жизни объектов на функциях, которые не возбуждают исключений, то для объявления или определения таких функций можно использовать спецификатор _decispec(nothrow). Хотя нужны дополнительные затраты, чтобы вручную вставлять в программу эти спецификаторы, но вы получите дополнительные выгоды от применения функции _set_se_translator и более "плотного" кода.

Листинг 9-4 демонстрирует программу, использующую функцию _set_se_ translator, которая не работает, если компилируется как финальное построение с умалчиваемыми (т. е. синхронными) исключениями. Для использования асинхронных исключений эта программа должна компилироваться с ключом /ЕНа. Таким образом, если нужно гарантировать повсеместное применение функции _set_se_transiator, включая функции, расположенные вне классов, то необходимо выполнять компиляцию с ключом /ЕНа и смириться с большим объемом дополнительного кода. Для работы с синхронными исключениями (что особенно полезно, если программа на C++ использует классы MFC) следует применять класс, выброшенный функцией _set_se_transiator, только в методах (функциях-членах) используемых классов.

Листинг 9-4.


Пример, в котором синхронные исключения не работают

// Компилировать как выпуск (версию) Win32, используя ключ /GX,

// чтобы убедиться, что функция-транслятор не будет вызываться.

// (Ключ /GX отображается в /EHsc.) Чтобы заставить эту программу

// работать в финальных построениях, компилируйте ее с ключом /ЕНа.

#include "stdafx.h" 

class CSEHError 

{

 public :

CSEHError ( void) 

{

m_uiErrCode = 0; 

}

CSEHError ( unsigned int u) 

{

m_uiErrCode = u; 

}

~CSEHError ( void) 



}

unsigned int m_uiErrCode; 

};

void TransFunc ( unsigned int u, EXCEPTION_POINTERS * pEP) 

{

printf ( "In TransFuncXn"); 

throw CSEHError ( u); 

}

void GrungyFunc ( char * p) 

{.

*P = 'p';

printf ( "This output should never be seen!\n"); 

}

void DoBadThings ( void)

 {

try 

{

GrungyFunc ( (char*)0xl); 

}

catch ( CSEHError e) 

{

printf ( "Got an exception! -> Ox%08X\n", e.m_uiErrCode);



}

int main ( int argc, char* argv[]) 

{

_set_se_translator ( TransFunc);

DoBadThings ();

return 0; 

}



Содержание раздела