Здравствуйте уважаемые подписчики, сегодня в номере:
От автора
Статья "Структурная обработка исключений (SEH) в примерах.
Часть 2"
От автора
После длительного перерыва рассылка начала работать в обычном
режиме. В ближайшие пару недель я постараюсь наверстать упущенное и
выпустить все пропущенные статьи.
В сегодняшнем выпуске мы продолжим рассматривать тему SEH
исключений. Это предпоследняя статья этого цикла.
Задавайте свои вопросы, я с удовольствием на них отвечу.
Очень важную роль в обработке исключений играет функция GetExceptionCode.
С помощью нее фильтр исключения может узнать причину ошибки. Обработчик исключения
должен знать, как реагировать на разные ситуации.
__try
{}
__except ((GetExceptionCode() == EXCEPTION_FLT_STACK_CHECK) ? EXCEPTlON_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH){// обработка переполнения при операции с плавающей точкой }
В этом примере обработчик знает, что делать только с одним типом исключения.
Все остальные ошибки – отправляются к вышестоящему обработчику.
Ниже приведен список всех возможных значений, которые возвращает GetExceptionCode.
EXCEPTION_ACCESS_VIOLATION – поток попытался обратиться
к виртуальному адресу, к которому у него нет доступа
EXCEPTION_BREAKPOINT – выполнение достигло точки останова
EXCEPTION_DATATYPE_MISALIGNMENT – попытка доступа к не
выровненным данным на устройстве, которое требует выравнивания
EXCEPTION_SINGLE_STEP – Возник сигнал о пошаговом выполнении
программы (трассировочная ловушка или другой механизм)
EXCEPTION_ARRAY_BOUNDS_EXCEEDED – обращение за границу
массива на устройстве, которое проверяет границы
EXCEPTION_FLT_DENORMAL_OPERAND – один из операндов операции
с плавающей точкой не нормализован.
EXCEPTION_FLT_DIVIDE_BY_ZERO – поток попытался сделать
деление на ноль с плавающей точкой.
EXCEPTION_FLT_INEXACT_RESULT - результат операции над
числами с плавающей точкой нельзя точно представить в виде десятичной дроби
EXCEPTION_FLT_INVALID_OPERATION – другие исключения операций
с плавающей точкой, которые не выделены в данном списке
EXCEPTION_FLT_OVERFLOW – переполнение при операции над
числами с плавающей точкой.
EXCEPTION_FLT_STACK_CHECK - переполнение стека или выход
за его нижнюю границу в результате выполнения операции над числами с плавающей
точкой
EXCEPTION_FLT_UNDERFLOW - порядок результата операции
над числами с плавающей точкой меньше минимальной величины для указанного
типа данных
EXCEPTION_INT_DIVIDE_BY_ZERO – целочисленное деление на
ноль
EXCEPTION_INT_OVERFLOW – переполнение разрядной сетки
при операциях с целыми числами
EXCEPTION_PRIV_INSTRUCTION – выполнение инструкции, которая
не доступна в данном режиме процессора
EXCEPTION_NONCONTINUABLE_EXCEPTION - фильтр исключений
вернул EXCEPTION_CONTINUE_EXECUTION в ответ па невозобновляемое
исключение (noncontinuable exception)
Список констант находится в файле WinBase.h.
Функция GetExceptionCode является встраиваемой.
Т.е. она поддерживается компилятором напрямую и ее нет ни в одной библиотеке.
GetExceptionCode можно вызывать только из фильтра исключений
- между скобками оператора _except, или из обработчика исключений. Ее нельзя
вызывать из функции-фильтра исключений. В примере выше дан первый вариант использования.
Второй выглядит так:
Во всех примерах использовались жестко зашитые значения в качестве фильтра
исключений. Однако нам ничего не мешает записать вместо него выражение с вызовом
функции. Задача такой функции - проанализировать причину исключения, возможно,
выполнить предварительную обработку, и вернуть значение, на основании которого
будет выбираться обработчик исключения. Рассмотрим пример:
Важной особенностью функции Filter является то, что она выполняется
на этапе поиска обработчика исключения. Это позволяет делать некоторые экзотические
вещи. Например, эмулировать отображение файла на виртуальную память. По мере
обращения по неверным указателям – будут вызываться исключения. Обработчик может
выделять память, загружать в нее содержимое, а потом перезапускать программу
с прерванного места. Выполнение таких действий невозможно из тела обработчика
исключений.
Существует еще одна очень полезная функция: GetExceptionInformation. Когда
возникает исключение, система запоминает структуры EXCEPTION_RECORD,
CONTEXT и EXCEPTION_POINTERS. Они в совокупности
описывают полностью контекст возникновения исключений.
Функция GetExceptionInformation, как и GetExceptionCode
является встраиваемой. Ее можно вызывать только из фильтра исключений, т.к.
именно в этот момент существуют структуры с контекстом исключения.
Чтобы получить доступ к информации из структур EXCEPTION_RECORD
или CONTEXT надо сохранить их содержимое из фильтра исключения.
Эти структуры существуют только во время обработки фильтра исключения, поэтому
запоминать на них указатель нет смысла.
Из структуры EXCEPTION_RECORD можно получить код исключения,
адрес возникновения исключения и другую полезную информацию. Это все позволяет
строить интересные стратегии обработки ошибок. В частности, в совокупности с
мини дампами можно построить систему сбора информации об ошибках на стороне
клиента, чтобы своевременно вносить исправления в код и добиваться высокого
качества ПО.
Если возникает необходимость использовать GetExceptionInformation
или GetExceptionCode из функции фильтра исключений, надо передавать
результат их работы через параметр фильтра. Такой способ используется в предыдущем
примере.
Использование этих функций внутри фильтра недопустимо. Компилятор отслеживает
все подобные попытки и выдает сообщения об ошибках.
Программные исключения
В предыдущих примерах исключения выступали в роли непредвиденного фактора.
Они служили индикатором, что программный код содержит ошибки. Существуют и «предвиденные»
исключения. О них и пойдет речь. Программные исключения могут вызываться принудительно
самой программой. Механизм перехвата и обработки исключения такой же, как и
при возникновении аппаратных исключений.
Классический подход в обработке ошибок - когда функция возвращает код ошибки.
Вызывающая сторона анализирует его и выполняет либо восстановление после ошибки,
либо освобождает ресурсы и тоже возвращает ошибку. Альтернативный вариант –
использование исключений для сообщения об ошибках. Принцип абсолютно тот же
самый, что и при использовании C++ исключений.
Очевидным недостатком использования SEH исключений является невозможность переноса
кода на другие платформы. SEH - это часть ОС Windows. При разработке программ
предпочтительно использовать механизм C++ исключений. Они обеспечивают переносимость
между платформами. Кроме того, они дают гибкий механизм для определения типа
исключений.
Некоторые WinAPI функции могут возбуждать SEH исключения. Например, HeapCreate
может сгенерировать исключение, если ей в параметрах указать специальный флаг:
HEAP_GENERATE_EXCEPTIONS. При вызове WinAPI всегда можно избежать
использования исключений.
Возбудить программное исключение очень просто. Достаточно вызывать RaiseException,
которая имеет следующий прототип:
Первый параметр - это код исключения. Его можно получить с помощью GetExceptionCode.
Эту функцию мы рассматривали выше. Код исключения можно построить по правилам,
применяемым для стандартных кодов ошибок Windows. Второй параметр указывает,
можно ли возобновить выполнение программы с команды, следующей за RaiseException.
Он может принимать значения 0 или EXCEPTION_NONCONTINUABLE.
Если вы указали EXCEPTION_NONCONTINUABLE, то фильтр обработчика
исключения не может вернуть значение EXCEPTION_CONTINUE_EXECUTION.
Если фильтр все же вернет это значение, то система возбудит еще одно исключение
с кодом EXCEPTION_NONCONTINUABLE_EXCEPTION.
Повторное возбуждение исключений – вполне типичная ситуация. Ведь обработчик
исключения тоже может породить исключение, так же, как и блок finally.
В этом случае обработчику верхнего уровня (который перехватит новое исключение)
будет доступна информация о предыдущем исключении. Указатель на предыдущее исключение
содержится в структуре EXCEPTION_RECORD. Ее можно получить
с помощью GetExceptionInformation. Фактически получается односвязный
список из структур EXCEPTION_RECORD.
Последние два параметра могут использоваться для передачи параметров исключения.
Они доступны обработчику через структуру EXCEPTION_RECORD.
Это поля NumberParameters и Exceptionlnformation.
Обычно эти параметры нулевые. Если надо передать параметры, то nNumberOfArguments
содержит количество указателей ULONG_PTR в массиве pArguments.
Сообщаем о том, что в системе нет памяти, и запрещаем перезапуск приложения
со следующей инструкции. Предполагаем, что обработчики исключения не в состоянии
изыскать недостающую память. Т.е. перезапуск не имеет смысла.
Программные исключения могут использоваться для сообщения об ожидаемых ошибках,
таких, как ошибки при доступе к файлу, некорректный ввод пользователя и т.п.
Наряду с этим их можно использовать и для сообщения о фатальных ошибках, которые
в конечном итоге приведут к завершению программы. Если в программе грамотно
спланирована обработка исключений, то можно регистрировать все ошибочные ситуации
и делать эффективную обработку ошибок.