воскресенье, 24 марта 2013 г.

C++: обработка исключений

В 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 для идентификации структурного исключения:

  1. #include <eh.h>
  2. #include <stdexcept>
  3.  
  4. class CSE
  5. {
  6. public:
  7.     static void MapSEToCE(){ _set_se_translator(TranslateSEToCE); }
  8.     operator DWORD() { return (m_er.ExceptionCode);}
  9.  
  10. private:
  11.     CSE(PEXCEPTION_POINTERS pep)
  12.     {
  13.         m_er = *pep->ExceptionRecord;
  14.         m_context = *pep->ContextRecord;
  15.         if (m_er.ExceptionCode == 0xe06d7363)
  16.         {
  17.             printf("C++ Exception: Code 0x%x, Address 0x%x\n", m_er.ExceptionCode, m_er.ExceptionAddress);
  18.         }
  19.         else
  20.         {
  21.             printf("SEH Exception: Code 0x%x, Address 0x%x\n", m_er.ExceptionCode, m_er.ExceptionAddress);
  22.         }
  23.     }
  24.     static void _cdecl TranslateSEToCE(UINT deEC,PEXCEPTION_POINTERS pep)
  25.     {
  26.         throw CSE(pep);
  27.     }
  28.  
  29. private:
  30.     EXCEPTION_RECORD m_er;
  31.     CONTEXT m_context;
  32. };

2. Для каждого потока надо вызвать один раз CSE::MapSEToCE(), после чего можно обрабатывать структурные исключения как обычные исключения C++:

  1. int DivideByZero()
  2. {
  3.     int x = 100;
  4.     int y = 0;
  5.     return x / y;
  6. }
  7.  
  8. void ThrowException()
  9. {
  10.     throw std::out_of_range("out_of_range");
  11. }
  12.  
  13. void TestCSE()
  14. {
  15.  
  16.     CSE::MapSEToCE();
  17.     try
  18.     {
  19.         //DivideByZero();
  20.         ThrowException();
  21.     }
  22.     catch (CSE se)
  23.     {
  24.         switch(se)
  25.         {
  26.             case EXCEPTION_INT_DIVIDE_BY_ZERO:
  27.                 printf("Divide by zero!\n");
  28.                 break;
  29.         }
  30.     }
  31.     catch (std::exception & e)
  32.     {
  33.         printf("std::exception: %s\n", e.what());
  34.     }
  35. }

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.

  1. #include <eh.h>
  2. #include <stdexcept>
  3.  
  4. LONG ExceptionFilter(PEXCEPTION_POINTERS pEP, const char * file, const char * function, int line)
  5. {
  6.     PEXCEPTION_RECORD pER = pEP->ExceptionRecord;
  7.     if (pER->ExceptionCode == 0xe06d7363)
  8.     {
  9.         if (pER->NumberParameters == 3)
  10.         {
  11.             const _ThrowInfo * pThrowInfo = (_ThrowInfo*)pER->ExceptionInformation[2];
  12.             if (pThrowInfo != NULL)
  13.             {
  14.                 const _CatchableTypeArray * pTypeArray = pThrowInfo->pCatchableTypeArray;
  15.                 if (pTypeArray != NULL)
  16.                 {
  17.                     const _TypeDescriptor * pExceptionDesc = (_TypeDescriptor*)&typeid(std::exception);  
  18.                     for (int i = 0; i < pTypeArray->nCatchableTypes; i++)
  19.                     {
  20.                         const _CatchableType * pCatchableType = pTypeArray->arrayOfCatchableTypes[i];
  21.                         if (pCatchableType != NULL && pExceptionDesc == pCatchableType->pType)
  22.                         {
  23.                             std::exception * e = (std::exception*)pER->ExceptionInformation[1];
  24.                             printf("File: %s\nFunction: %s\nLine: %i\nC++ Exception: %s\n", file, function, line, e->what());
  25.                             return EXCEPTION_EXECUTE_HANDLER;
  26.                         }
  27.                     }
  28.                 }
  29.             }
  30.         }
  31.         printf("File: %s\nFunction: %s\nLine: %i\nC++ Exception (code 0x%x at 0x%x)\n", file, function, line, pER->ExceptionCode, pER->ExceptionAddress);
  32.         return EXCEPTION_EXECUTE_HANDLER;
  33.     }
  34.     else
  35.     {
  36.         printf("File: %s\nFunction: %s\nLine: %i\nSEH Exception (code 0x%x at 0x%x)\n", file, function, line, pER->ExceptionCode, pER->ExceptionAddress);
  37.         return EXCEPTION_EXECUTE_HANDLER;
  38.     }
  39. }
  40.  
  41. int main(int argc, char* argv[])
  42. {
  43.     __try
  44.     {
  45.         //DivideByZero();
  46.         ThrowException();
  47.     }
  48.     __except(ExceptionFilter(GetExceptionInformation(), __FILE__, __FUNCTION__, __LINE__))
  49.     {
  50.         //handle exception here
  51.     }
  52.  
  53.     return 0;
  54. }

В заключении хочу отметить, что ExceptionAddress для исключений C++ не имеет особого смысла, потому что указывает всегда на один и тот же адрес внутри функции __CxxThrowException(), которая выполняется каждый раз, когда вызывается исключение с помощью throw.

===
Перепечатка материалов блога разрешается с обязательной ссылкой на blog.coolsoftware.ru

Комментариев нет:

Отправить комментарий