Структурированная обработка исключений
Структурированную обработку исключений (SEH) обеспечивает операционная система. Она напрямую связана с такими авариями, как нарушение доступа. SEH-обработка не зависит от языка и в программах C/C++ обычно реализуется парами ключевых слов _try/_except и _try/_finally. Методика использования пары _try/_except такова: сначала нужно установить свой код внутри блока _try, затем определить, как следует обрабатывать исключения в блоке _except (который называют также обработчиком исключений (exception handler)). В паре _try/_finally блок _finally (именуемый также обработчиком завершения (termination handler)) гарантирует, что данная секция кода будет всегда выполняться после выхода из функции (в которой размещается пара _try/_except), даже если код в блоке _try завершится преждевременно.
Листинг 9-1 содержит типичную функцию с SEH-обработкой. Блок _except выглядит почти как вызов функции, но в круглых скобках указывается значение специального выражения, называемого фильтром исключения (exception filter). В листинге 9-1 значение фильтра исключения (EXCEPTION_ EXECUTE_HANDLER) указывает на то, что код в блоке except должен выполняться каждый раз, когда внутри блока try происходит какое-нибудь (любое) исключение. Два других возможных значения фильтра исключения — EXCEPTION_CONTINUE_EXECUTION (позволяет проигнорировать исключение ) и EXCEPTION_CONTINUE_SEARCH (передает исключение через цепочку вызовов следующему блоку except). Можно сделать выражение фильтра исключений настолько простым или сложным, насколько это необходимо, что позволяет разработчику планировать только те исключения, в обработке которых он заинтересован.
Листинг 9-1. Пример SEH-обработчика
void Foo ( void) {
__try
{
_try
{
// Выполняемый код (любой).
}
_except ( EXCEPTION_EXECUTE_HANDLER)
{
// Этот блок будет выполняться, если в коде блока try
// возникает нарушение доступа или любой другой фатальный
// останов. Данный код также называют обработчиком исключения.
}
}
_finally
{
// Этот блок будет выполняться независимо от того, вызывает
// функция аварийный останов или нет. Здесь должен быть размещен
// код обязательной чистки.
}
}
Процесс поиска и выполнения обработчика исключений иногда называют раскруткой исключения (unwinding the exception). Обработчики исключений хранятся во внутреннем стеке. По мере роста цепочки вызовов функций обработчик исключений каждой новой функции (если он существует) помещается в этот внутренний стек. Когда происходит исключение, операционная система находит стек обработчика исключений потока и начинает вызывать обработчики исключений до тех пор, пока один из них не укажет, что именно он будет обрабатывать исключение. Как только исключение пройдет весь стек в поисках "своего" обработчика исключений, операционная система очищает стек вызовов и выполняет любые обработчики завершения, которые она находит. Если "раскручивание" продолжается до конца стека обработчиков исключений, то раскрывается диалоговое окно Application Error.
Ваш обработчик исключений может определять значение исключения с помощью специальной функции GetExceptionCode, которая вызывается только в фильтрах исключений. Если бы вы, например, писали математический пакет, то могли бы иметь обработчик исключений, который обрабатывает попытку "деления на нуль" и возвращает значение NaN (не число). Пример такого обработчика исключений показан в листинге 9-2. Фильтр исключений вызывает функцию GetExceptionCode, и если исключение есть "деление на нуль", то обработчик исключений выполняется. Если происходит любое другое исключение, то EXCEPTION_CONTINUE_SEARCH сообщает операционной системе, что нужно выполнять следующий блок _except (вверх по цепочке вызовов).
Листинг 9-2. Пример SEH-обработчика с обработкой фильтра исключения
long IntegerDivide ( long x, long у)
{
long IRet; _try
{
IRet = x / y;
}
_except ( EXCEPTION_INT_DIVIDE_BY_ZERO ==
GetExceptionCode ()
? EXCEPTION_EXECUTE_HANDLER
: EXCEPTION_CONTTNUE_SEARCH
)
{
IRet = NaN;
}
return ( IRet);
}
Если фильтр исключений должен быть более сложным, то в этом качестве можно даже вызвать одну из собственных функций (если она указывает, как нужно обрабатывать исключение, и возвращает одно из правильных значений фильтра исключений). В дополнение к вызову специальной функции GetExceptionCode, можно также вызывать функцию GetExceptionlnformation (в выражении фильтра исключений). Данная функция возвращает указатель на структуру EXCEPTION_POINTERS, которая полностью описывает причину аварии и состояние CPU в этот момент. Можно догадаться, что структура EXCEPTION_POINTERS пригодится позже в настоящей главе.
SEH-обработка не ограничивается только обработкой аварий. Программист может также создавать собственные исключения с помощью API-функции RaiseException. Большинство разработчиков не использует эту функцию, но она предлагает возможность быстрого выхода из глубоко вложенных условных операторов. Техника выхода из условных операторов с помощью функции RaiseException более корректна, чем применение старых функций setjmp и longjmp (из исполнительной библиотеки С).
Прежде чем углубляться в SEH-обработку и начинать ее использовать, нужно запомнить два ограничения. Первое (незначительное): коды ошибки ваших заказчиков представлены единственным целым числом без знака. Второе ограничение немного серьезнее: SEH-обработка не очень хорошо сочетается с программированием на C++, потому что сами C++-исключения реализованы с помощью SEH-обработчиков, и компилятор не всегда принимает ваши попытки произвольно комбинировать их. Причиной конфликта является то, что когда прямой SEH-обработчик раскручивается из функции, он не вызывает никаких деструкторов для объектов, созданных в стеке. Поскольку объекты C++ могут выполнять все виды инициализации в своих конструкторах (например, распределение памяти для внутренних структур данных), пропуск деструкторов может привести к утечкам памяти и другим проблемам.
Тем, кто захочет изучить SEH-обработчики подробнее, можно рекомендовать две ссылки (кроме просмотра MSDN). Лучший краткий обзор SEH-обработчиков приведен в книге Джеффри Рихтера (Jeffrey Richter, Programming Applications for Microsoft Windows, Microsoft Press, 1999). Если вы интересуетесь фактической реализацией SEH-обработчиков, найдите статью Мэтта Пиетрека — Matt Pietrek, "A Crash Course on the Depths of Win32 Structured Exception Handling" в январском номере журнала Microsoft Systems Journal за 1997 год.