В C++ под Windows есть два способа обработки исключений - традиционный для C++ с пом. try/catch и т.н. структурная обработка исключений или SEH. Основная разница между ними в том, что с помощью try/catch можно реализовывать для разных типов исключений C++ различную реакцию (поведение), а структурная обработка исключений способна отловить ситуации, которые с пом. catch не ловятся, например, Divide By Zero или Access Violation. SEH - это механизм, предоставляемый операционной системой и на самом деле try/catch реализуется также через него.
Одновременно в одной и той же процедуре использовать оба метода обработки исключений (try/catch и SEH) нельзя. Но можно использовать в одной процедуре try/cath, а в другой SEH.
Оба метода обработки исключений не идеальны и имеют свои достоинства и недостатки.
Основной недостаток try/catch заключается в том, что он не позволяет отловить и обработать все ошибки, а также не показывает место возникновения ошибки. Впрочем, в книжке Джеффри Рихтера (Windows для профессионалов. Создание эффективных Win32-пpилoжeний с учетом специфики 64-разрядной версии Windows) приводится способ перехвата структурных исключений:
1. Нужно создать класс CSE для идентификации структурного исключения:
2. Для каждого потока надо вызвать один раз CSE::MapSEToCE(), после чего можно обрабатывать структурные исключения как обычные исключения C++:
3. Компилировать нужно с опцией /EHa. В противном случае _set_se_translator не сработает и исключения SEH с помощью catch (...) отлавливаться не будут, а компилятор (Visual Studio 2010) выдаст следующее предупреждение:
warning C4535: calling _set_se_translator() requires /EHa
SEH способен отлавливать все исключения и, что важно на мой взгляд, показывать место возникновения ошибки (ExceptionAddress), а также позволяет продолжить выполнение программы с прерванного места. Но, к сожалению, при возникновении исключения C++ в обработчике (фильтре) SEH документированными способами нельзя узнать тип исключения C++ и получить его текст ошибки. Ниже описан недокументированный способ.
Все исключения C++ в фильтре SEH имеют один и тот же код 0xe06d7363. При этом поле NumberParameters содержит 3 (для 32-разрядных приложений), ExceptionInformation[1] указывает на объект-исключение C++, а ExceptionInformation[2] указывает на структуру _ThrowInfo (ExceptionInformation[0] не интересно). _ThrowInfo относится к так называемым Predefined C++ Types. Для использования предопределенных типов не нужно подключать никаких .h файлов. При редактировании IDE Visual Studio подчеркивает их красным цветом и пишет: Error: identifier "_ThrowInfo" is undefined. Но при этом все успешно компилируется и выполняется :-)
Ниже приведен SEH-фильтр, который способен анализировать исключения C++ и выводить информацию об исключении (what()). Стоит отметить, однако, что хотя эта процедура успешно работает в Visual Studio 2010, но нет никаких гарантий, что в будущем Microsoft не поменяет структуру _ThrowInfo.
В заключении хочу отметить, что ExceptionAddress для исключений C++ не имеет особого смысла, потому что указывает всегда на один и тот же адрес внутри функции __CxxThrowException(), которая выполняется каждый раз, когда вызывается исключение с помощью throw.
===
Перепечатка материалов блога разрешается с обязательной ссылкой на blog.coolsoftware.ru
Одновременно в одной и той же процедуре использовать оба метода обработки исключений (try/catch и SEH) нельзя. Но можно использовать в одной процедуре try/cath, а в другой SEH.
Оба метода обработки исключений не идеальны и имеют свои достоинства и недостатки.
Основной недостаток try/catch заключается в том, что он не позволяет отловить и обработать все ошибки, а также не показывает место возникновения ошибки. Впрочем, в книжке Джеффри Рихтера (Windows для профессионалов. Создание эффективных Win32-пpилoжeний с учетом специфики 64-разрядной версии Windows) приводится способ перехвата структурных исключений:
1. Нужно создать класс CSE для идентификации структурного исключения:
- #include <eh.h>
- #include <stdexcept>
- class CSE
- {
- public:
- static void MapSEToCE(){ _set_se_translator(TranslateSEToCE); }
- operator DWORD() { return (m_er.ExceptionCode);}
- private:
- CSE(PEXCEPTION_POINTERS pep)
- {
- m_er = *pep->ExceptionRecord;
- m_context = *pep->ContextRecord;
- if (m_er.ExceptionCode == 0xe06d7363)
- {
- printf("C++ Exception: Code 0x%x, Address 0x%x\n", m_er.ExceptionCode, m_er.ExceptionAddress);
- }
- else
- {
- printf("SEH Exception: Code 0x%x, Address 0x%x\n", m_er.ExceptionCode, m_er.ExceptionAddress);
- }
- }
- static void _cdecl TranslateSEToCE(UINT deEC,PEXCEPTION_POINTERS pep)
- {
- throw CSE(pep);
- }
- private:
- EXCEPTION_RECORD m_er;
- CONTEXT m_context;
- };
2. Для каждого потока надо вызвать один раз CSE::MapSEToCE(), после чего можно обрабатывать структурные исключения как обычные исключения C++:
- int DivideByZero()
- {
- int x = 100;
- int y = 0;
- return x / y;
- }
- void ThrowException()
- {
- throw std::out_of_range("out_of_range");
- }
- void TestCSE()
- {
- CSE::MapSEToCE();
- try
- {
- //DivideByZero();
- ThrowException();
- }
- catch (CSE se)
- {
- switch(se)
- {
- case EXCEPTION_INT_DIVIDE_BY_ZERO:
- printf("Divide by zero!\n");
- break;
- }
- }
- catch (std::exception & e)
- {
- printf("std::exception: %s\n", e.what());
- }
- }
3. Компилировать нужно с опцией /EHa. В противном случае _set_se_translator не сработает и исключения SEH с помощью catch (...) отлавливаться не будут, а компилятор (Visual Studio 2010) выдаст следующее предупреждение:
warning C4535: calling _set_se_translator() requires /EHa
SEH способен отлавливать все исключения и, что важно на мой взгляд, показывать место возникновения ошибки (ExceptionAddress), а также позволяет продолжить выполнение программы с прерванного места. Но, к сожалению, при возникновении исключения C++ в обработчике (фильтре) SEH документированными способами нельзя узнать тип исключения C++ и получить его текст ошибки. Ниже описан недокументированный способ.
Все исключения C++ в фильтре SEH имеют один и тот же код 0xe06d7363. При этом поле NumberParameters содержит 3 (для 32-разрядных приложений), ExceptionInformation[1] указывает на объект-исключение C++, а ExceptionInformation[2] указывает на структуру _ThrowInfo (ExceptionInformation[0] не интересно). _ThrowInfo относится к так называемым Predefined C++ Types. Для использования предопределенных типов не нужно подключать никаких .h файлов. При редактировании IDE Visual Studio подчеркивает их красным цветом и пишет: Error: identifier "_ThrowInfo" is undefined. Но при этом все успешно компилируется и выполняется :-)
Ниже приведен SEH-фильтр, который способен анализировать исключения C++ и выводить информацию об исключении (what()). Стоит отметить, однако, что хотя эта процедура успешно работает в Visual Studio 2010, но нет никаких гарантий, что в будущем Microsoft не поменяет структуру _ThrowInfo.
- #include <eh.h>
- #include <stdexcept>
- LONG ExceptionFilter(PEXCEPTION_POINTERS pEP, const char * file, const char * function, int line)
- {
- PEXCEPTION_RECORD pER = pEP->ExceptionRecord;
- if (pER->ExceptionCode == 0xe06d7363)
- {
- if (pER->NumberParameters == 3)
- {
- const _ThrowInfo * pThrowInfo = (_ThrowInfo*)pER->ExceptionInformation[2];
- if (pThrowInfo != NULL)
- {
- const _CatchableTypeArray * pTypeArray = pThrowInfo->pCatchableTypeArray;
- if (pTypeArray != NULL)
- {
- const _TypeDescriptor * pExceptionDesc = (_TypeDescriptor*)&typeid(std::exception);
- for (int i = 0; i < pTypeArray->nCatchableTypes; i++)
- {
- const _CatchableType * pCatchableType = pTypeArray->arrayOfCatchableTypes[i];
- if (pCatchableType != NULL && pExceptionDesc == pCatchableType->pType)
- {
- std::exception * e = (std::exception*)pER->ExceptionInformation[1];
- printf("File: %s\nFunction: %s\nLine: %i\nC++ Exception: %s\n", file, function, line, e->what());
- return EXCEPTION_EXECUTE_HANDLER;
- }
- }
- }
- }
- }
- printf("File: %s\nFunction: %s\nLine: %i\nC++ Exception (code 0x%x at 0x%x)\n", file, function, line, pER->ExceptionCode, pER->ExceptionAddress);
- return EXCEPTION_EXECUTE_HANDLER;
- }
- else
- {
- printf("File: %s\nFunction: %s\nLine: %i\nSEH Exception (code 0x%x at 0x%x)\n", file, function, line, pER->ExceptionCode, pER->ExceptionAddress);
- return EXCEPTION_EXECUTE_HANDLER;
- }
- }
- int main(int argc, char* argv[])
- {
- __try
- {
- //DivideByZero();
- ThrowException();
- }
- __except(ExceptionFilter(GetExceptionInformation(), __FILE__, __FUNCTION__, __LINE__))
- {
- //handle exception here
- }
- return 0;
- }
В заключении хочу отметить, что ExceptionAddress для исключений C++ не имеет особого смысла, потому что указывает всегда на один и тот же адрес внутри функции __CxxThrowException(), которая выполняется каждый раз, когда вызывается исключение с помощью throw.
===
Перепечатка материалов блога разрешается с обязательной ссылкой на blog.coolsoftware.ru
Комментариев нет:
Отправка комментария