SEH исключения VS С++ исключения
Введение
SEH исключения - это часть операционной системы и теоретически могут использоваться
из любой программы. К сожалению, Microsoft не предоставляет документации по
реализации исключений, поэтому их можно использовать, только если поддержка
встроена в компилятор языка. Зная особенности реализации, можно добавить SEH
в любую программу.
Исключения C++, с другой стороны, являются частью языка и поддерживаются всеми
компиляторами. Особенности реализации также не описаны в стандарте и остаются
полностью на откуп разработчикам компиляторов.
Разрабатывая приложения на C++ необходимо использовать именно средства языка.
В документации к MS VC и в Интернете есть неоднократные упоминания, что смешивать
SHE и C++ исключения не рекомендуется. С практической точки зрения использование
средств языка более предпочтительно, т.к. при обработке исключений и раскрутке
стека вызываются деструкторы объектов.
Описание SEH можно найти в статьях: «Введение
в обработку структурированных исключений SEH», «Структурная
обработка исключений (SEH) в примерах. Часть 1» и «Структурная
обработка исключений (SEH) в примерах. Часть 2». В этой статье мы подробнее
будем рассматривать C++ исключения вместе с SEH.
С++ исключения изнутри
Рассматривая ассемблерный код фреймов C++ исключений можно заметить, что в
компиляторе от MS реализация исключений сделана через SEH. Например, блок try
превращается в __try, а catch в __except. Оператор throw превращается в RaiseException.
Значение переменной оператора throw передается через аргумент функции RaiseException.
try
{
throw int(1);
}
catch(int)
{
}
На псевдокоде это можно записать с использованием SEH:
__try
{
RaiseException(OxE06D7363, EXCEPTION_NONCONTINUABLE, PARAMETERS(1));
}
__except(PARAMETER == int ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SFARCH)
{
}
Разработчики MS VC используют код OxE06D7363 для того, чтобы
выделить C++ исключения. Обратите внимание, что возобновить выполнение программы
в точке возбуждения исключения невозможно. Способ передачи типов и значений
операндов throw не документирован. Однако не трудно догадаться, как это происходит.
Оператор __except выполняет сравнение типа исключения. Если тип совпадет с
нужным – возвращается значение EXCEPTION_EXECUTE_HANDLER и выполняется тело
обработчика. Если тип не совпал, -происходит поиск другого обработчика исключения,
который способен обработать указанный тип.
Теперь, когда вы знаете, как реализованы исключения, можно смешивать оба эти
механизма в рамках одной программы. Использование SEH, на мой взгляд, целесообразно,
если надо возобновлять выполнение программы с прерванного места. Т.е. фильтр
исключения возвращает константу EXCEPTION_CONTINUE_EXECUTION. В остальных случаях
можно обойтись с помощью встроенного в язык механизма исключений try/catch.
Такой подход особенно интересен, потому что есть способ перехватывать SEH исключения
с помощью try/catch.
Перехват структурных исключений (SEH) с помощью try/catch
В MS VC можно перехватить структурные исключения с помощью следующего кода:
try
{
*(char*)0 = 10;
}
catch(…)
{
//сработает этот код при попытке записи по нулевому адресу.
}
Такой синтаксис предотвратит крах программы. Но, к сожалению, мы не можем получить
доступ к кодам исключений и его контексту.
C++ - это очень гибкий язык, а разработчики Майкрософта позаботились о том,
чтобы максимально облегчить совместное использование SEH и встроенных в язык
исключений. Работает это следующим образом. Каждый раз, когда возникает структурное
исключение – вызывается специальная функция «транслятор». Ей передаются результаты
работы функций GetExceptionCode и GetExceptionInformation. Дальше эта функция
может «запаковать» эти значения вовнутрь класса и выкинуть его в виде C++ исключения.
По-умолчанию, в системе не установлена функция-транслятор. Ее можно установить
с помощью библиотечной функции _set_se_translator.
Вот как это делается:
// crt_settrans.cpp
// compile with: /EHsc
#include <stdio.h>
#include <windows.h>
#include <eh.h>
void SEFunc();
void trans_func( unsigned int, EXCEPTION_POINTERS* );
//Класс-обертка, которая хранит в себе информацию об исключении.
class SE_Exception
{
private:
EXCEPTION_RECORD m_er;
CONTEXT m_context;
public:
SE_Exception(PEXCEPTION_POINTERS pep)
{
m_er = *pep->ExceptionRecord;
m_context = *pep->ContextRecord;
}
~SE_Exception() {}
};
int main( void )
{
try
{
_set_se_translator( trans_func );
SEFunc();
}
catch( SE_Exception e )
{
printf( "Caught a __try exception with SE_Exception.\n" );
}
}
void SEFunc()
{
__try
{
int x, y=0;
x = 5 / y;
}
__finally
{
printf( "In finally\n" );
}
}
void trans_func( unsigned int u, EXCEPTION_POINTERS* pExp )
{
printf( "In trans_func.\n" );
throw SE_Exception(pExp);
}
Рассмотрим работу этой программы по шагам:
- Устанавливаем функцию-транслятор с помощью _set_se_translator.
- Вызывается SEFunc
- Генерируется исключение
- Выполняется функция-транслятор, которая переводит SEH в C++ исключение.
- По правилам SEH выполняется блок __finaly
- Выполняется блок catch с типом SE_Exception. Т.е. блоку catch становятся
доступны параметры SEH исключения.
Обратите внимание, что функция-транслятор должна устанавливаться
для каждого потока в программе.
Как видим, с помощью C++ исключений можно перехватывать SEH. Поэтому в большинстве
случаев можно обойтись исключениями C++. Они обладают достаточной мощью, чтобы
перехватывать аппаратные и программные SEH исключения. Такой подход по-прежнему
не позволяет перезапускать программу с инструкции, которая вызвала исключение.
Перезапуск программы может потребоваться в очень редких случаях, поэтому в контексте
повседневной обработки ошибок это не актуально. Для тех, кому требуется такая
функциональность, могут смешивать SEH и C++ исключения. Это вполне допустимо,
потому что теперь вы знаете, как они устроены изнутри.
Коментарии к статье|
| нифига не понятно | |
| Советую прочитать вводные статьи по SEH. Ссылки даны в самом начале. Если У вас есть замечание по оформлению, стилю, содержанию и т.п. я с удовольствием их рассмотрю. | |
| Введите Ваш комментарий | |
Код
try
{
*(char*)0 = 10;
}
catch(…)
{
//сработает этот код при попытке записи по нулевому адресу.
}
У меня приводит к закрытию приложения. Возможно надо установить какие либо опции компилятора? | |
Да, точно
Вместо /EHsc () надо использовать /EHa (With SEH Exceptions (/EHa)) | |
с одной стороны удобно:
можно выставить глобальный транслятор структурных исключений, который вызывается всякий раз при исключении (т.е. задать один внешний контекст исключения, которому будут подчиняться даже уже завершенные классы, методы и т.п.)
но похоже мне придется вас разочаровать,
попытка приспособить _set_se_translator у в GUI уже провалилась:
наблюдаю игнорирование транслятора SE внутри вызовов CreateWindowEx, т.е., в момент прихода первого сообщения, я генерирую исключение (пишу по адресу 0, или просто бросаю throw) и оно не передается в транслятор ( если исключение обернуто в try-catch, catch отрабатывает и все, глухо, а должен перед этим передать SE), хотя передается глобальному catch\'еру
у меня логирование исключений происходит как раз в трансляторе, получается, исключение произошло, а в лог оно не попало
уже все проверил: /EHa стоит, поток тут один единственный, даже прямой вызов перед исключением _set_se_translator не помогает
т.е., тут не совсем хорошо это дело работает при большой косвенности кода (хотя это конечно не причина, просто факт) | |
ан, нет, ошибся
чисто структурные исключения транслятор не пропускает ,
а вот с с++ исключениями что-то непонятное, то попадают, то нет, пока повторить не смог | |
с одной стороны удобно:
можно выставить глобальный транслятор структурных исключений, который вызывается всякий раз при исключении (т.е. задать один внешний контекст исключения, которому будут подчиняться даже уже завершенные классы, методы и т.п.)
но похоже мне придется вас разочаровать,
попытка приспособить _set_se_translator у в GUI уже провалилась:
наблюдаю игнорирование транслятора SE внутри вызовов CreateWindowEx, т.е., в момент прихода первого сообщения, я генерирую исключение (пишу по адресу 0, или просто бросаю throw) и оно не передается в транслятор ( если исключение обернуто в try-catch, catch отрабатывает и все, глухо, а должен перед этим передать SE), хотя передается глобальному catch\'еру
у меня логирование исключений происходит как раз в трансляторе, получается, исключение произошло, а в лог оно не попало
уже все проверил: /EHa стоит, поток тут один единственный, даже прямой вызов перед исключением _set_se_translator не помогает
т.е., тут не совсем хорошо это дело работает при большой косвенности кода (хотя это конечно не причина, просто факт) |
|