Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Создание эффективных приложений для Windows Джеффри Рихтер 2004 (Книга).pdf
Скачиваний:
377
Добавлен:
15.06.2014
Размер:
8.44 Mб
Скачать

сможете расширить его функциональность — например, заменив исходную виртуаль ную функцию OnAccessViolation собственной реализацией, более аккуратно обрабаты вающей нехватку памяти Программа Spreadsheet как раз и демонстрирует этот спо соб использования класса CVMArray.

Исключения С++ и структурные исключения

Разработчики часто спрашивают меня, что лучше использовать: SEH или исключения С++. Ответ на этот вопрос Вы найдете здесь.

Для начала позвольте напомнить, что SEH — механизм операционной системы, доступный в любом языке программирования, а исключения С++ поддерживаются только в С++. Создавая приложение на С++, Вы должны использовать средства имен но этого языка, а не SEH. Причина в том, что исключения С++ — часть самого языка и его компилятор автоматически создает код, который вызывает деструкторы объектов и тем самым обеспечивает корректную очистку ресурсов.

Однако Вы должны иметь в виду, что компилятор Microsoft Visual С++ реализует обработку исключений С++ на основе SEH операционной системы. Например, когда Вы создаете С++-блок try, компилятор генерирует SEH-блок _try. С++-блок catch ста новится SEH-фильтром исключений, а код блока catch — кодом SEH-блока __except. По сути, обрабатывая С++-оператор throw, компилятор генерирует вызов Windows функции RaiseException, и значение переменной, указанной в throw, передастся этой функции как дополнительный аргумент.

Сказанное мной поясняет фрагмент кода, показанный ниже. Функция слева ис пользует средства обработки исключений С++, а функция справа демонстрирует, как компилятор С++ создает соответствующие им SEH-эквиваленты.

void ChunkyFunky()

{

try

{

// тело блока try

...

throw 5;

}

catch (int x)

{

// тело блока catch

...

}

...

}

void ChunkyFunky()

{

__try

{

// тело блока try

...

RaiseException(Code=OxE06D7363, Flag=EXCEPTION_NONCONTINUABLE,Args=5);

}

__except ((ArgType == Integer) ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SFARCH)

{

// тело блока catch

...

}

}

Обратите внимание на несколько интересных особенностей этого кода. Во-пер вых, RaiseExeption вызывается с кодом исключения 0xE06D7363Это код программ ного исключения, выбранный разработчиками Visual C++ на случай выталкивания (throwing) исключений С++ Вы можете сами в этом убедиться, открыв диалоговое окно Exceptions отладчика и прокрутив его список до конца, как на следующей ил люстрации.

Заметьте также, что при выталкивании исключения С++ всегда используется флаг EXCEPTION_NONCONTINUABLE. Исключения С++ не разрешают возобновлять выпол нение программы, и возврат EXCEPTION_CONTINUE_EXECUTION фильтром, диагно стирующим исключения С++, был бы ошибкой. Если Вы посмотрите на фильтр _except в функции справа, то увидите, что он возвращает только EXCEPTION_EXECUTE_HAND LER или EXCEPTION_CONTINUE_SEARCH.

Остальные аргументы RaiseException используются механизмом, который факти чески выталкивает (throw) указанную переменную. Точный механизм того, как дан ные из переменной передаются RaiseExceplion, не задокументирован, но догадаться о его реализации в компиляторе нс так уж трудно.

И последнее, о чем хотелось бы сказать Назначение фильтра __except — сравни вать тип throw-переменных с типом переменной, используемой в С++-операторе catch. Если их типы совпадают, фильтр возвращает EXCEPTION_EXECUTE_HANDLER, вызы вая выполнение операторов в блоке catch (_except) А если они нс совпадают, фильтр возвращает ЕХСЕРТION_СОМTINUE_SЕАRСНдля проверки "вышестоящих" подереву вызовов фильтров catch.

NOTE:

Так как исключения С++ реализуются через SEH, оба эти механизма можно ис пользовать в одной программе. Например, я предпочитаю передавать физичес кую память при исключениях, вызываемых нарушениями доступа Хотя С++ во обще не поддерживает этот тип обработки исключений (с возобновлением вы полнения), он позволяет применять SEH в тсх местах программы, где это нуж но, и Ваш фильтр _except может возвращать EXCEPTION_CONTINUE_EXECU TION Ну а в остальных частях исходного кода, где возобновление выполне ния после обработки исключения нс требуется, я пользуюсь механизмом об работки исключений, предлагаемым С++.

Перехват структурных исключений в С++

Обычно механизм обработки исключений в С++ не позволяет приложению восста новиться после таких серьезных исключений, как нарушение доступа или деление на нуль. Однако Microsoft добавила поддержку соответствующей функциональности в свой компилятор. Так, следующий код предотвратит аварийное завершение процесса.

void main()

{

try

{

* (PBYTE) 0 = 0; // нарушение доступа

}

catch ( ..)

{

// этот код обрабатывает исключения, связанные с нарушением доступа

}

// процесс завершается корректно

}

И это прекрасно, так как приложение может корректно справляться с серьезны ми исключениями. Но было бы еще лучше, если бы блок catch как-то различал коды исключений — чтобы мы могли писать, например, такой исходный код

void Functastic()

{

try

{

* (PBYTE) 0 = 0; // нарушение доступа

int x = 0;

x = 5 / x; // деление на нуль

}

catch (StructuredFxception)

{

switch (StructuredExceptionCode)

{

case EXCEPTION_ACCESS_VIOLATION:

// здесь обрабатывается нарушение доступа break;

case EXCEPTION_INT_OIVIDE_BY_ZERO:

// здесь обрабатывается деление на нуль break;

default:

//другие исключения мы не обрабатываем throw;

//может, какойнибудь другой блок catch

//обработает это исключение

break;

// никогда не выполняется

}

}

}

Так вот, хочу Вас порадовать. В Visual С++ теперь возможно и такое. От Вас потре буется создать С++-класс, используемый специально для идентификации структурных исключений. Например:

#include <eh.h> // для доступа к _set_se_translator

...

class CSE

{

public:

// вызовите эту функцию для каждого потока

static void MapSEtoCE() { _set_se_translator(TranslateSEtoCE); }

operator DWORD() { return(m_er.ExcepUonCude); }

privale:

CSE(PEXCEPTION_POINTERS pep)

{

m_er = *pep->ExceptionRecord; m_context = *pep->ContextRecord;

}

static void _cdecl TranslateSEtoCE(UINT dwEC, PEXCEPTION_POINTERS pep)

{

throw CSE(pep);

}

private:

EXCEPTION_RECORD m_er;

// машинно-независимая информация ofi исключении

CONTEXT m_context;

// машинно-зависимая информация об исключении

};

Внутри входных функций потоков вызывайте статическую функцию-член Map SEtoCE. В свою очередь она обращается к библиотечной С-функции _set_sefranslator, передавая ей адрес функции TranslateSEtoCE класса CSE. Вызов _set_se_translator сооб щает С++, что при возбуждении структурных исключений Вы хотите вызывать Trans lateSEtoCE. Эта функция вызывает конструктор CSE-объектя и инициализирует два элемента данных машинно-зависимой и машинно-независимой информацией об исключении. Созданный таким образом CSE-объскт может быть вытолкнут ак же, как и любая другая переменная. И теперь Ваш С++-код способен обрабатывать структур ные исключения, захватывая (catching) переменную этого типа.

Вот пример захвата такого С++-объекта.

void Functastic()

{

CSE::MapSEtoCE(); // должна быть вызвана до возникновения исключений

try

{

* (PBYTE) 0 = 0; // нарушение доступа

int x = 0;

x = 5 / x; // деление на нуль

}

catch (CSE se)

{

switch (se)

{

// вызывает функцию-член оператора DWORD()

case EXCEPTION_ACCESS_VIOLATION

// здесь обрабатывается исключение вызванное нарушением доступа

break;

case EXCEPTION_INT_DIVIDE_BY_ZERO

// здесь обрабатывается исключение, вызванное делением на нуль break;

default:

//другие исключения мы не обрабатываем throw;

//может, какойнибудь другой блок catch

//обработает это исключение

break;

//никогда не выполняется

}