Здравствуйте уважаемые подписчики, сегодня в номере:
От Автора
Статья "Необычное применение SEH"
От Автора
Сегодня в номере последняя статья цикла о структурной обработке исключений.
Если у Вас есть вопросы по этому материалу или конструктивная критика статей
– пишите. Ссылка на мой адрес вверху письма. Для связи можно также использовать
форму на сайте.
В последнее время мне все больше и больше приходит писем с просьбой рассказать
о многопоточности. Это обширная тема с множеством нюансов. Одному сложно охватить
такой объем работ. Если у Вас есть материалы, вопросы и пожелания огромная просьба
связаться со мной. Если вы решите опубликовать готовую статью – милости прошу.
Все авторские права остаются за Вами.
Внимание! Компания CycloneSoft набирает программистов на постоянную
работу в г. Ростове-на-Дону. Работа в офисе. Обязательное требование – отличное
знание языка C++ и искренний интерес к профессии. Резюме направляйте по адресу
resume@raurat.ru.
Эта статья является заключительной в цикле статей о структурной обработке исключений.
В предыдущих статьях подробно разбиралось, что это такое
и как их использовать. Примеры носили академический характер и не могли рассматриваться
как законченные решения. Также там говорилось о том, что возобновлять выполнение
программы с прерванного места не рекомендуется. Тому есть масса причин. Тем
не менее, в данной статье будет описан именно этот случай.
Приведенный материал можно рассматривать как оригинальный трюк. Или для демонстрации
своей крутости. Использовать подобный стиль настоятельно не рекомендуется, если
есть альтернативные решения.
Описание проблемы
Предположим, что у Вас есть функция с определенным набором параметров. Теперь
предположим, что иногда функция требует дополнительных параметров. Если вызывающий
код и код в функции в нашей компетенции, мы легко можем добавить недостающие
параметры. При этом могут потребоваться значительные модификации кода, т.к.
надо будет модифицировать все точки вызова.
Гораздо интересней ситуация, когда функция используется для обратного вызова
из сторонней библиотеки или API. Такая функция может требовать дополнительного
набора параметров, которые не предусмотрены архитектурой. Обычно при проектировании
таких вещей предусматривается как минимум один параметр для Вашей функции (контекст).
Как правило, это указатель на тип void. Т.е. через него можно передать структуру
с любым количеством параметров. К сожалению, не все проектировщики такие умные.
Примером такого промаха может служить функция WinAPI: SetWindowsHookEx. Она
в качестве параметра может принимать указатель на функцию обратного вызова,
но не позволяет передавать в нее дополнительные параметры. А если очень хочется
передать в нее дополнительные данные?
Решение
Что же можно сделать, если мы хотим передать в функцию дополнительные параметры,
но не хотим или не можем модифицировать код? Я знаю несколько решений:
Использовать глобальные переменные. Надо просто установить в них нужные
значения, перед тем как такая функция может быть вызвана. Такие переменные
доступны отовсюду. Это наиболее прямолинейный способ, но с множеством недостатков.
Его нельзя использовать в многопоточной среде, при рекурсии, если используются
множественные отложенные обратные вызовы и т.п.
Можно усовершенствовать предыдущий способ и использовать TLS. Это решит
проблемы многопоточности. Остальные недостатки останутся на месте. И еще добавится
необходимость инициализации TLS.
Ну и на сладкое. Можно использовать SEH (Windows structured exception handling)
для предоставления дополнительных параметров в функцию. Безусловно, метод
не лишен недостатков. О них было сказано в начале статьи.
Давайте взглянем на следующий пример:
const DWORD EXCEPTION_FETCHPARAM_B = 0x800A1111L;
void Nested(int iParam){// Сдесь работаем и обнаруживаем, что нам нужен еще один параметрint b;
//Получим его!
ULONG_PTR nExceptionParam = (ULONG_PTR) &b;
RaiseException(EXCEPTION_FETCHPARAM_B, 0, 1, &nExceptionParam);
// Теперь в b находится нужное нам значение. Продолжаем работу.}void Intermediate(int iParam){// Вызываем вложенную функцию
Nested(iParam);
}void Top(){// В этом месте у нас есть все параметры для функции Nested
EXCEPTION_POINTERS* pException;
__try
{
Intermediate(12);
}
__except
(
pException = GetExceptionInformation(),
EXCEPTION_FETCHPARAM_B == pException->ExceptionRecord->ExceptionCode ?
(
ASSERT(1 == pException->ExceptionRecord->NumberParameters),
ASSERT(pException->ExceptionRecord->ExceptionInformation[0]),
*((int*) pException->ExceptionRecord->ExceptionInformation[0]) = 14,
EXCEPTION_CONTINUE_EXECUTION
) :
EXCEPTION_CONTINUE_SEARCH
){// Сюда мы не попадаем, т.к. фильтр никогда не возвращает// EXCEPTION_EXECUTE_HANDLER}}
Функция Top знает о параметрах, которые надо передать в функцию Nested. Первый
– это значение 12. Оно передается в виде обычного параметра. Второе значение
– 14. Оно передается при помощи исключений. Это происходит очень просто. Когда
функция Nested хочет получить дополнительный параметр – она возбуждает программное
исключение. В качестве параметра передается адрес дополнительного параметра.
Вышележащая функция Top перехватывает исключение и проверяет, чтобы код исключения
соответствовал случаю получения дополнительного параметра. Если это так – мы
передаем значение 14 через структуру контекста исключения. В этом случае фильтр
возвращает код EXCEPTION_CONTINUE_EXECUTION. Это заставляет систему сделать
перезапуск приложения с команды, следующей за RaiseException.
Обратите внимание на синтаксис фильтра исключения в операторе __except. Там
используется оператор «запятая». С помощью него можно записать такое сложное
выражение без написания функции-фильтра. Выражения, которые идут через запятую,
выполняются слева направо. Результатом является значение последнего выражения.
Если исключение возникло по другой причине (код не равен EXCEPTION_FETCHPARAM_B),
то фильтр возвращает значение EXCEPTION_CONTINUE_SEARCH. Это заставляет систему
искать обработчики исключения на более высоких уровнях.
Пример может быть легко расширен для того, чтобы передавать несколько параметров.
Для этого можно написать полноценную функцию-фильтр, которая будет проверять
код исключения и в зависимости от него передавать нужный параметр.
Быстродействие
Общепринято считать, что обработка исключений весьма «тяжелая» и медленная
операция. Это не совсем так. Максимальные потери возникают, если требуется выполнять
глобальную раскрутку. Т.е. фильтр исключения должен вернуть EXCEPTION_EXECUTE_HANDLER.
При этом выполняются все блоки __finaly. В нашем случае таких сложностей не
возникает, т.к. программа продолжает свое выполнение с прерванного места. Безусловно,
RaiseException не корректно сравнивать с простым вызовом. Описанный способ все-таки
медленнее прямой передачи параметров.
Обратите внимание, что когда программа работает под отладчиком,
она может выводить в отладочную консоль сообщения “First-chance exception….”.
Если таких строчек много – процесс работы может существенно замедлиться. Это
не означает, что в «боевом» варианте все будет работать так же медленно.
Заключение
Данный способ может использоваться для получения информации в любой точке программы
с более высоких уровней.
Обратите внимание. Функцию Nested можно вызывать только из потока,
который имеет обработчик программного исключения. В противном случае ее использование
вызовет крах.
Иногда в программах все возникающие исключения игнорируются. Это придает глючным
программам стабильность. Опасное заблуждение – ошибки они и в Африке ошибки.
Если при этом вы будете использовать описанный трюк – готовьтесь к неожиданностям.
Еще раз обращаю внимание, что данный подход можно отнести к разряду трюков,
которые надо использовать с большой осторожностью.