SEH исключения - это часть операционной системы и теоретически могут использоваться
из любой программы. К сожалению, Microsoft не предоставляет документации по
реализации исключений, поэтому их можно использовать, только если поддержка
встроена в компилятор языка. Зная особенности реализации, можно добавить SEH
в любую программу.
Исключения C++, с другой стороны, являются частью языка и поддерживаются всеми
компиляторами. Особенности реализации также не описаны в стандарте и остаются
полностью на откуп разработчикам компиляторов.
Разрабатывая приложения на C++ необходимо использовать именно средства языка.
В документации к MS VC и в Интернете есть неоднократные упоминания, что смешивать
SHE и C++ исключения не рекомендуется. С практической точки зрения использование
средств языка более предпочтительно, т.к. при обработке исключений и раскрутке
стека вызываются деструкторы объектов.
Рассматривая ассемблерный код фреймов C++ исключений можно заметить, что в
компиляторе от MS реализация исключений сделана через SEH. Например, блок try
превращается в __try, а catch в __except. Оператор throw превращается в RaiseException.
Значение переменной оператора throw передается через аргумент функции RaiseException.
try
{
throw int(1);
}
catch(int){}
На псевдокоде это можно записать с использованием SEH:
Разработчики 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(unsignedint, 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(unsignedint 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++ исключения. Это вполне допустимо,
потому что теперь вы знаете, как они устроены изнутри.