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

анализировать ассемблерный текст, сгенерированный для исходного кода. Вывод: будьте крайне осторожны, возвращая EXCEPTION_CONTINUE_EXECUTION из фильтра исключений.

EXCEPTION_CONTINUE_EXECUTION всегда срабатывает лишь в одной ситуации: при передаче памяти зарезервированному региону. О том, как зарезервировать боль шую область адресного пространства, а потом передавать ей память лишь по мере необходимости, я рассказывал в главе 15 Соответствующий алгоритм демонстриро вала программа-пример VMAlloc. На основе механизма SEH то же самое можно было бы реализовать гораздо эффективнее (и не пришлось бы все время вызывать функ цию

VirtualAtloc).

В главе l6 мы говорили о стеках потоков, В частности, я показал, как система ре зервирует для стека потока регион адресного пространства размером 1 Мб и как она автоматически передает ему новую память по мере разрастании стека. С этой целью система создает SEH-фрейм. Когда поток пытается задействовать несуществующую часть стека, генерируется исключение. Системный фильтр определяет, что исключе ние возникло из-за попытки обращения к адресному пространству, зарезервирован ному под стек, вызывает функцию VirtualAlloc для передачи дополнительной памяти стеку потока и возвращает EXCEPTION_CONTINUE_EXECUTION. После этого машин ная команда, пытавшаяся обратиться к несуществующей части стека, благополучно выполняется, и поток продолжает свою работу.

Механизмы использования виртуальной памяти в сочетании со структурной об работкой исключений позволяют создавать невероятно «шустрые* приложения Про грамма-пример Spreadsheet в следующей главе продемонстрирует, как на основе SEH

эффективно реализовать управление памятью в электронной таблице. Этот код вы полняется чрезвычайно быстро.

EXCEPTION_CONTINUE_SEARCH

Приведенные до сих пор примеры были ну просто детскими Чтобы немного встрях нуться, добавим вызов функции:

char g_szBuffer[100];

void FunclinRoosevelt2()

{

char *pchBuffer = NULL;

__try

{

FuncAtude2(pchBuffer);

}

__except (OilFilter2(&pchBuffer))

{

MessageBox(...);

}

}

void FuncAtude2(char *sz)

{

*sz = 0;

}

LONG OilFilter2(char **ppchBuffer)

{

if (*ppchBuffer == NULL)

{

*ppchBuffer = g_szBuffer; return(EXCEPTION_CONTINUE_EXECUTION);

}

return(EXCEPTION_EXECUTE HANDLER);

}

При выполнении FunclinRoosevelt2 вызывается FuncAtude2, которой передается NULL. Последняя приводит к исключению. Как и раньше, система проверяет выраже ние в фильтре исключений, связанном с последним исполняемым блоком try. В на шем примере это блок try в FunclinRoosevelt2, поэтому для оценки выражения в филь тре исключений система вызываег OilFilter2 (хотя исключение возникло в FuncAtude2).

Замесим ситуацию еще круче, добавив другой блок try-except

char g_szBuffer[100];

void FunclinHoosevelt3()

{

char *pchBuffer = NULL;

__try

{

FuncAtude3(pchBuffer);

}

__except (OilFilter3(&pch8uffer))

{

Message8ox(...);

}

}

void FuncAtude3(char *sz)

{

__try

{

*sz = 0;

}

__except (EXCEPTION_CONTINUE_SEARCH)

{

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

...

}

}

LONG OilFilter3(Utar **ppchBuffer)

{

if (*ppchBuffer == NULL)

{

*ppchBuffer = g_szBuffer;

return(EXCEPTION CONTINUE_EXECUTION);

}

return(EXCEPTIQN_EXECUTE_HANDLER);

}

Теперь, когда FuncAtude3 пытается занести 0 по адресу NULL, по-прежнему возбуж дается исключение, но в работу вступает фильтр исключений из FuncAtude3. Значе ние этого очень простого фильтра — EXCEPTIUN_CONTINUE_SEARCH. Данный иден тификатор указывает системе перейти к предыдущему блоку tty, которому соответ ствует блок except, и обработать его фильтр.

Так как фильтр в FuncAtude3 дает EXCEPTION_CONTINUE_SEARCH, система пере ходит к предыдущему блоку try (в функции FunclinRoOsevelt3) и вычисляет eго фильтр OilFilter3. Обнаружив, что значение pchBuffer равно NULL, OilFilter3 меняет его так, чтобы оно указывало на глобальный буфер, и сообщает системе возобновить выпол нение с инструкции, вызвавшей исключение Это позволяет выполнить код в блоке try функции FuncAtude3, но, увы, локальная переменная sz в этой функции не измене на, и возникает новое исключение Опять бесконечный цикл!

Заметьте, я сказал, что система переходит к последнему исполнявшемуся блоку try, которому соответствует блок except, и проверяет его фильтр Это значит, что система пропускает при просмотре цепочки блоков любые блоки try, которым соответствуют блоки finally (а не except). Причина этого очевидна, в блоках finally нет фильтров ис ключений, а потому и проверять в них нечего. Если бы в последнем примере Func Atude3

содержала вместо except, система начала бы проверять фильтры исключений с OilFilter3 в FunclinRroosevelt3

Дополнительную информацию об EXCEPTION_CONTINUE_SEARCH см. в главе 25.

Функция GetExceptionCode

Часто фильтр исключений должен проанализировать ситуацию, прежде чем опреде лить, какое значение ему вернуть. Например, Ваш обработчик может знать, что делать при делении на нуль, по не знать, как обработать нарушение доступа к памяти Имен но поэтому фильтр отdечает за анализ ситуации и возврат соответствующего значения Этот фрагмент иллюстрирует метод, позволяющий определять тип исключения:

__try

{

x = 0;

У = 4 / x;

}

__except ((GetExceptionCode() == EXCEPTION_INT_DIVIDE_BY_ZERO) ? EXCEPTlON_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH)

{

// обработка деления иа нуль

}

Встраиваемая функция GetExceptionCode возвращает идентификатор типа исклю чения.

DWORD GetExceptionCode();

Ниже приведен список всех предопределенных идентификаторов исключений с пояснением их смысла (информация взята из докуметации Platform SDK) Эти иден тификаторы содержатся в заголовочном файле WinBase.h. Я сгруппировал исключе ния по категориям.

Исключения, связанные с памятью

EXCEPTION_ACCESS_VIOLATION Поток пьтался считать или записать по виртуальному адресу, не имея на то необходимых прав. Это самое распрост раненное исключение.

EXCEPTION_DATATYPE_MISALIGNMENT Поток пытался считать или запи сать невыровненные данные на оборудовании, которое не поддерживает ав томатическое выравнивание. Например, 16-битные значения должны быть вы ровнены по двухбайтовым границам, 32-битные — по четырехбайтовым и т. д,

EXCEPTION_ARRAY_ROUNDS_EXCEEDED Поток пытался обратиться к эле менту массива, индекс которого выходит за границы массива; при этом обо рудование должно поддерживать такой тип контроля.

EXCEPTION_INPAGE_ERROR Ошибку страницы нельзя обработать, так как файловая система или драйвер устройства сообщили об ошибке чтения.

EXCEPTION_GUARD_PAGE Поток пытался обратиться к странице памяти с атрибутом защиты FAGE_GUARD. Страница становится доступной, и генериру ется данное исключение

EXCEPTION_STACK_OVERFLOW Стек, отведенный потоку, исчерпан.

EXCEPTION_ILLEGAL_INSTRUCTION Поток выполнил недопустимую инст рукцию Это исключение определяется архитектурой процессора; можно ли перехватить выполнение неверной инструкции, зависит от типа процессора.

EXCEPTION_PRIV_INSTRUCTION Поток пытался выполнить инструкцию, не допустимую в данном режиме работы процессора.

Исключения, связанные с обработкой самих исключений

EXCEPTION_INVALID_DISPOSITION Фильтр исключений вернул значение,

огличное от EXCEPTION_EXECUTE_HANDLER, EXCEPTION_CONTINUE_SEARCH или EXCEPTION_CONTINUE_FXECUTION.

EXCEPTION_NONCONTINUABLEEXCEPTION Фильтр исключений вернул

EXCEPTION_CONTINUE_EXECUTION в ответ па невозобновляемое исключение

(noncontinuable exception).

Исключения, связанные с отладкой

EXCEPTION_BREAKPOINT Встретилась точка прерывания (останова). EXCEPTION_SINGLE_STEP Трассировочная ловушка или другой механизм пошагового исполнения команд подал сигнал о выполнении одной команды. EXCEPTION_INVALID_HANDLE В функцию передан недопустимый описатель.

Исключения, связанные с операциями над целыми числами

EXCEPTION_INT_DIVIDE_BY_ZERO Поток пытался поделить число целого типа на делитель того же типа, равный 0

EXCEPTION_INT_OVERFLOW Операция над целыми числами вызвала пере ног старшего разряда результата.

Исключения, связанные с операциями над вещественными числами

EXCEPTION_FLT_DENORMAL_OPERAND Один из операндов в операции над числами с плавающей точкой (вещественного типа) не нормализован. Ненор мализованными являются значения, слишком малые для стандартного пред ставления числа с плавающей точкой.

EXCEPTION_FLT_DIVIDE_BY_ZERO Поток пытался поделить число веще ственного типа на делитель того же типа, равный 0. EXCEPTION_FLT_INEXACT_RESULT Результат операции над числами с пла вающей точкой нельзя точно представить я виде десятичной дроби

EXCEPTION_FLT_INVALID_OPERATION Любое другое исключение, относя щееся к операциям над числами с плавающей точкой и нс включенное в этот список

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

EXCEPTION_FLT_STACK_CHECK Переполнение стека или выход за его ниж нюю границу в результате выполнения операции над числами с плавающей точкой.

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

Встраиваемую функцию GetExceptionCode можно вызвать только из фильтра ис ключений (между скобками, которые следуют за _except) или из обработчика исклю чений. Скажем, такой код вполне допустим:

__try

{

У = 0;

x = 4 / у;

}

__except

{

{(GetExceptionCode() == EXCEPTION_ACCESS_VIOLATION) || (GetExceptionCode() == EXCEPTION_INT_DIVIDE_BY_ZERO)) ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEAHCH)

{

switch (GetExceptionCode())

{

case EXCEPTION_ACCESS_VIOLATION:

// обработка нарушения доступа к памяти

...

break;

case EXCEPTION_INT_DIVIDE_BY_ZERO:

// обработка деления целого числа на нуль

...

break;

}

}

Однако GetExceptionCode нельзя вызывать из функции фильтра исключений. Ком пилятор помогает вылавливать такие ошибки и обязательно сообщит о таковой, если Вы попытаетесь скомпилировать, например, следующий код:

__try

{

У = 0;

x = 4 / у;

}

__except (CoffeeFilter())

{

// обрабогка исключения

...

}

LONG CoffeeFilter(void)

{

// ошибка при компиляции: недопустимый вызов

GetExceptionCode

return((GetExceptionCode() == EXCFPTION_ACCESS_VIOLATION) ? EXCEPTION_EXECUTE_HANDLER :

EXCEPTION_CONTINUE_SEARCH);

}

 

Нужного

эффекта можно добиться, переписав код так:

__try

 

{

 

y

= 0;

x

= 4 / у;

}

 

__except (CoffeeFi]ter(GetExceptionCode()))

{

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

...

}

LONG CoffeeFilter(DWORD dwExceptionGode)

{

return((dwExceptionCode == EXCEPTION_ACCESS_VIOLATION) ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH);

}

Коды исключений формируются по тем же правилам, что и коды ошибок, опре деленные в файле WinError.h. Каждое значение типа UWORD разбивается на поля, как показано в таблице 24-1.

Биты

31-30

29

28

27-16

15-0

 

 

 

 

 

 

Содержимое

Код степени "тяжести"

Кем определен —

Зарезервирован

Код подсистемы

Код исключения

 

(severity)

Microsoft или

 

(facility code)

 

 

 

пользователем