Некоторые полезные примеры
Допустим, Вы хотите создать отказоустойчивое приложение, которое должно рабо тать 24 часа в сутки и 7 дней в неделю. В наше время, когда программное обеспече ние настолько усложнилось и подвержено влиянию множества непредсказуемых фак торов, мне кажется, что без SEH просто нельзя создать действительно надежное при ложение. Возьмем элементарный пример, функцию strcpy из библиотеки С:
char* strcpy(char* strDestination, const char* strSource);
Крошечная, давно известная и очень простая функция, да? Разве она может выз вать завершение процесса? Ну, если в каком-нибудь из параметров будет передан NULL (или любой другой недопустимый адрес), strcpy приведет к нарушению доступа, и весь процесс будет закрыт.
Создание абсолютно надежной функции strcpy возможно только при использова нии SEH
char* RobustStrCpy(char* strDestination, const char* strSource)
{
__try
{
strcpy(strDestination, strSource);
}
except (EXCEPTION_EXECUTE_HANDLER)
{
// здесь ничего на делаем
}
return(strDestination);
}
Все, что делает эта функция, — помещает вызов strcpy в SEH-фрейм. Если вызов strcfiy приходит успешно, RobustStrCpy просто возвращает управление. Если же strcpy генерирует нарушение доступа, фильтр исключений возвращает значение EXCEP TION_EXECIITE_HANDLER, которое заставляет поток выполнить код обработчика. В функции RobublStrCpy обработчик не делает ровным счетом ничего, и опягь Robust StrCpy просто возвращает управление. Но она никогда не приведет к аварийному за вершению процесса1
Рассмотрим другой пример. Вот функция, которая сообщает число отделенных пробелами лексем в строке.
int RobustHowManyToken(const char* str)
{
int nHowManyTokens = -1,
// значение, равное -1, сообщает о неудаче
char* strTemp = NULL;
// предполагаем худшее
__try
{
// создаем временный буфер
strTemp = (char*) malloc(strlen(str) + 1);
// копируем исходную строку во временный буфер
strcpy(strTemp, str);
// получаем первую лексему
char* pszToken = strtok(strTemp, " ");
// перечисляем все лексемы
for (; pszToken != NULL; pszToken = strtok(NULL, " ")) nHowManyTokens++;
nHowManyTokens++; // добавляем 1, так как мы начали с -1
}
__except (EXCEPTION_EXECUTE_HANDLER}
{
// здесь ничего не делаем
}
// удаляем временный буфер (гарантированная операция)
free(strTemp);
return(nHowManyTokens);
}
Эта функция создает временный буфер и копирует в нсго строку. Затем, вызывая библиотечную функцию strtok, она разбирает строку на отдельные лексемы. Времен ный буфер необходим из-за того, что strtok модифицирует анализируемую строку.
Благодаря SEH эта обманчиво простая функция справляется с любыми неожидан ностями. Давайте посмотрим, как она работает в некоторых ситуациях
Во-первых, если ей передастся NULL (или любой другой недопустимый адрес), переменная nHowManyTokens сохраняет исходное значение -1. Вызов strlen внутри блока try приводит к нарушению доступа. Тогда управление передается фильтру ис ключений, а от него — блоку except, который ничего не делает. После блока except вызывается free, чтобы удалить временный буфер в памяти. Однако он не был создан, и в данной ситуации мы вызываем/гее с передачей ей NULL Стандарт ANSl С допус кает вызов/me с передачей NULL, в каковом случае эта функция просто возвращает управление, так что ошибки здесь нет. В итоге RobustHowManyToken возвращает зна чение -1, сообщая о неудаче, и аварийного завершения процесса нс происходит,
Во-вторых, если функция получает корректный адрес, но вызов malloc (внутри блока try) заканчивается неудачно и дает NULL, то обращение к strcpy опять приво дит к нарушению доступа. Вновь активизируется фильтр исключений, выполняется блок ехсерг (который ничего не делает), вызывается free с передачей NULL (из-за чего она тоже ничего не делает), и RobustHowManyToken возвращает -1, сообщая о неуда че. Аварийного завершения процесса не происходит.
Наконец, допустим, что функции передан корректный адрес и вызов malloc про шел успешно. Тогда преуспеет и остальной код, а в переменную nHowManyTokens бу дет записано число лексем в строке, В этом случае выражение в фильтре исключений (в конце блока try) не оценивается, код в блоке except не выполняется, временный буфер нормально удаляется, и nHowManyTokens сообщает количество лексем в строке.
Функция RobustHowManyToken демонстрирует, как обеспечить гарантированную очистку ресурса, не прибегая к try-finally. Также гарантируется выполнение любого кода, расположенного за обработчиком исключения (если, конечно, функция не воз вращает управление из блока try, но таких вещей Вы должны избегать)
Атеперь рассмотрим последний, особенно полезный пример использования SEH. Вот функция, которая дублирует блок памяти:
PBYTE RobustMemDup(PBYTE pbSrc, size_t cb)
{
PBYTE pbDup = NULL;
// заранее предполагаем неудачу
__try
{
// создаем буфер для дублированного блока памяти
pbDup = (PBYTE) malloc(cb);
memcpy(pbDup, pbSrc, cb);
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
free(pbDup);
pbDup = NULL;
}
return(pbDup);
}
Эта функция создает буфер в памяти и копирует в него байты из исходного бло ка. Затем она возвращает адрес этого дубликата (или NULL, если вызов закончился неудачно). Предполагается, что буфер освобождается вызывающей функцией — ког да необходимость в нем отпадает Это первый пример, где в блоке except понадобится какой-то код. Давайте проанализируем работу этой функции в различных ситуациях.
• Если в пираметр pbSrc передается некорректный адрес или если вызов malloc завершается неудачно (и дает NULL), memcpy возбуждает нарушение доступа А это приводит к выполнению фильтра, который передает управление блоку except. Код в блоке except освобождает буфер памяти и устанавливает pbDup в NULL, чтобы вызвавший эту функцию поток узнал о cc неудачном завершении. (Не забудьте, что стандарт ANSI С допускает передачу NULL функции free.)
• Если в параметрер pbSrc передается корректный адрес и вызов malloc проходит успешно, функция возвращает адрес только что созданного блока памяти