DevDoc home page DevDoc background gradient
 
   
Имя Пароль Запомнить Зарегистрироваться

Автор: Кудинов Александр
Последняя модификация: 2007-05-30 19:06:56

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);
}

Рассмотрим работу этой программы по шагам:

  1. Устанавливаем функцию-транслятор с помощью _set_se_translator.
  2. Вызывается SEFunc
  3. Генерируется исключение
  4. Выполняется функция-транслятор, которая переводит SEH в C++ исключение.
  5. По правилам SEH выполняется блок __finaly
  6. Выполняется блок catch с типом SE_Exception. Т.е. блоку catch становятся доступны параметры SEH исключения.

Обратите внимание, что функция-транслятор должна устанавливаться для каждого потока в программе.

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

Подпишитесь, чтобы получать все новые статьи с сайта первым!

Оценить статью Текущий рейтинг: 3.0952. Проголосовало 42 человек.

Коментарии к статье

нифига не понятно
Советую прочитать вводные статьи по 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 не помогает

т.е., тут не совсем хорошо это дело работает при большой косвенности кода (хотя это конечно не причина, просто факт)

Добавить комментарий

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

Отправлять сообщения могут только зарегистрированные пользователи

Copyright (C) Kudinov Alexander, 2006-2012

Перепечатка материалов с данного сайта запрещена без письменного разрешения автора. При перепечатке обязательно указывать ссылку на оригинал.

Generation time: 0,216194868088 seconds