    СТРУКТУРНАЯ ОБРАБОТКА ИСКЛЮЧЕНИЙ 

    глава 16 

    Закроем глаза и помечтаем, какие бы программы мы писали, если бы сбои в 
них были невозможны! Представляете: памяти навалом, неверных указателей никто 
не передает, нужные файлы всегда на месте. Не программирование, а праздник, 
да? А код программ? Насколько он стал бы проще и понятнее! Без всех этих ifvi goto. 
    И если Вы давно мечтали о такой среде программирования, Вы сразу же 
оцените структурную обработку исключений (structured exception handling, SEH). 
Преимущество SEH в том, что при написании кода можно сосредоточиться на 
решении своей задачи. Если при выполнении программы возникнут неприятности, 
система сама обнаружит их и сообщит Вам. 
    Хотя полностью игнорировать ошибки в программе при использовании SEH 
нельзя, она все же позволяёт"6тделить основную работу от рутинной обработки 
ошибок, к которой можно вернуться позже. 
    Главное, почему Microsoft ввела в Win32 поддержку SEH, было ее стремление 
упростить разработку Windows 95 и Windows NT и повысить надежность этих 
операционных систем. А нам SEH поможет сделать надежнее наши программы. 
    Основная нагрузка по поддержке SEH ложится на компилятор, а не на 
операционную систему. Он генерирует специальный код на входах и выходах блоков 
исключений (exception blocks), создает таблицы вспомогательных структур данных 
для поддержки SEH и предоставляет функции_рбратного вызова, к которым система 
могла бы обращаться для прохода по блокам исключений. Компилятор отвечает и за 
формирование стековых фреймов (stack frames) и другой внутренней информации, 
используемой операционной системой. Добавить поддержку SEH в компилятор — 
задача не из легких, поэтому не удивляйтесь, если фирма-разработчик Вашего 
любимого компилятора задержится с выпуском его версии для Win32. 
    Не удивляйтесь и тому, что разные фирмы по-разному реализуют SEH в своих 
компиляторах. К счастью, на детали реализации можно не обращать внимания, а 
просто задействовать возможности компилятора по поддержке SEH. 
    Различия в реализации SEH разными компиляторами могли бы затруднить 
описание конкретных примеров использования SEH. Но большинство фирм — 
разработчиков компиляторов придерживается синтаксиса, рекомендованного 
Microsoft. Синтаксис и ключевые слова в моих примерах могут отличаться от 
применяемых в других компиляторах, но основные концепции SEH везде одинаковы. 
В этой главе я использую синтаксис компилятора Microsoft Visual C++. 

    517 

    518 _____________________________Windows для профессионалов 


    /анимАниЕ\ 
    Не путайте SEH с обработкой исключений в C++, которая представляет собой 
еще одну форму обработки исключений, построенную на применении новых ключевых 
слов C++: catch и throw. Microsoft ввела эту форму обработки исключений в 
Visual C++, начиная с версии 2.0. Реализация Microsoft Visual C++ использует 
преимущества поддержки SEH, уже обеспеченной компилятором и операционными 
системами Windows. 

    SEH предоставляет 2 основные возможности: обработку завершения 
(termination handling) и обработку исключений (exception handling). 

    Обработчики завершения 

    Обработчик завершения гарантирует, что блок кода (собственно обработчик) 
будет выполнен независимо от того, как происходит выход из другого блока кода 
— защищенного участка программы. Синтаксис обработчика завершения при работе с 
компилятором Microsoft Visual C++ выглядит так: 

    __try { 
    // защищенный блок 
    } 
    „finally { 
    // обработчик завершения 
    } 

    Новые ключевые слова _try и _finally обозначают 2 блока обработчика 
завершения. В нашем фрагменте — благодаря совместным действиям операционной 
системы и компилятора — гарантируется, что код блока _finally обработчика 
завершения будет выполнен независимо от того, как произойдет выход из 
защищенного блока. И неважно, разместите ли Вы в защищенном блоке операторы 
return, goto или даже longjump — обработчик завершения все равно будет вызван. 
Вот порядок исполнения кода: 

    // 1. Исполняется код перед блоком __try 
    __try { 
    // 2. Исполняется код внутри блока __try 
    } 
    „finally { 
    // 3. Исполняется код внутри блока „finally 
    } 
    // 4. Исполняется код после блока „finally 


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

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

    _________________Глава 16. Структурная обработка исключений 519 

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

    Funcenstem 1 

    Чтобы оценить последствия применения обработчиков завершения, рассмотрим 
более конкретный пример: 

    DWORD Funcensteini (void) { 
    DWORD dwTemp: 
    // 1. Что-то делаем здесь 
    —try { 
    // 2. Запрашиваем разрешение на доступ 
    // к защищенным данным, а затем используем их 
    WaitForSingleObject(g_hSem, INFINITE); 
    -fcdwProtectedData = 5; 
    dwTemp = g_dwProtectedData; 
    i 
    f 
    __finally { 
    // 3, Даем и другим попользоваться защищенными данными 
    ReleaseSemaphore(g_hSe(n, 1, NULL); 
    ! 
    // 4. Продолжаем что-то делать 
    return(dwTemp): 
    } 

    Использование в Funcensteini блоков try-finally на самом деле мало что 
дает. Код ждет освобождения семафора, изменяет содержимое защищенных данных, 
сохраняет новое значение в локальной переменной dwTemp и возвращает его в 
вызывающую функцию. 

    Funcensfe»n2 

    Теперь чуть-чуть изменим код функции и посмотрим, что получится: 

    DWORD Funcenstein2 (void) { 
    DWORD dwTemp; 
    // 1. Что-то делаем здесь 
    __try { 
    // 2. Запрашиваем разрешение на доступ 
    // к защищенным данным, а затем используем их 
    WaltForSingleObject(g_hSem, INFINITE); 
    g_dwProtectedData = 5; 
    dwTemp = g_dwProtectedData; 

    см. след. стр. 

    520 _____________________________Windows для профессионалов 


    // возвращаем новое значение 
    return(dwTemp); 
    ! 
    „finally { 
    // 3. Даем и другим попользоваться защищенными данными 
    ReleaseSemaphore(g_hSem, 1, NULL): 
    > 
    // 4. Продолжаем что-то делать - в данной версии 
    // этот участок кода никогда не выполняется 
    dwTemp = 9; 
    return(dwTemp): 
    } 

    В конец блока try в функции Funcenstein2 добавлен оператор return. Он 
сообщает компилятору, что Вы хотите выйти из функции и вернуть значение 
переменной dwTemp (в данный момент равное 5). Но, если будет выполнен return, 
текущий поток никогда не освободит семафора, и другие потоки не получат шанса 
его занять. Такой порядок выполнения грозит вылиться в действительно серьезную 
проблему: ведь потоки, ожидающие семафора, могут оказаться не в состоянии 
возобновить свое выполнение. 
    Применив обработчик завершения, мы не допустили преждевременного 
выполнения оператора return. Когда return пытается реализовать выход из блока 
try, компилятор проверяет, чтобы сначала был выполнен код в блоке finally, — 
причем до того, как оператору return в блоке try будет дозволено реализовать 
выход из функции. Вызов Releasesemaphore в обработчике завершения (в функции 
Funcenstein2) гарантирует освобождение семафора — поток не сможет случайно 
сохранить права на семафор и тем самым лишить процессорного времени все 
ожидающие этот семафор потоки. 
    После выполнения блока finally функция фактически завершает работу. Любой 
код за блоком finally не выполняется, поскольку возврат из функции происходит 
внутри блока try. Так что функция возвращает 5 и никогда — 9. 
    Как же компилятор гарантирует выполнение блока finally до выхода из блока 
fry? Дело вот в чем. Просматривая исходный текст, компилятор «видит», что Вы 
вставили return внутрь блока try. Тогда он генерирует код, который сохраняет 
возвращаемое значение (в нашем примере 5) в созданной им же временной 
переменной. Затем генерирует код для исполнения инструкций, содержащихся в 
блоке finally, — это называется локальной раскруткой (local unwind). Точнее, 
локальная раскрутка происходит, когда система выполняет блок finally изза 
преждевременного выхода из блока try. Значение временной переменной, 
сгенерированной компилятором, возвращается из функции после выполнения 
инструкций в блоке finally. 
    Как видите, чтобы все это «вытянуть», компилятору приходится генерировать 
дополнительный код, а системе — выполнять дополнительную работу. На разных 
типах процессоров поддержка обработчиков завершения реализуется по-разному. 
Например, процессорам MIPS, Alpha и PowerPC понадобится несколько сотен или 
даже тысяч машинных команд, чтобы перехватить преждевременный возврат из try и 
вызвать код блока finally. Поэтому лучше не писать код, вызывающий 
преждевременный выход из блока try обработчика завершения, — это может 
отрицательно сказаться на быстродействии программы. Чуть позже мы обсудим 
ключевое слово _leave, которое помогает избежать написания кода, приводящего к 
локальной раскрутке. 
    Обработка исключений предназначена для перехвата тех исключений, которые 
происходят не слишком часто (в нашем случае — преждевременного возврата). Если 
же какое-то исключение чуть ли не норма, гораздо эффективнее проверять его 
явно, не полагаясь на SEH. 

    _________________Глава 16. Структурная обработка исключений 521 

    Заметьте: если поток управления выходит из блока try естественным образом 
(как в Funcensteini), плата за вызов блока finally минимальна. При 
использовании компилятора Microsoft на процессорах х86 для входа в finally при 
нормальном выходе из try исполняется всего одна машинная команда — вряд ли Вы 
заметите ее влияние на время выполнения своей программы. Но «цены взлетят», 
если компилятору придется генерировать дополнительный код, а операционной 
системе — выполнять дополнительную работу, как в Funcenstein2. 

    Funcenstein3 

    Снова изменим код функции: 

    DWORD Funcenstein3 (void) { 
    DWORD dwTemp; 
    // 1. Что-то делаем здесь 
    __try { 
    // 2. Запрашиваем разрешение на доступ 
    // к защищенным данным, а затем используем их 
    WaitForSingleObject(g_hSem, INFINITE); 
    g_dwProtectedData = 5; 
    dwTemp = g_dwProtectedData; 
    // пытаемся перескочить через блок finally 
    goto ReturnValue; 
    i 
    „finally { 
    // 3. Даем и другим попользоваться защищенными данными 
    ReleaseSemaphore(g_hSem, 1, NULL); 
    i 
    dwTemp = 9; 
    // 4. Продолжаем что-то делать 
    ReturnValue: 
    return(dwTemp); 
    } 

    Обнаружив в блоке try функции Funcenstein3 оператор goto, компилятор 
генерирует код для локальной раскрутки, чтобы сначала выполнялся блок finally. 
Но на этот раз после finally исполняется код, расположенный за меткой 
ReturnValue, так как возврат из функции "не происходит ни в блоке try, ни в 
блоке finally. В итоге функция возвращает 5. И опять, поскольку Вы прервали 
естественный ход потока управления из try в finally, быстродействие программы 
— в зависимости от типа процессора — может снизиться весьма значительно. 

    FuncfurterI 

    А сейчас разберем другой сценарий, в котором обработка завершения 
действительно полезна: 

    DWORD Funcfurter-l (void) { 
    DWORD dwTemp; 

    см. след. стр. 

    522 _____________________________Windows для профессионалов 


    // 1. Что-то делаем здесь 
    _ _ '- i    У       1 
    // 2. Запрашиваем разрешение на доступ 
    // к защищенным данным, а затем используем их 
    WaitForSingleObject(g_hSeni, INFINITE): 
    dwTemp = Funcinator(g_dwProtectedData); 
    „finally { 
    // 3. Даем и другим попользоваться защищенными данными 
    ReleaseSemaphore(g_hSem, 1, NULL); 
    } 
    // 4. Продолжаем что-то делать 
    return(dwTemp): 
    / 

    Допустим, в функции Funcinator, вызванной из блока try, — «жучок», из-за 
которого возникает нарушение доступа к памяти. В приложении для 16-разрядной 
Windows пользователь в очередной раз увидел бы самое известное диалоговое окно 
Application Error (Ошибка приложения). Стоит его закрыть — завершится и 
приложение. Если бы этот код выпол нялся в УУш32-приложении без блока 
try-finally и оно завершилось бы из-за неправильного доступа к памяти в 
Funcinator, семафор остался бы занят и не освободился — соответственно и 
ожидающие его потоки не получили бы процессорного времени. Но вызов 
ReleaseSemaphore в блоке finally гарантирует освобождение семафора, даже если 
нарушение доступа к памяти происходит в какой-то другой функции. 
    Раз обработчик завершения — такое мощное средство, способное перехватывать 
завершение программы из-за неправильного доступа к памяти, можно смело 
рассчитывать и на то, что оно перехватит также комбинации setjump'/longjump и 
элементарные операторы типа break и continue. 

    Проверьте себя: FuncaDood/eDoo 

    Посмотрим, отгадаете ли Вы, что именно возвращает следующая функция: 

    DWORD FuncaDoodleDoo (void) { 
    DWORD dwTemp = 0; 
    while (dwTemp < 10) { 
    __try { 
    if (dwTemp == 2) 
    continue; 
    if (dwTemp == 3) 
    break; 
    i 
    „finally { 
    dwTemp++; 
    } 


    _________________Глава 16. Структурная обработка исключений 523 


    dwTe[iip++; 
    } 
    dwTemp += 10; 
    return(dwTemp); 
    } 

    Проанализируем эту функцию шаг за шагом. Сначала dwTemp приравнивается 0. 
Код в блоке try исполняется, но ни одно из условий в операторах if не дает 
TRUE, и поток управления естественным образом переходит в блок finally, где 
dwTemp увеличивается до 1. Затем инструкция после блока finally снова 
увеличивает значение dwTemp, приравнивая его 2. 
    На следующей итерации цикла dwTemp равно 2, поэтому исполняется оператор 
continue в блоке try. Без обработчика завершения, вызывающего принудительное 
исполнение блока finally перед выходом из try, управление было бы передано 
непосредственно в начало цикла while, значение dwTemp больше бы не менялось — 
и мы в бесконечном цикле! В присутствии же обработчика завершения система 
обнаруживает, что оператор continue приводит к преждевременному выходу из try, 
и передает управление блоку finally. Значение dwTemp в нем увеличивается до 3, 
но код за этим блоком не исполняется, так как управление снова передается 
оператору continue, и мы вновь в начале цикла. 
    ТеперТ обрабатываем третий проход цикла. На этот раз значение выражения в 
первом if равно FALSE, а во втором — TRUE. Система снова перехватывает нашу 
попытку прервать выполнение блока try и обращается к коду finally. Значение 
dwTemp увеличивается до 4. Так как выполнен оператор break, выполнение 
возобновляется после тела цикла. Поэтому код, расположенный за блоком finally 
(но в теле цикла), не выполняется. Код, расположенный за телом цикла, 
добавляет 10 к значению dwTemp, что дает в итоге J 4, — это и есть результат 
функции. Даже не стану убеждать Вас никогда не писать такой код, как в 
FuncaDoodleDoo. Ято включил continue и break в середину кода, только чтобы 
продемонстрировать поведение обработчика завершения. 
    Хотя обработчик завершения справляется с большинством ситуаций, в которых 
выход из блока try был бы преждевременным, он не может вызвать исполнение 
блока finally при завершении потока или процесса. Вызов ExitThread или 
ExitProcess сразу завершит поток или процесс — без выполнения какого-либо кода 
в блоке finally. To же самое будет, если Ваш поток или процесс «умрут» из-за 
того, что некая программа вызвала TerminateThread или TerminateProcess. 
Некоторые функции стандартной библиотеки С (вроде abort), в свою очередь 
вызывающие ExitProcess, тоже исключают выполнение блока finally. He допустить 
завершения какого-либо из потоков или процессов нельзя, зато Вы можете сами не 
делать преждевременных вызовов ExitThread и ExitProcess. 

    Funcenstein4 

    Рассмотрим еще один сценарий обработки завершения. 

    DWORD Funcenstein4 (void) { 
    DWORD dwTemp; 
    // 1. Что-то делаем здесь 
    __try { 
    // 2. Запрашиваем разрешение на доступ 
    // к защищенным данным, а затем используем их 
    WaitForSingleObject(g_hSem, INFINITE); 

    см. след. стр. 


    524 ______________________________Windows для профессионалов 


    g_dwProtectedData = 5; 
    dwTemp = g_dwProtectedData; 
    // возвращаем новое значение 
    return(dwTemp); 
    i 
    __finally { 
    // 3 Даем и другим попользоваться защищенными данными 
    ReleaseSemaphore(g_hSem, 1, NULL); 
    return(103); 
    i           ^-- - • • 
    ( 
    // 4. Продолжаем что-то делать - этот код 
    // никогда не исполняется 
    dwTemp = 9: 
    return(dwTe[np); 
    } 

    Блок try в Funcenstein4 пытается вернуть значение переменной dwTemp (5) 
функции, вызвавшей Funcenstein4. Как мы уже отметили при обсуждении 
Funcenstein2, попытка преждевременного возврата из блока try вызывает 
генерацию кода, который записывает возвращаемое значение во временную 
переменную, созданную компилятором. Затем выполняется код в блоке finally. 
Кстати, в этом варианте Funcenstein2 я добавил в блок finally оператор return. 
Вопрос: что вернет Funcenstein4 — 5 или 103? Ответ: 103, так как оператор 
return в блоке finally приведет к записи значения 103 в ту же временную 
переменную, в которую занесено значение 5. По завершении блока finally текущее 
значение временной переменной (103) возвращается функции, вызвавшей Funcenstein4. 
    Итак, обработчики завершения, весьма эффективные при преждевременном 
выходе из блока try, могут дать нежелательные результаты именно потому, что 
предотвращают преждевременный выход из блока try. Лучше всего избегать любых 
операторов, способных вызвать преждевременный выход из блока try обработчика 
завершения. А в идеале — удалить все операторы return, continue, break, goto 
(и им подобные) как из блоков try, так и из блоков finally. Тогда компилятор 
сгенерирует код и более компактный (перехватывать преждевременные выходы из 
блоков try не понадобится), и более быстрый (на локальную раскрутку 
потребуется меньше машинных команд). Да и читать Ваш код будет гораздо легче. 

    Funcarama ? 

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

    BOOL Funcaramal (void) { 
    HANDLE hFile = INVALID_HANDLE_VALUE; 
    LPVOID IpBuf = NULL; 
    DWORD dwNumBytesRead; 
    BOOL fOk; 
    hFile = CreateFileC'SOMEDATA.DAT", GENERIC_READ, 
    FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); 


    __________________Глава 16. Структурная обработка исключений 525 


    if (hFile == INVALID_HANDLE_VALUE) { 
    return(FALSE); 
    } 
    IpBuf = VirtualAlloc(NULL, 1024, MEM_COMMIT, PAGE_READWRITE); 
    if (IpBuf == NULL) < 
    CloseHandle(hFile); 
    return(FALSE); 
    1 
    fOk = ReadFile(hFile, IpBuf, 1024, &dwNumBytesRead, NULL); 
    if (!f0k :; (dwNumBytesRead ==0)) { 
    VirtualFreedpBuf, MEM_RELEASE : MEM_DECOMMIT); 
    CloseHandle(hFile); 
    return(FALSE); 
    f 
    // что-то делаем с данными 
    // очистка всех ресурсов 
    VirtualFreedpBuf, MEM_RELEASE : MEM_DECOMMIT); 
    CloseHandle(hFile); 
    return(TRUE); 
    } 

    Проверки ошибок в функции Funcaramal затрудняют чтение ее текста, что 
усложняет ее понимание, сопровождение и модификацию. 

    Funcarama2 

    Конечно, можно переписать Funcaramal так, чтобы она стала яснее: 

    BOOL Funcarama2 (void) { 
    HANDLE hFile = INVALID_HANDLE_VALUE: 
    LPVOID IpBuf = NULL; 
    DWORD dwNumBytesRead; 
    BOOL fOk, fSuccess = FALSE; 
    hFile = CreateFileC'SOMEDATA.DAT", GENERIC_READ, 
    FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); 
    if (hFile != INVALID_HANDLE_VALUE) { 
    IpBuf = VirtualAlloc(NULL, 1024, MEM_COMMIT, PAGE_READWRITE): 
    if (IpBuf != NULL) { 
    fOk = ReadFile(hFile, IpBuf, 1024, &dwNumBytesRead, NULL); 
    if (fOk && (dwNumBytesRead != 0)) { 
    // что-то делаем с данными 
    fSuccess = TRUE; 
    } 
    VirtualFreedpBuf, MEM_RELEASE ; MEM_DECOMMIT); 
    } 

    см. след. стр. 

    526 _____________________________Windows для профессионалов 


    CloseHandle(hFile); 
    return(fSuccess); 
    } 

    Vuncarama.2 легче для понимания, но по-прежнему трудна для модификации и 
сопровождения. Кроме того, приходится делать слишком много отступов по мере 
добавления новых условных операторов; после такой переделки Вы того и гляди 
начнете писать код на правом краю экрана и переносить операторы на другую 
строку через каждые 5 символов! 

    FuncaramaS 

    Перепишем-ка еще раз первый вариант (Funcaramal), задействовав 
преимущества обработки завершения: 

    BOOL FuncaramaS (void) { 
    HANDLE hFile = INVALID_HANDLE_VALUE; 
    LPVOID IpBuf = NULL; 
    __try { 
    DWORD dwNumBytesRead; 
    BOOL fOk; 
    hFile = CreateFileC'SOMEDATA.DAT", GENERIC_READ, 
    FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); 
    if (hFile == INVALID_HANDLE_VALUE) { 
    return(FALSE); 
    }   """' 
    IpBuf = VirtualAlloc(NULL, 1024, MEM_COMMIT, PAGE_READWRITE); 
    if (IpBuf == NULL) { 
    return(FALSE); 
    } 
    fOk = ReadFile(hFile, IpBuf, 1024, &dwNumBytesRead, NULL); 
    if (!f0k ;: (dwNumBytesRead != 1024)) { 
    return(FALSE): 
    // что-то делаем с данными 
    „finally { 
    // очистка всех ресурсов 
    if (IpBuf != NULL) 
    VirtualFreedpBuf, MEM_RELEASE : MEM_DECOMMIT); 
    if (hFile != INVALID_HANDLE_VALUE) 
    CloseHandle(hFile); 
    } 
    // продолжаем что-то делать 
    return(TRUE); 
    } 

    _________________Глава 16. Структурная обработка исключений 527 


    Главное достоинство РипсагатаЗ в том, что весь код, занимающийся очисткой, 
собран в одном месте — в блоке finally. Если понадобится включить что-то в эту 
функцию, мы просто добавим одну-единственную строку для очистки в блок finally 
— возвращаться к каждому месту возможного возникновения ошибки и вставлять в 
него строку для очистки не нужно. 

    Funcarama4: последний рубеж 

    Настоящая проблема в Funcarama3 — расплата за изящество. Я уже говорил: 
избегайте по возможности операторов return внутри блока try. 
    Чтобы облегчить последнюю задачу, Microsoft ввела еще одно ключевое слово 
в свой компилятор C++: _leave. Вот новая версия (Funcarama4), построенная на 
применении нового ключевого слова: 

    BOOL Funcarama4 (void) { 
    HANDLE hFile = INVALID_HANDLE_VALUE; 
    LPVOID IpBuf = NULL; 
    // предполагаем, что выполнение функции будет неудачным 
    BOOL fFunctionOk = FALSE; 
    __try { 
    DWORD dwNumBytesRead; 
    BOOL fOk; 
    hFile = CreateFile("SOMEDATA.DAT", GENERIC_READ, 
    FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL): 
    if (hFile == INVALID_HANDLE_VALUE) < 
    „leave; 
    IpBuf = VirtualAlloc(NULL, 1024, MEM_COMMIT, PAGE_READWRITE); 
    if (IpBuf == NULL) { 
    „leave; 
    i 
    fOk = ReadFile(hFile, IpBuf, 1024, &dwNumBytesRead, NULL); 
    if (!f0k ;; (dwNumBytesRead == 0)) { __leave; 
    // что-то делаем с данными 
    // функция выполнена успешно 
    fFunctionOk = TRUE; 
    } 
    „finally { 
    // очистка всех ресурсов 
    if (IpBuf != NULL) 
    VirtualFreedpBuf, MEM_RELEASE ; MEM_DECOMMIT): 

    см. след. стр. 


    528 _____________________________Windows для профессионалов 


    if (hFile != INVALID_HANDLE_VALUE) 
    CloseHandle(hFile): 
    } 
    // продолжаем что-то делать 
    return(fFunctionOk); 
    } 

    Ключевое слово _leave в блоке try вызывает переход в конец этого блока. 
Можете рассматривать это как переход на закрывающую фигурную скобку блока try. 
И никаких неприятностей это не таит, потому что выход из блока try и вход в 
блок finally происходит естественным образом. Правда, нужно ввести 
дополнительную Булеву переменную fFunctionOk, сообщающую о завершении функции: 
удачно оно или нет. 
    Разрабатывая функции, использующие обработчики завершения именно так, 
проинициализируйте все описатели ресурсов неверными значениями перед входом в 
блок try. Тогда в блоке finally Вы проверите, какие ресурсы выделены успешно, 
и узнаете тем самым, какие из них следует потом освободить. Другой 
распространенный метод отслеживания ресурсов, подлежащих освобождению, — 
установка флага при успешном выделении ресурса. Код finally проверяет 
состояние флага и таким образом определяет, надо ли освобождать ресурс. 

    И еще о блоке finally 

    Пока нам с Вами удалось четко выделить только 2 сценария, приводящих к 
выполнению блока finally. 
    • нормальная передача управления из блока try в блок finally, 
    • локальная раскрутка — преждевременный выход из блока try (goto, 
longjump, continue, break, return и т. д.), вызывающий принудительную передачу 
управления блоку finally. 
    Третий сценарий — глобальная раскрутка (global unwind) — протекает не 
столь выраженно. Вспомним Funcfurterl. Ее блок try содержал вызов функции 
Funcinator. При неверном доступе к памяти в Funcinator глобальная раскрутка 
вызывала исполнение блока finally в Funcfurterl. Но подробнее о глобальной 
раскрутке мы поговорим в разделе «Фильтры и обработчики исключений». 
    Выполнение кода в блоке finally всегда начинается в результате 
возникновения одной из трех, уже описанных ситуаций. Чтобы определить, какая 
из них вызвала исполнение блока finally, вызовите встраиваемую функцию' AbnormalTermination: 

    BOOL AbnormalTermination(VOID); 

    Ее можно вызвать только из блока finally, она возвращает Булево значение, 
определяющее, был ли преждевременный выход из блока try, связанного с данным 
блоком finally. Иначе говоря, если управление естественным образом передано из 
try в finally, AbnormalTermination возвращает FALSE. А если выход был 
преждевременным — обычно либо из-за локальной раскрутки, вызванной оператором 
goto, return, break или continue, либо из-за глобальной раскрутки, вызванной 
нарушением доступа к памяти, — то вызов AbnormalTermination дает TRUE. Но 
когда она возвращает TRUE, различить, чем вызвано исполнение блока finally — 
глобальной или локальной раскруткой, — нельзя. 
    1. Это особая функция, распознаваемая компилятором. Вместо генерации 
вызова такой функции он подставляет в точке вызова ее код. Примером 
встраиваемой функции является тетсру (если указан ключ компилятора /Oi). 
Встречая вызов тетсру, компилятор подставляет ее код непосредственно в 
вызывающую функцию. Обычно это ускоряет работу программы ценой увеличения ее 
размера. Функция AbnormalTermination отличается от тетсру тем, что существует 
только во встраиваемом варианте. 

    _________________Глава 16. Структурная обработка исключений 529 


    Funcfurter2 

    Следующий фрагмент демонстрирует использование встраиваемой функции 
Abnormal

    Termination: 
    DWORD Funcfurter2 (void) { 
    DWORD dwTemp; 
    // 1. Что-то делаем здесь 
    __try { 
    // 2. Запрашиваем разрешение на доступ 
    // к защищенным данным, а затем используем их 
    WaitForSingleObject(g_hSem, INFINITE); 
    dwTemp = Funcinator(g_dwProtectedData); 
    .„finally { 
    // 3. Даем и другим попользоваться защищенными данными 
    ReleaseSemaphore(g_hSem, 1, NULL); 
    if (!AbnormalTermination()) { 
    // в блоке try не было ошибок - управление 
    // передано в блок finally естественным образом 
    } else { 
    
    // что-то вызвало исключение, и, так как в блоке try 
    // нет кода, который мог бы вызвать преждевременный 
    // выход, блок finally выполняется из-за глобальной 
    // раскрутки 
    
    // если бы в блоке try не было goto, мы бы 
    // не узнали, как попали сюда 
    
    // 4. Продолжаем что-то делать 
    } 

    Теперь Вы знаете, как создавать обработчики завершения. Вскоре Вы увидите, 
что они могут быть еще полезнее и важнее, — когда мы дойдем до фильтров и 
обработчиков исключений. А пока еще раз отметим причины, по которым следует 
применять обработчики завершения. Вот что они позволяют: 
    • упростить обработку ошибок — очистка гарантируется и проводится в одном месте; 
    • улучшить «читабельность» программ; 
    • облегчить сопровождение кода; 
    • добиться минимальных затрат по сковости и размеру кода— при условии 
правильного применения обработчиков. 

    530 _____________________________Windows для профессионалов 


    Программа-пример SEHTerm 

    Программа SEHTerm (SEHTERM.EXE) (см. листинг на рис. 16-1) демонстрирует 
применение обработчиков завершения, моделируя для этого выполнение функции, 
подсчитывающей число слов в файле. Файлы исходного кода и ресурсов для этой 
программы находятся в каталоге SEHTERM на компакт-диске, прилагаемом к книге. 
Функция Dlg_CountWordsInFile из этой программы работает так: 
    1. Открывает файл. 
    2. Получает его размер. 
    3. Выделяет блок памяти в соответствии с результатом операции из п. 2. 
    4. Считывает содержимое файла в выделенный блок памяти. 
    5. Подсчитывает количество слов в блоке. 
    Затем проводит очистку и возвращает количество слов: 
    1. Освобождает блок памяти. 
    2. Закрывает файл. 
    3. Возвращает количество слов в файле (в случае ошибки возвращается -1). 
    При инициализации в этой функции на любом из этапов 1-4 может произойти 
ошибка. Если это случается, функция, прежде чем вернуть -1 вызывающей 
процедуре, должна освободить все ранее выделенные ресурсы. Корректную очистку 
гарантирует обработчик завершения. 
    Впервые запустив SEHTerm, Вы увидите диалоговое окно: 

    .• Results of execution 
    (7 opening of tile succeeds; 
    ' 3? Setting tils size succeeds 
    1У Memory allocation succeeds               I———————i ! 
    ?.<ecutB  [ 
    ^ Oata read succeeds 
    Execution log; 

    В разделе Results Of Execution (результаты выполнения) можно указать, 
какие из четырех операций будут удачными, а какие — нет. Помните: программа 
только делает вид, что работает; на самом деле она не открывает файлов, ничего 
не читает и ничего не вычисляет. В каком-то смысле Вы играете здесь роль 
операционной системы, сообщая программе, какие операции будут успешны, а какие 
— нет. 
    Установив по своему усмотрению 4 флажка, щелкните кнопку Execute 
(выполнить). Программа с помощью Dlg_CountWordsInFik попытается выполнить 
операции с 1 по 4. Все они выполняются внутри блока try. Если в результате 
какой-то операции происходит ошибка, внутри блока try исполняется оператор 
_leave, и управление сразу же передается в finally, пропуская остаток кода в try. 

    ________________Глава 16. Структурная обработка исключений 531 

    Код в блоке finally проверяет результат инициализации, просматривая для 
этого переменные hFile и IpvFileData, а затем выполняет соответствующие 
процедуры очистки. Окно списка Execution Log (результаты выполнения) в нижней 
части диалогового окна SEH Termination Handler Test (тест обработчика 
завершения SEH) показывает результаты каждой из операций, выполненных функцией Dlg_CountWordsInFile. 
    Пусть в первом эксперименте все 4 операции завершаются успешно. Для этого 
перед щелчком кнопки Execute удостоверимся, что все флажки установлены. После 
выполнения кода мы увидим результат в окне списка Execution Log: 

    : Results of execution 
    P Opening of file succeeds 
    ' F getting tile size succeeds                                 : 
    У Memory allocation succeeds                   \; -.- ..............................1 
    I Execute |j : 
    P Data read succeeds 
    Execution IOQ: 
    Starting execution 
    : File open: OK 
    4 File size: OK 
    II Memory allocation: OK 
    I File read: OK 
    J Calculating the number of words 
    |il Cleaning up 
    "'': Freeing memory                                               ..„ 
    Closing file                                                   "y 
    Result: Words in file'37                                     'tt 

    Диалоговое окно сообщает, что инициализация прошла успешно, слова 
подчитаны и очистка выполнена. Теперь смоделируем ошибку при выделении памяти. 
И получим: 

    ' 1^ Opening of file succeeds                                  ^ 
    17 Getting file size succeeds                                 : 
    f Memory allocation succeeds                   ?.„....... ....................rj 
    | Execute j 
    P Date read succeed?                                , j 
    Execution IO.Q: 
    Starting execution 
    File open: OK 
    File size: OK                                                           '..';' 
    Memory allocation: Fail                                     '^ 
    Cleaning up                                                 У 
    Closing file                                                       'g| 
    Error occurred in function,                                           :t| 

    Функция открыла файл, определила его размер и попыталась выделить блок 
памяти. Когда мы принудительно вызвали ошибку при выделении памяти, управление 
перескочило через остаток кода try в блок finally. Проверив переменную hFile, 
код в блоке finally обнаружил, что файл открыт, и закрывает его вызовом CloseHandle. 
    Код в блоке finally проверяет также переменную IpvFileData, чтобы 
определить, был ли выделен блок памяти. В данном случае IpvFileData содержит 
NULL, поэтому код не пытается 

    532 ______________________________Windows для профессионалов 

    освободить блок памяти и строка Freeing memory (освобождаю память) в окне 
Execution Log на этот раз не появляется. 
    Кроме того, функция вернула -1, что указывает на ошибку. В результате в 
окне появилась строка Error occurred in function (произошла ошибка в функции). 
    Но проведем еще один эксперимент. Зададим ошибку при открытии файла, а все 
остальное пусть завершится успешно. Тогда выполнение кода в блоке try 
прекратится, как только функция не сумеет открыть файл. В этом случае 
остальная инициализация не проводится, а сразу начинается выполнение блока 
finally. Код, реализующий очистку, «видит», что файл не открыт, блок памяти не 
выделен, и поэтому ничего не делает. 

    Results of execution     . 
    Г" Opening of file succeeds                                  ; 
    17 Getting file size succeeds                                   j 
    : R Memory allocation succeeds                    i..................................,..| 
    | Execute j 
    : p Data read succeeds 
    Execution lofl: 
    Starting execution 
    File open: Fail 
    Cleaning up 
    Error occurred in function. 

    Вы можете сами поэкспериментировать с этой программой, устанавливая разные 
комбинации флажков. Понаблюдайте, как при этом меняется содержимое списка 
Execution Log. Чтобы глубже вникнуть в то, как действуют обработчики 
завершения, поглядывайте при экспериментах на исходный код программы SEHTerm. 

    SEHTERM.C                      ^lq———1———^^H^^————l 
    М;д;:**ЗЕИТ;;:*Г"**""*""""""*"""*"^^^^^^^Д
    Автор: Copyright © 1995-1997, Джеффри Рихтер (Jeffrey Richter)   ЯЦ^^^^^^^^^^^^Н 
    
    tfinclude ". .\CffinHdr, H"              /* см. приложение В */            
        "Ж^^^^^^^Н 
    ffinclude <windows.h>                                                      
     "•'^^^^^^^1 
    ftinclude <windowsx.h>                                                     
    lifl^^^^^^^H 
    ftinclude <tchar.h>                                                        
       '^^^^^^H 
    ttinolude <stdio.h>                                                        
       .'^^^^^^1 
    ffinclude "Resource. H"                                                    
        ||^^^^^^^1 
    
ltl!ltl|ll!/llll/lll!///lll!^/ll!//////!ii////!'i!///^l/"!/i!!!!!^!"•'!/:lll  ''^^^^P 
    // Следует определить препроцессорный символ SIMULATION.                   
     '^fl^^^^^B 
    // Он существует для того, чтобы Вы легко отличали имитацию                
    - "•^ННЯНВ 
    - '"fSSSSSSS^i^iSiit 

    см. след. стр. 

    Рис. 16-1. Листинг программы SEHTerm 


    _________________Глава 16. Структурная обработка исключений 533 


    РИС. 16-1   (продолжение) 
    
    II от настоящего кода, который может пригодиться Вам для 
    // выполнения своих операций. 
    ftdefine SIMULATION 
    
    I I Illllilllllllllllllllllllllllllllllllllllllllllfill/.111111/111111111111111111 
    BOOL Dlg_OnInitDialog (HWND hwnd, HWND hwndFocus, LPARAM IParam) { 
    
    // связываем значок с диалоговым окном 
    chSETDLGICONS(hwnd, IDI_SEHTERM, IDI_SEHTERM); 
    Button_SetCheck(GetDlgItem(hwnd, IDC_OPENSUCCEEDS), TRUE): 
    Button_SetCheck(GetDlgItem(hwnd, IDC_SIZESUCCEEDS), TRUE); 
    Button_SetCheck(GetDlgItem(hwnd, IDC_MEMSUCCEEDS), TRUE); 
    Button_SetCheck(GetDlgItem(hwnd, IDC_READSUCCEEDS), TRUE); 
    return(TRUE); 
    } 
    11111111111111111111111111111111/llllllilllilllll/llilllllllllllllllll/llllllll 
    LONG Dlg_CountWordsInFile (HWND hwndLog, BOOL fOpenSucceeds, 
    BOOL fFileSizeSucceeds, BOOL fMemSucceeds, BOOL fReadSucceeds) { 
    HANDLE hFile = INVALID_HANDLE_VALUE: 
    DWORD dwFileSize = 0; 
    LPVOID IpvFileData = NULL; 
    BOOL  fFileReadOk = FALSE; 
    LONG  INumWords = -1; 
    DWORD dwLastError; 
    „try { 
    
    // очищаем окно списка Execution Log 
    ListBox_ResetContent(hwndLog); 
    ListBox_AddString(hwndLog, __TEXT("Start ing execution")); 
    
    // открываем файл 
    ftifdef SIMULATION 
    hFile = (fOpenSucceeds ? (HANDLE) !INVALID_HANDLE_VALUE : 
    INVALID_HANDLE_VALUE); 
    ffelse 
    hFile = CreateFile(...); 
    ftendif 
    
    if (hFile == INVALID_HANDLE_VALUE) { 
    // файл открыть не удалось 
    ListBox_AddString(hwndLog, __TEXT("   File open: Fail")); 
    „leave: 
    } else { 
    ListBox_AddString(hwndLog, __TEXT("   File open: OK")): 
    i 
    / 
    (.'-г. слео. стр. 


    534 ____________________________Windows для профессионалов 


    РИС. 16-1  (продолжение) 
    
    II определяем размер файла 
    Wifdef SIMULATION 
    dwLastError = fFileSizeSucceeds ? NO_ERROR : !NO_ERROR; 
    ftelse 
    dwFileSize = GetFileSize(hFile); 
    dwLastError = GetLastError(); 
    #endif 
    if (dwLastError != NO_ERROR) { 
    // размер файла получить не удалось 
    ListBox_AddString(hwndLog, __TEXT("   File size: Fail")); 
    __leave; 
    } else { 
    ListBox_AddString(hwndLog, __TEXT("   File size: OK")); 
    ( 
    
    // выделяем блок памяти для считывания всего файла 
    (tifdef SIMULATION 
    IpvFileData = fMemSucceeds ? !NULL : NULL; 
    ftelse 
    IpvFileData = HeapAlloc(GetProcessHeap(), 0, dwFileSize): 
    ffendif 
    if (IpvFileData == NULL) { 
    
    // ошибка при выделении памяти 
    ListBox_AddString(hwndLog, __TEXT("   Memory allocation: Fail")); 
    __leave; 
    } else { 
    ListBox_AddString(riwndLog, __TEXT("   Memory allocation: OK")); 
    > 
    // считываем файл в буфер 
    ffifdef SIMULATION 
    fReadSucceeds = fReadSucceeds; 
    ffelse 
    fReadSucceeds = ReadFile(hFile, IpvFileData, dwFileSize, NULL, NULL); 
    ttendif 
    if (!fReadSucceeds) { 
    
    // содержимое файла поместить в память не удалось 
    ListBox_AddString(hwndLog, __TEXT("   File read: Fail")); 
    „leave; 
    } else { 
    ListBox_AddString(hwndLog, __TEXT("   File read: OK")); 
    fFileReadOk = TRUE; 
    } 
    // Вычисляем количество слов в файле. Здесь следовало бы 
    // разместить алгоритм для подсчета слов. В данном случае 

    см. след. стр. 

    __________________Глава 16. Структурная обработка исключений 535 


    РИС. 16-1  (продолжение) 

    /I я просто приравниваю INumWords 37. 
    ListBox_AddString(hwndLog, __TEXT("   Calculating the number of words")); 
    INumWords = 37: 
    i // try 
    „finally { 
    // сообщаем о том, что проводится очистка 
    ListBox_AddString(hwndLog, __TEXT("   Gleaning up")); 
    // проверяем освобождение памяти 
    if (IpvFileData != NULL) { 
    ListBox_AddString(hwndLog, __TEXT("   Freeing memory")); 
    ffifndef SIMULATION 
    HeapFree(GetProcessHeap(), 0, IpvFileData); 
    #endif 
    } 
    // проверяем, закрыт ли файл 
    if (hFile != INVALID_HANDLE_VALUE) { 
    ListBox_AddString(hwndLog, __TEXT("   Closing file")); 
    ftifndef SIMULATION 
    CloseHandle(hFile): 
    ffendif 
    } 
    } // finally 
    return(lNumWords); 
    ) 
    l/l/ll//lllllllllll//llll/il//IH/IIIHIII/IIIHIII/ll/l/IIIIIIIHIIIIIIIIIIIIt 
    void Dlg„OnCommand (HWNO hwnd, int id, HWND nwndCtI, UINT codeNotify) { 
    TCHAR szBuf[100]; 
    LONG INumWords; 
    
    switch (id) { 
    case IDOK: 
    INumWords = Dlg_CountWordsInFile( 
    Get01gltem(hwnd, IOC_LOG), 
    
    Button_GetCheck(GetDlgItem(hwnd, IDC_OPENSUCCEEDS)), 
    Button_GetCneck(GetDlgIten)(hwnd, IDC_SIZESUCCEEDS)), 
    Button_GetCheck(Get01gItem(hwnd, IDC_MEMSUCCEEDS)), 
    Bu t tan_Ge tCfieck(Ge t01gltef!f(fimd, IOC_READSUCCEEOS)) 
    ): 

    см. след. стр. 


    536 _____________________________Windows для профессионалов 


    РИС. 16-1   (продолжение) 

    if (INumWords == -1) { 
    ListBox_AddString(GetDlgItem(hwnd, IDC_LOG), 
    __TEXT("Error occurred in function.")); 
    } else { 
    _stprintf(szBuf, __TEXT("Result: Words in file = %d"), INumWords); 
    ListBox_AddString(GetDlgItein(hwnd, IOC_LOG), szBuf); 
    } 
    break; 
    case IDCANCEL: 
    EndDialog(hwnd, id); 
    break; 
    » » 
    Illll/lltllllllllllllllllllllllllllllll/llllillllllllllllllllll'/Illl/llllli'llii 
    BOOL CALLBACK DIgProc (HWND hwnd. UINT uMsg, WPARAM wParam, LPARAM 
IParani) { switch (uMsg) { 
    chHANDLE_DLGMSG(hwnd, WM_INITDIALOG. DIgJMnitDialog); 
    chHANDLEJ)LGMSG(hwnd, WM_COMMAND, DlgJ)nCommand); 
    } 
    return(FALSE); 
    } 
    IIIIIIIIIIIIHIIIIIIIIIIIIIIIIIIIIIIIItllllllllllllllllllilllllllllllllllllllll 
    int WINAPI _tWinMain (HINSTANCE hinstExe, 
    HINSTANCE hinstPrev, LPTSTR pszCmdLine, int nCindShow) { 
    chWARNIFUNICODEUNOERWIN95(); 
    DialogBox(hinstExe, MAKEINTRESOURCE(IDD_SEHTERM), NULL. DIgProc); 
    return(O); 
    Illltllllllllllllll/llllllllllllll конец файла I t I I I I I I I I I I I I 


    Фильтры и обработчики исключений 

    Исключение — это событие, которого Вы не ожидали. В хорошо написанной 
программе не предполагается попыток обращения по неверному адресу или деления 
на нуль. И все же такие ошибки случаются. За перехват попыток обращения по 
неверному адресу и деления на нуль отвечает центральный процессор, 
возбуждающий исключения в ответ на такие ошибки. Исключение, возбужденное 
процессором, называется аппаратным (hardware exception). Далее мы увидим, что 
операционная система и прикладные программы способны возбуждать собственные 
программные исключения (software exceptions). 

    __________________Глава 16. Структурная обработка исключений 537 

    При возникновении аппаратного или программного исключения операционная 
система дает Вашему приложению шанс определить его тип и самостоятельно 
обработать. Синтаксис обработчика исключений таков: 

    __try { 
    // защищенный блок 
    > 
    __except (фильтр исключений) { 
    // обработчик исключений 

    Обратите внимание на ключевое слово _except. За блоком try всегда должен 
следовать либо блок finally, либо блок except. Для данного блока try нельзя 
указать одновременно и блок finally, и блок except, к тому же за try не могут 
следовать несколько блоков finally или except. Однако try-except можно вложить 
в try-finally, и наоборот. 

    Примеры использования фильтров и обработчиков исключений 

    В отличие от обработчиков завершений, фильтры и обработчики исключений 
выполняются непосредственно операционной системой — нагрузка на компилятор при 
этом минимальна. В следующих 6 разделах я расскажу, как обычно выполняются 
блоки try-except, как и когда операционная система проверяет фильтры 
исключений, и объясню, в каких случаях она выполняет код обработчиков исключений. 

    Funcmeister] 

    Вот более конкретный пример кода try-except 

    DWORD Funcmeister-1 (void) { 
    DWORD dwTemp: 
    // 1. Что-то делаем здесь 
    __try { 
    // 2. Выполняем какую-то операцию 
    dwTemp = 0; 
    } 
    „except (EXGEPTION_EXECUTE_HANOLER) { 
    // обрабатываем исключение; этот код 
    // никогда не исполняется 
    // 3. Продолжаем что-то делать 
    return(dwTemp): 
    } 

    В блоке try функции Funcmeisteri мы просто присваиваем 0 переменной 
dwTemp. Такая операция не приведет к исключению, и поэтому код в блоке except 
никогда не выполняется. Обратите внимание на такую особенность: конструкция 
try-finally ведет себя иначе. 

    538 _____________________________Windows для профессионалов 

    После того как переменной dwTemp присваивается 0, следующим исполняемым 
оператором оказывается return. 
    Хотя ставить операторы return, goto, continue и break в блоке try 
обработчика завершения настоятельно не рекомендуется, их применение в блоке 
try обработчика исключений не приводит к снижению быстродействия кода или к 
увеличению его размера. Использование этих операторов в блоке try, связанном с 
блоком except, не вызовет таких неприятностей, как локальная раскрутка. 

    Funcmeister2 

    Попробуем модифицировать нашу функцию и посмотрим, что будет: 
    DWORD Funcmeister2 (void) { 
    DWORD dwTemp; 
    // 1, Что-то делаем здесь 
    __1' у i 
    // 2. Выполняем какую-то операцию 
    dwTemp = 5 / dwTemp;   // генерирует исключение 
    dwTemp += 10:          // никогда не выполняется 
    } __except ( /* 3. Проверяем фильтр */ EXCEPTION_EXECUTE_HANDLER) { 
    // 4. Обрабатываем исключение 
    MessageBeep(O); 
    // 5. Продолжаем что-то делать 
    return(dwTemp); 
    } 

    Инструкция внутри блока try функции Funcmeister2 пытается разделить 5 на 
0. Перехватив это событие, процессор возбуждает аппаратное исключение. Тогда 
операционная система ищет начало блока except и проверяет выражение, указанное 
в качестве фильтра исключений; оно должно дать один из трех идентификаторов, 
определенных в заголовочном файле Win32 ЕХСРТ.Н: 

    Идентификатор                             Значение 

    EXCEPTION_EXECUTE_HANDLER               1 
    EXCEPTION_CONTINUE_SEARCH               0 
    EXCEPTION_CONTINUE_EXECUTION           -1 
    EXCEPTION_EXECUTE_HANDLER 

    Фильтр исключений в функции Funcmeister2 определен как 
EXCEPTION_EXECUTE_HANDLER. Это значение сообщает системе в основном вот что: 
«Я вижу это исключение; так и знал, что оно где-нибудь да произойдет; у меня 
есть код для его обработки, и я хочу его 

    Глава 16. Структурная обработка исключений 539 

    сейчас выполнить». Управление сразу же передается коду внутри блока except 
(коду обработчика исключений), а после его выполнения система считает 
исключение обработанным и разрешает программе продолжить работу. 
    Как видите, и здесь мало что общего с приложениями 16-разрядной Windows, в 
которых деление на нуль вызывает появление окна System Error (Системная 
ошибка), и пользователю остается одно — немедленно закрыть программу. В 
УУт32-приложении эту ошибку можно перехватить, обработать и продолжить 
выполнение программы — пользователь даже не узнает, что была какая-то ошибка. 
    Но вот откуда возобновится выполнение? Поразмыслив, можно представить 
несколько вариантов. 
    Первый вариант. Выполнение возобновляется сразу за строкой, возбудившей 
исключение. Тогда в Funcmeister2 выполнение продолжилось бы с инструкции, 
которая прибавляет к dwTemp число 10. Вроде логично, но на деле в большинстве 
программ невозможно продолжить корректное выполнение, если одна из предыдущих 
инструкций вызвала ошибку. 
    В нашем случае нормальное выполнение можно продолжить, но Funcmeister2 в 
этом смысле не типична. Скорее всего Ваш код структурирован так, что 
инструкции, следующие за той, что возбудила исключение, ожидают от нее 
правильного значения. Например, у Вас может быть функция, выделяющая блок 
памяти; тогда для операций с ним, несомненно, предусмотрена целая серия 
инструкций. Если блок памяти выделить не удастся, все они потерпят неудачу, и 
программа повторно вызовет исключение. 
    Вот еще пример того, почему выполнение нельзя продолжить сразу после 
команды, возбудившей исключение. Заменим оператор языка С, дающий исключение в 
Funcmeister2, строкой: 

    malloc(5 / dwTemp): 

    Компилятор сгенерирует для нее машинные команды, которые выполняют 
деление, результат помещают в стек и вызывают malloc. Если попытка деления 
привела к ошибке, дальнейшее (корректное) выполнение кода невозможно. Система 
должна поместить что-то в стек, иначе он будет разрушен. 
    К счастью, Microsoft не дает нам шансов возобновлять выполнение со строки, 
расположенной вслед за возбудившей исключение. Это спасает нас от только что 
описанных потенциальных проблем. 
    Второй вариант. Выполнение возобновляется с той же команды, что возбудила 
исключение. Этот вариант довольно интересен. А что если в блоке except 
присутствует оператор: 

    dwTemp = 2; 

    Тогда Вы вполне могли бы возобновить выполнение с возбудившей исключение 
команды. На этот раз Вы поделили бы 5 на 2, и программа спокойно продолжила бы 
свою работу. Иначе говоря, Вы что-то меняете и заставляете систему повторить 
выполнение команды, возбудившей исключение. Но, применяя такой прием, нужно 
иметь в виду некоторые тонкости (о них — в следующем разделе). 
    Третий, и последний, вариант — приложение возобновляет выполнение с 
инструк-. ции, следующей за блоком except. Именно так и происходит, когда 
фильтр исключений определен как EXCEPTION_EXECUTE_HANDLER. По окончании 
выполнения кода в блоке except управление передается на первую строку за этим блоком. 

    EXCEPTION_CONTINUE_EXECUTION 

    Давайте приглядимся к тому, как фильтр исключений получает один из трех 
идентификаторов, определенных в ЕХСРТ.Н. В Funcmeister2 значение 
EXCEPTION_EXECUTE_HANDLER «зашито» (простоты ради) в код самого фильтра, но из 
него можно вызывать функцию, которая определяла бы нужный идентификатор. Взгляните: 

    540 _____________________________Windows для профессионалов 


    char g_szBuffer[100]; 
    void FunclinRooseveltl (void) { 
    int x = 0: 
    char *lpBuffer = NULL; 
    __try { 
    «IpBuffer = 'J'; 
    x = 5 / x: 
    __except (OilFilter1(&lpBuffer)) { 
    MessageBox(NULL, "An exception occurred", NULL, MB_OK); 
    } 
    MessageBox(NULL, "Function completed", NULL. MB_OK); 
    i 
    LONG OilFilter-l (char **lplpBuffer) { 
    if (*lplpBuffer == NULL) { 
    *lplpBuffer = g_szBuffer; 
    return(EXCEPTION_CONTINUE_EXECUTION); 
    f 
    return(EXCEPTION_EXECUTE_HANDLER); 
    } 

    В первый раз проблема возникает, когда мы пытаемся поместить '/' в буфер, 
на который указывает IpBuffer. К сожалению, мы не определили IpBuffer как 
указатель на наш глобальный буфер g_szBuffer — вместо этого он указывает на 
NULL. Процессор генерирует исключение и вычисляет фильтр исключений в блоке 
except, связанном с блоком try, в котором и произошло исключение. В блоке 
except адрес переменной IpBuffer передается функции OilFilterI. 
    Получая управление, OilFilterI проверяет, не равен ли *lplpBuffer значению 
NULL, и, если да, устанавливает его так, чтобы он указывал на глобальный буфер 
g_szBuffer. Тогда фильтр возвращает EXCEPTION_CONTINUE_EXECUTION. Обнаружив 
такое значение выражения в фильтре, система возвращается к инструкции, 
вызвавшей исключение, и пытается выполнить ее снова. На этот раз все проходит 
успешно, и '/ будет записана в первый байт буфера g_szBuffer. 
    Когда выполнение кода продолжится, мы столкнемся с проблемой в блоке try — 
теперь это деление на нуль. И снова система вычислит фильтр исключений. На 
этот раз *lplpBuffer не равен NULL, и поэтому OilFilterI вернет 
EXCEPTION_EXECUTE_HANDLER, что подскажет системе выполнить код в блоке except, 
и на экране появится окно с сообщением An exception occurred (произошло исключение). 
    Как видите, внутри фильтра исключений можно проделать массу всякой работы. 
Но, разумеется, в итоге фильтр должен вернуть один из трех идентификаторов. 

    Будьте осторожны с EXCEPTION_CONTINUE_EXECUTION 

    Будет ли удачной попытка исправить ситуацию в только что рассмотренной 
функции и заставить систему продолжить выполнение программы, зависит от типа 
процессора, от того, как компилятор генерирует машинные команды при трансляции 
операторов С, и от параметров, заданных компилятору. 
    Компилятор мог сгенерировать 2 машинные команды для оператора: 

    «IpBuffer = 'J': 


    _________________Глава 16. Структурная обработка исключений 541 

    Первая загрузила бы содержимое IpBuffer в регистр, а вторая попыталась бы 
записать Т по адресу, который хранится в этом регистре. Последняя команда и 
возбудила бы исключение. Фильтр исключений, перехватив его, исправил бы 
значение IpBuffer и указал системе повторить последнюю команду. Но проблема в 
том, что содержимое регистра не изменится так, чтобы отразить новое значение 
IpBuffer, и поэтому повторение команды снова приведет к исключению. Вот и 
бесконечный цикл! 
    Выполнение программы благополучно возобновится, если компилятор 
оптимизирует код, но может прерваться, если компилятор код не оптимизирует. 
Обнаружить такого «жучка» крайне трудно, и — чтобы определить, откуда он 
взялся в программе, — придется проанализировать ассемблерный текст, 
сгенерированный для исходного кода. Вывод: будьте очень-очень осторожны, 
возвращая EXCEPTION_CONTINUE_EXECUTION из фильтра исключений. 

    EXCEPTION_CONTINUE_SEARCH 

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

    void FunclinRoosevelt2 (void) { 
    char *lpBuffer = NULL: 
    -_try { 
    FuncSinatra2(lpBuffer): 
    } 
    „except (OilFilter2(&lpBuffer)) { 
    MessageBox(NULL, ...); 
    } 
    void FuncSinatra2 (char *sz) { 
    *sz = 0; 
    } 
    
    LONG OiiFilter2(char **ipipBuffer) { 
    if (*lplpBuffer == NULL) { 
    *lplpBuffer = g_szBuffer; 
    return(EXCEPTION_CONTINUE_EXECUTION); 
    } 
    return(EXCEPTION_EXECUTE_HANDLER): 
    } 

    При выполнении FunclinRoosevelt2 вызывается FuncSinatra2, которой 
передается NULL. Последняя приводит к исключению. Как и раньше, система 
проверяет выражение в фильтре исключений, связанном с последним исполняемым 
блоком try. В нашем примере это блок try в FunclinRoosevelt2, поэтому для 
оценки выражения в фильтре исключений система вызывает OUFilter2 — хотя 
исключение возникло в FuncSinatra2. Замесим ситуацию еще круче, добавив другой 
блок try-except. 

    void FunclinRooseveltS (void) { 
    char *lpBuffer = NULL; 
    —try { 
    FuricSinatra3( IpBuffer); 
    } 

    см. след. стр. 


    542 _____________________________Windows для профессионалов 


    „except (OilFilter3(&lpBuffer)) { 
    MessageBox(NULL, ...); 
    } 
    void FuncSinatra3 (char *sz) { 
    __try { 
    *sz = 0; 
    } 
    „except (EXCEPTION_CONTINUE_SEARGH) { 
    // этот код никогда не исполняется 
    LONG OilFilter3 (char **lplpBuffer) { 
    if (*lplpBuffer == NULL) { 
    *lplpBuffer = g_szBuffer; 
    return(EXCEPTION_CONTINUE_EXECUTION); 
    } 
    return(EXCEPTION_EXECUTE_HANDLER); 
    } 

    Теперь, когда FuncSinatra3 пытается занести 0 по адресу NULL, по-прежнему 
возбуждается исключение, но в работу вступает фильтр исключений из 
FuncSinatra3. Значение этого очень простого фильтра — 
EXCEPTION_CONTINUE_SEARCH. Данный идентификатор указывает системе перейти к 
предыдущему блоку try, которому соответствует блок except, и обработать его фильтр. 
    Так как фильтр в FuncSinatra3 дает EXCEPTION_CONTINUE_SEARCH, система 
переходит к предыдущему блоку try (в функции FunclinRoosevelt3) и вычисляет 
его фильтр OilFilter3. Обнаружив, что значение IpBuffer равно NULL, OilFilter3 
меняет его, чтобы оно указывало на глобальный буфер, и сообщает системе 
возобновить выполнение с инструкции, вызвавшей исключение. Это позволит 
выполнить код в блоке try функции FuncSinatra3, но, увы, локальная переменная 
sz в этой функции не изменена — и возникает новое исключение. Опять 
бесконечный цикл! 
    Заметьте, я сказал, что система переходит к последнему исполнявшемуся 
блоку try, которому соответствует блок except, и проверяет его фильтр. Это 
значит, что любые блоки try, которым соответствуют блоки finally (а не 
except), система, просматривая цепочки блоков, пропускает. Причина ясна: у 
блоков finally нет фильтров исключений, а потому и проверять в них нечего. 
Если бы в последнем примере FuncSinatra3 содержала вместо except блок finally, 
система начала бы проверять фильтры исключений с OilFilter3 в FunclinRoosevelt3. 
    Блок-схема на рис. 16-2 иллюстрирует операции, выполняемые системой после 
генерации исключения. 

    Глобальная раскрутка 

    Обработка исключений включает в себя глобальную раскрутку, выполняемую, 
когда фильтр исключений возвращает EXCEPTION_EXECUTE_HANDLER. Она приводит к 
продолжению выполнения всех незавершенных блоков try-finally, исполнение 
которых началось вслед за блоком try-except, обрабатывающим данное исключение. 
Рассмотрим для примера пару функций (см. с. 544): 

    544 _____________________________Windows для профессионалов 


    void FuncOStimpyI (void) { 
    // 1. Что-то делаем здесь 
    __try i 
    // 2. Вызываем другую функцию 
    FuncORen1( ); 
    // этот код никогда не исполняется 
    i 
    „except ( /* 6. Проверяем фильтр исключений */ EXCEPTION_EXECUTE_HANDLER) { 
    // 8. После раскрутки выполняется этот обработчик 
    MessageBox(NULL, ...): 
    } 
    // 9. Исключение обработано - продолжаем выполнение 
    void FuncORenI (void) { 
    DWORD dwTemp = 0; 
    // 3. Что-то делаем здесь 
    __iry { 
    // 4. Запрашиваем разрешение на доступ к защищенным данным 
    WaitForSingleObject(g_hSem, INFINITE); 
    // 5. Изменяем данные, и здесь генерируется исключение 
    g_dwProtectedData = 5 / dwTemp; 
    } 
    „finally { 
    // 7. Происходит глобальная раскрутка, так как 
    // фильтр возвращает EXCEPTION_EXECL)TE_HANDLER 
    // Даем и другим попользоваться защищенными данными 
    ReleaseSemaphore(g_hSem, 1, NULL); 
    i. 
    // продолжение обработки; сюда мы не попадаем 

    FuncOStimpyI и FuncORenI иллюстрируют самые запутанные аспекты структурной 
обработки исключений. Номера в начале комментариев показывают порядок 
выполнения, но «возьмемся за руки, друзья», и пойдем вместе. 
    FuncOStimpyI начинает исполнение со входа в блок try и вызова FuncORenI. 
Последняя входит в свой блок try и ждет семафора. Завладев им, она пытается 
изменить значение глобальной переменной g_dwProtectedData. Но деление на нуль 
возбуждает исключение. 

    _________________Глава 16. Структурная обработка исключений 545 

    Система, перехватив управление, ищет блок try, которому соответствует блок 
except. Поскольку блоку fry функции FuncORenI соответствует блок finally, 
система продолжает поиск и находит блок fry в FuncOStimpyI, которому как раз и 
соответствует блок except. 
    Тогда система проверяет значение фильтра исключений в блоке except функции 
FuncOStimpyI. Обнаружив, что оно — EXCEPTION_EXECUTE_HANDLER, система начинает 
глобальную раскрутку с блока finally в функции FuncORenI. Заметьте: раскрутка 
происходит до выполнения кода из блока except в FuncOStimpyI. Осуществляя 
глобальную раскрутку, система возвращается к последнему незавершенному блоку 
fry и ищет теперь блоки try, которым соответствуют блоки finally. В нашем 
случае блок finally находится в функции FuncORenI. 
    Мощь SEH по-настоящему проявляется, когда система выполняет код finally в 
FuncORenI. Из-за его выполнения семафор освобождается, и поэтому другие потоки 
получают возможность продолжить работу. Если бы вызов Releasesemaphore в блоке 
finally отсутствовал, семафор никогда бы не освободился. 
    Завершив исполнение блока finally, система ищет другие незавершенные блоки 
finally. В нашем примере таких нет. Дойдя до блока except, обрабатывающего 
исключение, система прекращает «восходящий» проход по цепочке блоков. В этой 
точке глобальная раскрутка завершается, и система может выполнить код в блоке except. 
    Блок-схема на рис. 16-3 поясняет, как система осуществляет глобальную раскрутку. 
    Вот так и работает структурная обработка исключений. Вообще-то, SEH — 
штука весьма трудная для понимания: в выполнение Вашего кода вмешивается 
операционная система. Код больше не выполняется последовательно, сверху вниз; 
система устанавливает свой порядок — сложный, но все же предсказуемый. 
Поэтому, следуя блок-схемам на рис. 16-2 и 16-3, Вы сможете уверенно применять SEH. 

    Остановка глобальной раскрутки 

    Глобальную раскрутку, осуществляемую системой, можно остановить, если в 
блок finally включить оператор return. Взгляните: 

    void FuncMonkey (void) { 
    __try { 
    FuncFish(); 
    } 
    „except (EXCEPTION_EXECUTE_HANDLER) { 
    MessageBeep(O): 
    } 
    MessageBox(.,. )• } 
    void FuncFish (void) { 
    FuncPheasantO; 
    MessageBox(...); 
    } 
    void FuncPheasant (voici) { 
    „try { 
    strcpy(NULL, NULL); 
    i 
    __finally { 
    return:                                                                 / 
    } 


    546 ______________________________Windows для профессионалов 


    Начать    > глобальную  | раскрутку  f 
    Запомнить местонахождение j блока fry, фильтр      gi которого вернул      
 ; 
    EXCEPTION EXECUTE_HANDLER' 
    ——Т—-' 
    Найти самый «нижний» из вложенных блоков fry 
    ————\———— 
    Выполнить    \                   п          /
    , „       \                  Достигнут ли блок 
    код обработчика L^__ дд —    fry, обрабатывающий   ^——————————————— 
    в блоке     1            ^К.              о    .^г 
    f               -S^ исключение? ^^^ 
    НЕТ 
    \ 
    Соответствует                          „ . 
    ,     '          __ „.-т _^    Найти предыдущий 
    „       ли блоку fry       ^—— НЕТ —>•             - г   . 
    ^s^     г   [• II о     ^г                 вложенный блок fry 
    -S^   блок hnally?   ^^ 
    ДА 
    Выполнить код в блоке finally 

    Рис. 16-3. Так система проводит глобальную раскрутку 

    При вызове strcpy в блоке try функции FuncPheasant из-за, нарушения 
доступа к памяти генерируется исключение. Как только это происходит, система 
начинает просматривать код, пытаясь найти фильтр, способный обработать данное 
исключение. Обнаружив, что фильтр в FuncMonkey готов обработать его, система 
приступает к глобальной раскрутке. 
    Такая раскрутка начинается с выполнения кода в блоке finally функции 
FuncPheasant. Но этот блок содержит оператор return. Он заставляет систему 
прекратить раскрутку, и 

    ________________Глава 16. Структурная обработка исключений 547 

    FuncPheasant фактически завершается возвратом в FuncFish, которая выводит 
сообщение на экран. Затем FuncFish возвращает управление FuncMonkey, и та 
вызывает MessageBox. 
    Заметьте: код блока except в FuncMonkey никогда не вызовет MessageBeep. 
Оператор return в блоке finally функции FuncPheasant заставит систему вообще 
прекратить раскрутку, и поэтому выполнение продолжится так, будто ничего не произошло. 
    Microsoft намеренно вложила в SEH такую логику. Иногда ведь нужно 
прекратить раскрутку и продолжить выполнение программы. Хотя в большинстве 
случаев так все же не делают. А значит, будьте внимательны и избегайте 
операторов return в блоках finally. 

    Еще несколько слов о фильтрах исключений 

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

    __try { 
    х = 0; 
    у = 4 / х; 
    „except ((GetExceptionCodeO == EXCEPTION_INT_DIVIDE_BY_ZERO) ? 
    EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH) { 
    // обработка деления на нуль 

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

    DWORD GetExceptionCode(VOID); 

    Ниже приведен список всех предопределенных идентификаторов исключений с 
пояснением их смысла (информация взята из документации на Win32). Эти 
идентификаторы содержатся в заголовочном файле Win32 WINBASE.H. 
    EXCEPT10N_ACCESS_VIOLAT10N Поток пытался считать или записать по 
виртуальному адресу, не имея на то необходимых прав. 
    EXCEPTION_ARRAY_BOUNDS_EXCEEDED Поток пытался обратиться к элементу 
массива, индекс которого выходит за элементы массива; при этом оборудование 
должно поддерживать такой тип контроля. 
    EXCEPTION_BREAKPOINT Встретилась точка останова. 
    EXCEPTION_DATATYPE_MISAL1GNMENT Поток пытался считать или записать 
невыровненные данные на оборудовании, которое не поддерживает автоматическое 
выравнивание. Например, 16-битные значения должны быть выровнены по 
двухбайтовым границам, 32битные — по четырехбайтовым и т. д. 
    EXCEPTION_FLT_DENORMAL_OPERAND Один из операндов в операции с плавающей 
точкой не нормализован. Ненормализованными являются значения, слишком малые 
для стандартного представления числа с плавающей точкой. 
    EXCEPTION_FLT_DIVIDE_BY_ZERO Поток пытался поделить число с плавающей 
точкой на делитель с плавающей точкой, равный 0. 

    548 _____________________________Windows для профессионалов 

    EXCEPTION_FLT_INEXACT_RESULT Результат операции с плавающей точкой нельзя 
точно представить в виде десятичной дроби. 
    EXCEPTION_FLT_INVALID_OPERATION Любое другое исключение, относящееся к 
числам с плавающей точкой, не включенное в этот список. 
    EXCEPTION_FLT_OVERFLOW Порядок результата операции с плавающей точкой 
превышает максимальную величину для указанного типа данных. 
    EXCEPTION_FLT_STACK_CHECK Переполнение стека или выход за его нижнюю 
границу в результате выполнения операции с плавающей точкой. 
    EXCEPTION_FLT_UNDERFLOW Порядок результата операции с плавающей точкой 
меньше минимальной величины для указанного типа данных. 
    EXCEPTION_GUARD_PAGE Поток пытался обратиться к странице памяти с 
атрибутом защиты PAGE_GUARD. Страница становится доступной, и генерируется 
данное исключение. 
    EXCEPTION_ILLEGAL_INSTRUCTION Поток выполнил недопустимую инструкцию. Это 
исключение определяется архитектурой центрального процессора; можно ли 
перехватить выполнение неверной инструкции, зависит от типа процессора. 
    EXCEPTION_IN_PAGE_ERROR Ошибку страницы нельзя обработать, так как 
файловая система или драйвер устройства сообщили об ошибке чтения. 
    EXCEPTION_INT_DIVIDE_BY_ZERO Поток пытался поделить целое число на 0. 
    EXCEPTION_INT_OVERFLOW Операция с целыми числами вызвала перенос старшего 
разряда результата. 
    EXCEPTION_INVALID_DISPOSITION Обработчик исключений вернул значение, 
отличное от 
    EXCEPTION_EXECUTE_HANDLER, EXCEPTION_CONTINUE_SEARCH или EXCEPTION_
    CONTINUE_EXECUTION. 
    EXCEPTION_NONCONTINUABLE_EXCEPTION Поток пытался возобновить исполнение 
после невозобновляемого исключения (noncontinuable exception). 
    EXCEPTION_PRIV_INSTRUCTION Поток пытался выполнить инструкцию, 
недопустимую в данном режиме процессора. 
    EXCEPTION_SINGLE_STEP Трассировочная ловушка или другой механизм 
пошагового исполнения подал сигнал о выполнении одной машинной команды.       __ 
    EXCEPTION_STACK_OVERFLOW Стек, отведенный пользователю, исчерпан. 
    Встраиваемую функцию GetExceptionCode можно вызвать только из фильтра 
исключений (между скобками, которые следуют за _except) или из обработчика 
исключений. Скажем, такой код недопустим: 

    __try { 
    У = 0; 
    х - 4 / у: 
    __except { 
    ((GetExceptlonCodeO == EXCEPTION_ACCESS_VIOLATION) :; 
    (GetExceptlonCodeO == EXCEPTION_INT_DIVIDE_BY_ZERO)) ? 
    EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH) { 


    _________________Глава 16. Структурная обработка исключений 549 


    switch (GetExceptionCodeO) { 
    case EXCEPTION_ACCESS_VIOLATION: 
    // обработка нарушения доступа к памяти 
    break; 
    case EXCEPTION_INT_DIVIDE_BY_ZERO: 
    // обработка деления целого числа на нуль 
    break; 
    | 
    ) 

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

    __try { 
    У = 0; 
    х = 4 / у; 
    } 
    „except (CoffeeFilter-0) { 
    // обработка исключения 
    LONG CoffeeFilter (void) { 
    
    // ошибка при компиляции: неправильный вызов GetExceptionCode 
    return((GetExceptionCodeO == EXCEPTION_ACCESS_VIOLATION) ? 
    EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH); 
    } 

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

    __try { 
    у -- 0; 
    х = 4 / у; 
    __except (CoffeeFilter(GetExceptionCodeO)) { 
    // обработка исключения 
    LONG CoffeeFilter(DWORD dwExceptionCode) { 
    return((dwExceptionCode == EXCEPTION_ACCESS_VIOLATION) ? 
    EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH); 
    } 

    Коды исключений формируются по правилам кодов ошибок в Win32, определенных 
в файле WINERROR.H. Каждое значение типа DWORD разбивается на поля, как 
показано в таблице: 

    550 _____________________________Windows для профессионалов 

    Биты        31-30             29-28            27-16          15-0 

    Содержимое: 
    Код степени           Флаги                Код подсистемы   Код исключения 

    «тяжести» (severity)                          (facility code) 

    Значение:     0 = успех             Бит 29               определяется      
Определяется     Microsoft          Microsoft или     программистом 
    1 = информация      0 = Microsoft 
    2 = предупреждение   1 = Пользователь 
    3 = ошибка           Бит 28 зарезервирован 
    (должен быть 0) 

    На сегодняшний день определены такие коды подсистемы: 

    Код подсистемы              Значение 

    FACILITY_NULL                 О 
    FACILITY_RPC                  1 
    FACILITY_DISPATCH             2 
    FACILITY_STORAGE              3 
    FACILITYJTF                   4 
    FACILITY_WIN32                7 
    FACILITY_WINDOWS            8 
    FACILITY_CONTROL             10 

    Следующая таблица содержит значения кодов исключений, определенных в системе: 

    Код исключения                                 Код               Степень «тяжести» 
    EXCEPTION_ACCESS_VIOLATION                OxCOOOOOOS        Ошибка 
    EXCEPTION_ARRAY_BOUNDS_EXCEEDED          OxC000008C        Ошибка 
    EXCEPTION_BREAKPOINT                      0х80000003         

Предупреждение _ 

    EXCEPTION_DATATYPE_MISALIGNMENT          0х80000002         Предупреждение 
    EXCEPTION_FLT_DENORMAL_OPERAND          OxC000008D        Ошибка 
    EXCEPTION_FLT_DIVIDE_BY_ZERO               OxCOOOOOSE        Ошибка 
    EXCEPTION_FLT_INEXACT_RESULT               OxC000008F        Ошибка 
    EXCEPTION_FLT_INVALID_OPERATION            ОхСОООООЗО        Ошибка 
    EXCEPTION_FLT_OVERFLOW                    OxC0000091         Ошибка 
    EXCEPTION_FLT_STACK_CHECK                 OxC0000032        Ошибка 
    EXCEPTION_FLT_UNDERFLOW                  ОхСОООООЗЗ        Ошибка 
    EXCEPTION_GUARD_PAGE                      0х80000001         Предупреждение 
    EXCEPTION JLLEGALJNSTRUCTION             OxCOOOOOlD        Ошибка 
    EXCEPTION JN_PAGE_ERROR                   ОхСООООООб        Ошибка 
    EXCEPTION_INT_DIVIDE_BY_ZERO               OxC0000094        Ошибка 
    EXCEPTION JNT_OVERFLOW                    OxC0000035        Ошибка 
    
    см. след. стр. 

    _________________Глава 16. Структурная обработка исключений 551 


    (продолжение) 

    Код исключения                           Код                Степень «тяжести» 
    
    EXCEPTION_INVALID_DISPOSITION               OxC0000026         Ошибка 
    EXCEPT10N_NONCONTINUABLE_EXCEPTION      OxC0000025         Ошибка 
    EXCEPTION_PRIV_INSTRUCTION                 OxC0000096         Ошибка 
    EXCEPTION_SINGLE_STEP                       0х80000004         Предупреждение 
    EXCEPTION_STACK_OVERFLOW                  OxCOOOOOFD        Ошибка 


    Функция GetExceptionlnformation 

    Когда возникает исключение, операционная система помещает в стек 
соответствующего потока структуры EXCEPTION_RECORD, CONTEXT и EXCEPTION_POINTERS. 
    EXCEPTION_RECORD содержит информацию об исключении, независимую от типа 
процессора, a CONTEXT — машинно-зависимую информацию об этом исключении. В 
структуре EXCEPTION_POINTERS всего 2 элемента — указатели на помещенные в стек 
структуры EXCEPTION_RECORD и CONTEXT: 

    typedef struct _EXCEPTION_POINTERS { 
    PEXCEPTION_RECORD ExceptionRecord; 
    PCONTEXT ContextRecord; 
    } EXCEPTION_POINTERS; 

    Чтобы получить эту информацию и использовать ее в программе, вызовите GetExceptionlnformation: 

    LPEXCEPTION_POINTERS GetExceptionlnformation(VOID); 

    Эта встраиваемая функция возвращает указатель на структуру EXCEPTION_POINTERS. 
    Самое важное в GetExceptionlnformation то, что ее можно вызывать только в 
фильтре исключений и больше нигде, потому что структуры CONTEXT, 
EXCEPTION_RECORD и EXCEPTION_POINTERS существуют только во время проверки 
фильтра исключений. Когда управление переходит к обработчику исключений, 
данные в стеке разрушаются. 
    Если Вам нужно получить доступ к информации об исключении из обработчика, 
сохраните структуру EXCEPTION_RECORD и/или CONTEXT (на которые указывают 
элементы структуры EXCEPTION_POINTERS) в объявленных Вами переменных. Вот 
пример сохранения этих структур: 

    void FuncSkunk (void) { 
    
    // объявляем переменные, которые мы сможем потом использовать 
    // для сохранения информации об исключении (если оно произойдет) 
    EXCEPTION_RECORD SavedExceptRec; 
    
    GOTEXT SavedContext; 
    try { 
    „except { 
    
    SavedExceptRec = *(GetExceptionInformation())->ExceptionRecord, 
    SavedContext = *(GetExceptionInformation())->ContextRecord, 
    EXGEPnON_EXECUTE_HANDLER) { 

    см. след. стр. 

    552 _____________________________Windows для профессионалов 


    // мы можем теперь использовать переменные SavedExceptRec 
    // и SavedContext в блоке обработчика исключений 
    switch (SavedExceptRec.ExceptionCode) { 
    ( 
    } 

    В фильтре исключений используется оператор-запятая (,) — мало кто из 
программистов знает о нем. Он указывает компилятору, что выражения, отделенные 
запятыми, следует выполнять слева направо. После вычисления всех выражений 
возвращается результат последнего из них — крайнего правого. 
    В FuncSkunk сначала выполняется левое выражение, что приводит к сохранению 
находящейся в стеке структуры EXCEPTION_RECORD в локальной переменной 
SavedExceptRec. Результат выражения — значение SavedExceptRec. Но он 
отбрасывается, и вычисляется выражение, расположенное правее. Это приводит к 
сохранению размещенной в стеке структуры CONTEXT в локальной переменной 
SavedContext. И снова результат — значение SavedContext — отбрасывается, и 
вычисляется третье выражение. Оно равно EXCEPTION_EXECUTE_HANDLER — это и 
будет результатом всего выражения в скобках. 
    Так как фильтр возвращает EXCEPTION_EXECUTE_HANDLER, выполняется код в 
блоке except. К этому моменту переменные SavedExceptRec и SavedContext уже 
инициализированы, и их можно использовать в данном блоке. Важно, чтобы 
переменные SavedExceptRec и SavedContext были объявлены вне блока try. 
    Вероятно, Вы догадались, что элемент ExceptionRecord структуры 
EXCEPTION_POINTERS указывает на структуру EXCEPTION_RECORD: 

    typedef struct _EXCEPTION_RECORD { 
    DWORD ExceptionCode; 
    DWORD ExceptionFlags; 
    struct _EXCEPTION_RECORD *ExceptionRecord; 
    PVOID ExceptionAddress; 
    DWORD NumberParameters; 
    DWORD ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS]; 
    } EXCEPTIONJECORD: 

    Структура EXCEPTIONJRECORD содержит подробную машинно-независимую 
информацию о последнем исключении: 
    • ExceptionCode — код исключения. Это информация, возвращаемая функцией GetExceptionCode. 
    • ExceptionFlags — флаги исключения. На данный момент определены только 2 
значения: 0 (возобновляемое исключение) и EXCEPTION_NONCONTINUABLE 
(невозобновляемое исключение). Любая попытка возобновить работу программы 
после невозобновляемого исключения генерирует новое — EXCEPTION_NONCONTINUABLE_EXCEPTION. 
    • ExceptionRecord— указатель на структуру EXCEPTION_RECORD, содержащую 
информацию о другом необработанном исключении. При обработке одного исключения 
может возникнуть другое. Например, код внутри фильтра исключений может 
попытаться выполнить деление на нуль. Когда возникает серия вложенных 
исключений, записи с информацией о них могут образовывать связанный список. 
Исключение будет вложенным, если оно генерируется при обработке фильтра. В 
отсутствие необработанных исключений ExceptionRecord равен NULL. 

    _________________Глава 16. Структурная обработка исключений 553 

    • ExceptionAddress — машинный адрес инструкции, при выполнении которой 
произошло исключение. 
    • NumberParameters — количество параметров, связанных с исключением. Это 
число заполненных элементов в массиве Exceptionlnformation. 
    • Exceptionlnformation — массив дополнительных 32-битных аргументов, 
описывающих исключение. Для большинства кодов исключений элементы этого 
массива не определены. 
    Последние два элемента структуры EXCEPTION_RECORD сообщают фильтру 
дополнительную информацию об исключении. Сейчас такую информацию дает только 
один тип исключений: EXCEPTION_ACCESS_VIOLATION. Все остальные дают нулевое 
значение в элементе NumberParameters. Проверив его, Вы узнаете, сколько 
заполнено двойных слов с информацией об исключении. 
    При исключении EXCEPTION_ACCESS_VIOLATION элемент Exceptioninformation [0] 
содержит флаг, указывающий тип операции, вызвавшей нарушение доступа. Если его 
значение равно 0, поток пытался читать недоступную ему информацию; 1 — 
записывать по недоступному ему адресу. Exceptionlnformation [1] определяет 
виртуальный адрес недоступных данных. 
    Эта структура позволяет писать фильтры исключений, сообщающие значительный 
объем информации о работе программы. Можно создать, например, такой фильтр: 

    __try { 
    „except (ExpFltr(GetExceptionInformation()-> ExceptionRecord)) { 
    
    LONG ExpFltr (LPEXCEPTION_RECORD IpER) { 
    char szBuf[300], *p; 
    
    DWORD dwExceptionGode = lpER->ExceptionCode: 
    
    sprintf(szBuf, "Code = %x, Address = %x", 
    dwExceptionCode, lpER->ExceptionAddress); 
    
    // находим конец строки 
    р = strchr(szBuf, 0); 
    
    // я использовал оператор switch на тот случай, если Microsoft 
    // в будущем добавит параметры для других кодов исключений 
    switch (dwExceptionCode) { 
    
    case EXCEPTION_ACCESS_VIOLATION; 
    
    sprintf(p, "Attempt to %s data at address %x", 
    
    lpER->ExceptionInformation[0] ? "write" : "read", 
    lpER->ExceptionInformation[1]); 
    break; 
    default; 
    break; 
    MessageBox(NULL, szBuf, "Exception", MB_OK ; MB_ICONEXCLAMATION); 
    return(EXCEPTION_CONTINUE_SEARCH); 


    554 ____________________________Windows для профессионалов 

    Элемент ContextRecord структуры EXCEPTION_POINTERS указывает на структуру 
CONTEXT, содержимое которой зависит от типа процессора. Например, для 
процессоров х8б: 

    typedef struct „CONTEXT { 
    // флаги, описывающие содержимое структуры CONTEXT 
    DWORD ContextFlags; 
    
    // отладочные регистры 
    DWORD DrO; 
    DWORD Dr1; 
    DWORD Dr2; 
    DWORD Dr3; 
    DWORD Dr6; 
    DWORD Dr7; 
    // регистры с плавающей точкой 
    FLOATING_SAVE_AREA FloatSave: 
    // сегментные регистры 
    DWORD SegGs: 
    DWORD SegFs; 
    DWORD SegEs; 
    DWORD SegDs; 
    // целочисленные регистры 
    DWORD Edi; 
    DWORD Esi; 
    DWORD Ebx; 
    DWORD Edx; 
    DWORD Ecx; 
    DWORD Eax; 
    
    // управляющие регистры 
    DWORD Ebp: 
    DWORD Eip; 
    DWORD SegCs; 
    DWORD EFlags; 
    DWORD Esp; 
    DWORD SegSs; 
    } CONTEXT: 

    С помощью этой структуры, в основном содержащей по одному элементу для 
каждого регистра процессора, можно получить дополнительную информацию о 
возникшем исключении. Увы, это потребует написания машинно-зависимого кода, 
способного распознавать тип процессора и использовать подходящую для него 
структуру CONTEXT. Поэтому лучше поместить в код набор директив #ifdefw\z 
разных типов процессоров. Структуры CONTEXT для процессоров х86, MIPS, Alpha и 
PowerPC содержатся в заголовочном файле WINNT.H. 

    Программа-пример SEHExcpt 

    Программа SEHExcpt (SEHEXCPT. ЕХЕ) (см. листинг на рис. 16-4) 
демонстрирует применение фильтров и обработчиков исключений. Файлы исходного 
кода и ресурсов для этой 

    _________________Глава 16. Структурная обработка исключений 555 

    программы находятся в каталоге SEHEXCPT на компакт-диске, прилагаемом к 
книге. Запустив ее. Вы увидите диалоговое окно: 

    Clicking Execute reserves an array of БО 4-KB structures and          | 
randomly writes to elements in the array. 
    Number of writes to perform:      IjTifflIExecute| Execution loa: 

    После щелчка кнопки Execute программа вызовет VirtualAlloc, чтобы 
зарезервировать в адресном пространстве процесса регион памяти, необходимый 
для размещения массива из 50 элементов, каждый по 4 Кб. Заметьте: не передать, 
а зарезервировать. 
    Зарезервировав регион, программа заполняет случайно выбранные элементы 
массива. Вы задаете число попыток, набирая соответствующее значение в поле 
ввода Number Of Writes To Perform (число попыток записи). 
    При записи в первый случайно выбранный элемент произойдет исключение — 
нарушение доступа, так как память зарезервирована, но не передана. В этот 
момент операционная система вызывает фильтр исключений — функцию ExpFilter из 
файла SEHEXCPT.С. 
    Этот фильтр отвечает за повторный вызов VirtualAlloc, на этот раз ей 
передается МЕМ_СОММГТ, чтобы она передала память зарезервированному региону. 
Но сначала фильтру надо определить, что исключение, «фильтрируемое» в данный 
момент, вызвано нарушением доступа к памяти в зарезервированном регионе. 
    Важно, чтобы программа не просто слепо «поглощала» исключения. Создавая 
фильтр исключений, проверяйте всеми способами, что обрабатывается именно то 
исключение, для которого этот фильтр и предназначен. Если фильтр не может 
обработать какое-то исключение, он должен вернуть EXCEPTION_CONTINUE_SEARCH. 
    Функция ExpFilter определяет, вызвано ли исключение неправильным доступом 
к массиву, и для этого проверяет: 
    1. Равен ли код исключения значению EXCEPTION_ACCESS_VIOLATION? 
    2. Зарезервирована ли память под массив на момент исключения? (Исключение 
могло возникнуть до резервирования памяти.) 
    3. Попадает ли адрес, по которому возникло нарушение доступа, в 
зарезервированный для массива регион памяти? 
    Если какая-то из проверок не проходит, значит, фильтр не предназначен для 
обработки этого исключения; он возвращает EXCEPTION_CONTINUE_SEARCH. Если же 
истинны все 3 условия, фильтр «считает», что был неверный доступ к массиву, и 
вызывает функцию CommitMemory. Она проверяет, произошло это при попытке записи 
или чтения, и генерирует строку, выводимую в окно списка Execution Log. Эта 
программа выполняет только 

    556 _____________________________Windows для профессионалов 

    запись в память. В заключение CommitMemory вызывает VirtualAlloc для 
передачи памяти тому элементу массива, к которому было обращение. 
    Когда CommitMemory передает управление фильтру исключений, тот возвращает 
EXCEPTION_CONTINUE_EXECUTION. Это приводит к повторному выполнению команды, 
вызвавшей исключение. На этот раз доступ к памяти будет успешным, так как она передана. 
    Я уже говорил, что надо быть очень внимательным, возвращая из фильтра 
ЕХСЕРTION_CONTINUE_EXECUTION, и что не исключены проблемы, если компилятор 
генерирует несколько машинных команд для одного оператора C/C++. В только что 
рассмотренном случае проблема не возникнет никогда. Этот пример гарантированно 
работает на любой процессорной платформе независимо от конкретного языка 
программирования или компилятора, потому что мы не изменяем никаких 
переменных, которые компилятор мог бы попытаться загрузить в регистры. 0'кей, 
а теперь посмотрим, что нам даст прогон программы. 

    Clicking Execute reserves an array of 50 4-KB structures and randomly 
writes to elements in the array, 
    lumber of writes to perform:      |i и и["' Execute '1 
    Execution log: 
    Execution started3 
    Writing index: A                                               ^ 
    —> Committing memory (write attempted) 
    Writing index: 11 
    —> Committing memory (write attempted) 
    Writing index: 11                                                  "• 
    —> Committing memory (write attempted)                        " 
    Writing index: 25 
    —> Committing memory (write attempted) 
    Writing index: 9 
    —> Committing memory (write attempted) 
    Writing index: 25 
    Writing index: 21 
    —> Committing memory (write attempted) 
    Writing index. 20                                              J-J 

    Здесь показаны результаты 100 случайных попыток записи в массив. Сначала 
память массиву не передается, что и является причиной вызова фильтра 
исключений для элементов массива с индексами 4, 11, 41, 25 и 9. Но затем вновь 
идет запись по индексу 25. На этот раз нарушения доступа не происходит, фильтр 
не вызывается. Теперь прокрутим список до конца: 

    Clicking Execute reserves an array of 50 4-KB structures end • randomly 
writes to elements in the array. 
    ^Limber of writes to perform:      h Q\~\f""^pp^g"""'| 
    Execution loo; 
    Writing ndex: AS                                            3 ) 
    Writing ndex: 13                                                j 
    Writing ndex: 21 
    Writing ndex: 1                                                  | 
    Writing ndex: 27 
    Writing ndex: 15                                             j 
    —> Committing memory (write attempted) 
    Writing ndex: 33 
    Writing ndex: 13                                           'Я 
    Writing ndex: A2                                              Щ 
    Writing ndex: 21                                             ^l| 
    Writing ndex: 1                                                 "• | 
    Writing index: At                                              , 
    Writing index; 9 
    Execution ended                                        3 


    _________________Глава 16. Структурная обработка исключений 557 

    В конце серии из 100 попыток доступа возникает крайне мало исключений, так 
как большинство индексов массива уже выбрано и память для соответствующих 
элементов передана. 
    В исходном тексте программы Вы заметите, что я поместил в тело функции 
ExpFilter блок try-finally. Сделано это, чтобы показать: использование SEH в 
самом фильтре исключений вполне допустимо и даже полезно. Допустимо и вложение 
блоков try-finally или tryexcept внутрь блоков finally или except, более того, 
возможно даже вложение блоков try-finally и try-except друг в друга. Вложение 
обработчиков исключений в фильтр исключений я продемонстрирую позже — на 
примере программы SEHSoft. 
    Как и в программе SEHTerm, поэкспериментируйте с SEHExcpt, меняя число 
обращений к массиву, и посмотрите, как при этом меняется количество вызовов 
фильтра исключений. 

    SEHEXCPT.C 
    Модуль: SEHExcpt.С 
    Автор: Copyright © 1995-1997, Джеффри Рихтер (Jeffrey Richter) 
    „*******.***..*...».,*,,**,.**.***»***,*..**.**..**,.*.*.*,..**..*.,..*..**../ 
    Sinclude ".,\CmnHdr,H"           /* см. приложение В */ 
    ftinclude <windows,h> 
    #include <windowsx.h> 
    ftinclude <tchar.h> 
    Binclude <stdio.h>               // для sprintf 
    ^include <stdlib.n>              // для rand 
    ^include "Resource.H" 
    IIIIIIIIIIIIIIIIIIIIIHIIIIIItlllllllllllllllllllllllltlllllllHIIIIIIIIIIIIII/ 
    «define NUMELEMENTS    (50) 
    
    // объявляем размер каждого элемента массива по 4 Кб 
    typedef struct { 
    BYTE bReserved[4 * 1024]; 
    } ELEMENT, *LPELEMENT; 
    IIIIIIIIIIHIIIIIIIIIIIIIIItllllll/lllllllllllllllllllllll/lll/llllllllllllllll 
    void CommitMemory (HWND hwndLog, LPEXCEPTION_POINTERS IpEP, 
    LPBYTE IpbAttemptedAddr) { 
    BOOL fAttemptedWrite; 
    TCHAR szBur[100]; 
    // узнаем, была ли попытка доступа к памяти 
    fAttemptedWrite = (BOOL) lpEP->ExceptionRecord->ExceptionInformation[0]; 
    
    // добавим строку в окно списка Execution Log 
    _stprintf(szBuf, __TEXT("--> Committing memory (%s attempted)"), 
    fAttemptedWrite ? __TEXT("write") : __TEXT("read")); 

    си. след. стр. 

    Рис. 16-4. Листинг программы SEHExcpt 

    558 _____________________________Windows для профессионалов 

    РИС. 16-4  (продолжение) 

    ListBox_AddString(hwndLog, szBuf); 
    
    // Эта попытка доступа к памяти произошла в момент, когда программа 
    // обращалась к элементу нашего массива. Попробуем передать память для 
    // отдельного элемента зарезервированного адресного пространства массива. 
    VirtualAllocdpbAttemptedAddr, sizeot'(ELEMENT), MEM_COMMIT, PAGE_REAOWRITE): 
    \ 
    f 
    ///////////////////////// /////////////////////////// /////////////////////////// 
    
    int ExpFilter (LPEXCEPTION_POINTERS IpEP, LPBYTE IpbArray, 
    LONG INumBytesInArray, HWND hwndLog) { 
    LPBYTE IpbAtteffiptedAddr = NULL; 
    
    // получаем код исключения, объясняющий причину вызова фильтра 
    DWORD dwExceptionCode = lpEP->ExceptionRecord->ExceptionCode; 
    
    // допустим, этот фильтр НЕ обработает исключение и позволит 
    // системе продолжить поиск других фильтров 
    int nFilterResult = EXCEPTION_CONTINUE_SEARCH; 
    __try { 
    
    // Сначала надо определить, произошло ли исключение 
    // из-за попытки доступа к нашему массиву. Этот фильтр 
    // и обработчик не обрабатывают исключения другого типа. 
    
    if (dwExceptionCode != EXCEPTION_ACCESS_VIOLATION) { 
    
    // Если исключение не является нарушением доступа к памяти, 
    // его причина не связана с обращением к элементу массива. 
    // Система должна продолжить поиск другого фильтра. 
    nFilterResult = EXCEPTION_CONTINUE_SEARCH; 
    __leave; 
    i 
    ! 
    if (IpbArray == NULL) { 
    // либо исключение возникло до попытки резервирования 
    // адресного пространства, либо эта попытка была неудачной 
    nFilterResult = EXCEPTION_CONTINUE_SEARCH: 
    __leave; 
    } 
    
    // получаем адрес, по которому была попытка доступа 
    IpbAttemptedAddr = (LPBYTE) 
    1рЕР->ExceptionRecord->Except ion In format ion[1]; 
    if ((IpbAttemptedAddr < IpbArray) ;'. 
    ((IpbArray + INumBytesInArray) < IpbAttemptedAddr)) { 

    см. след. стр. 

    _________________Глава 16. Структурная обработка исключений 559 


    РИС. 16-4  (продолжение) 
    
    I / Адрес доступа расположен либо ДО зарезервированного 
    // под массив адресного пространства, либо ПОСЛЕ этого 
    // пространства. Пусть это исключение обработает 
    // какой-нибудь другой фильтр. 
    nFilterResult = EXCEPTION_CONTINUE_SEARCH; 
    __leave: 
    i 
    J 
    
    // этот фильтр обработает исключение 
    CommitMemory(hwndLog, IpEP, IpbAttemptedAddr); 
    
    // Сейчас память передана. Давайте снова выполним инструкцию, 
    // которая и вызвала исключение. На этот раз она пройдет успешно 
    // и не вызовет нового исключения. 
    nFilterResult = EXCEPTION_CONTINUE_EXECUTION: 
    } 
    „finally { 
    } 
    
    // теперь, когда память передана, мы можем возобновить исполнение 
    // с инструкции, вызвавшей исключение в самом начале 
    return(nFilterResult); 
    } 
    l/lllll lllllllillllll lllllllll/111/llllll/IHIIHIIIIIIIIIIHIIIIIIIIIIIIIIIIII 
    void Dlg_ReserveArrayAndAccessIt (HWND hwndLog, int nNumAccesses) { 
    LPELEMENT IpArray = NULL; 
    ELEMENT Element; 
    TCHAR szBuf[100]; 
    int nElementNum; 
    const LONG INumBytesInArray = sizeof(ELEMENT) * NUMELEMENTS; 
    
    // очищаем окно списка Execution Log 
    ListBox_ResetContent(hwndLog); 
    ListBox_AddString(hwndLog, __TEXT("Execution started")): 
    __try { 
    // резервируем адресное пространство, достаточное 
    // для размещения NUMELEMENTS структур ELEMENT 
    IpArray = VirtualAlloc(NULL, INumBytesInArray, MEM_RESERVE, PAGE_NOACCESS); 
    while (nNumAccesses--) { 
    
    // получаем случайный индекс элемента 
    nElementNum = rand() % NUMELEMENTS: 

    см. след. стр. 

    560 ______________________________Windows для профессионалов 


    РИС. 16-4  (продолжение) 
    
    I / попытка записи 
    _stprintf(szBuf, __ТЕХТ( "Writing index: %d"), nElementNum); 
    ListBox_AddString(hwndLog, szBuf); 
    
    // в этой строке возникнет исключение 
    lpArray[nElementNum] = Element: 
    } // while 
    
    // выполнение закончено 
    ListBox_AddString(hwndLog, __TEXT("Execution ended")); 
    
    // возвращаем переданную память и освобождаем массив ELEMENTS 
    VirtualFreedpArray, О, MEM_RELEASE); 
    } // -_try 
    __except ( 
    
    ExpFilter(GetExceptionInfomiation(), (LPBYTE) IpArray, 
    INumBytesInArray, hwndLog)) { 
    
    // поскольку фильтр никогда не возвращает 
    // EXCEPTION_EXECUTE_HANDLER, никаких 
    // действий в блоке except не требуется 
    } // „except 
    } 
    /////////////////////////////////////////////////////////////////////////////// 
    BOOL Dlg_OnInitDialog (HWND hwnd, HWND hwndFocus, LPARAM IParam) { 
    
    // связываем значок с диалоговым окном 
    chSETDLGICONS(hwnd, IDI_SEHEXCPT, IDI_SEHEXCPT); 
    
    // no умолчанию количество попыток доступа - 100 
    SetDlgItemInt(hwnd, IDC_NUMACCESSES, 100. FALSE); 
    return(TRUE); 
    } 
    IIIIIIII/II/IIIIIIIII!IIIIIHIII/III!III//I/I/II//IIIIIIIIIIIIIIIII/II/IIIIIII/ 
    
    void Dlg_OnCo(iimand (HWND hwnd, int id, HWND hwndCtI, UINT codeNotify) { 
    int nNumAccesses: 
    BOOL fTranslated; 
    
    switch (id) { 
    case IDOK; 
    
    nNumAccesses = GetDlgItemInt(hwnd, IDC_NUMACGESSES, 
    &fTranslated, FALSE); 

    см. след. стр. 

    _________________Глава 16. Структурная обработка исключений 561 


    РИС. 16-4  (продолжение) 

    111111111111111111111/11111111/1111111111111111111/11111111111/1111111111111111 
    BOOL CALLBACK Dlg_Proc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM 
IParam) { 
    switch (uMsg) { 
    chHANDLE_DLGMSG(hwnd, WM_INITDIALOG, Dlg_OnInitDialog); 
    chHANDLE_DLGMSG(hwnd, WM_COMMAND, Dlg_OnCo(nmand); 
    } 
    return(FALSE); 
    } 
    lllll l/IIIIIHIIIIHIIIIIIII/ll tll///il/// ll/lllllll/lllll lll/ll/llllll ii/lllli 
    int WINAPI _tWinMain (HINSTANCE hinstExe, 
    HINSTANCE hinstPrev, LPTSTR pszCmdLine, int nCmdShow) { 
    chWARNIFUNICODEUNDERWIN95(); 
    DialogBox(hinstExe, MAKEINTRESOURCE(IDD_SEHEXCPT), NULL, Dlg_Proc); 
    return(O); 
    i i 
    11111111111/1111111111111111111111 конец файла 111111111111111111111111111111111 


    Программа-пример SEHSum 

    Программа SEHSum (SEHSUM.EXE) (см. листинг на рис. 16-5) демонстрирует 
применение фильтров и обработчиков исключений для корректного восстановления 
после переполнения стека. Файлы исходного кода и ресурсов для этой программы 
находятся в каталоге SEHSUM на компакт-диске, прилагаемом к книге. Возможно, 
Вам придется вновь просмотреть раздел «Стек потока» в главе 7, чтобы понять, 
как работает эта программа. 
    Она суммирует числа от 0 до х, где х — число, введенное пользователем. 
Конечно, проще было бы написать функцию с именем Sum, которая вычисляла бы по формуле: 

    Sum = (х * (х + 1)) / 2; 

    Но для этого примера я сделал функцию Sum рекурсивной. 

    562 _____________________________Windows для профессионалов 

    При запуске появляется диалоговое окно: 

    Calculate the sum of the numbers в—————— 
    from 0 through & where x Is:       |i 
    .Calculate| Answer:  ? 

    В этом окне Вы вводите число и «нажимаете» кнопку Calculate (вычислить). 
Программа создает поток, единственная обязанность которого — сложить все числа 
от 0 до x. Пока он исполняется, первичный поток программы, вызвав 
WaitForSingkObject, просит систему не выделять ему процессорного времени. 
Когда новый поток завершается, система вновь выделяет процессорное время 
первичному потоку. Тот выясняет сумму, получая код завершения нового потока 
вызовом GetExitCode Thread, и — это очень важно — закрывает описатель нового 
потока, так что система сможет разрушить объект-поток, и утечки ресурсов не произойдет. 
    Далее первичный поток проверяет код завершения суммирующего потока. Если 
он равен UINT_MAX, значит, произошла ошибка: суммирующий поток переполнил стек 
при подсчете суммы; первичный поток тогда выведет окно с соответствующим 
сообщением. Если же код завершения отличен от UINT_MAX, суммирующий поток 
отработал успешно; код завершения и есть искомая сумма. В этом случае 
первичный поток просто отображает результат суммирования в диалоговом окне. 
    Теперь обратимся к суммирующему потоку. Его функция — SumThreadFunc. При 
создании первичный поток передает ему в единственном параметре р количество 
целых чисел, которые следует просуммировать. Затем его функция инициализирует 
переменную uSum значением UINT_MAX, т. е. изначально предполагается, что 
работа функции не завершится успехом. SumThreadFunc активизирует SEH так, 
чтобы перехватывать любое исключение, возникающее при выполнении потока. После 
чего — для вычисления суммы — вызывается рекурсивная функция Sum. 
    Если сумма благополучно вычислена, SumThreadFunc просто возвращает 
значение переменной uSum; оно и будет кодом завершения потока. Но, если при 
выполнении Sum возникает исключение, система сразу оценивает выражение в 
фильтре исключений. Иначе говоря, система вызывает FilterFunc, передавая ей 
код исключения. В случае переполнения стека этим кодом будет 
EXCEPTION_STACK_OVERFLOW. Чтобы увидеть, как программа обрабатывает 
исключение, вызванное переполнением стека, дайте ей просуммировать числа от 0 
до 44000. 
    Моя функция FilterFunc очень проста. Сначала она «предполагает», что 
возникшее исключение не имеет к ней никакого отношения. Фильтр должен вернуть 
EXCEPTION_CONTINUE_SEARCH, если не знает, как обработать текущее исключение. 
Далее он должен проверить, не является ли оно исключением 
EXCEPTION_STACK_OVERFLOW. Если да — вернуть EXCEPTION_EXECUTE_HANDLER. Тогда 
система «поймет», что фильтр ждал этого исключения и что надо выполнить код в 
блоке except. В данном случае значение ЕХСЕРTION_EXECUTE_HANDLER укажет на то, 
что ошибка произошла при выполнении функции Sum, и потоку теперь следует 
завершиться, вернув UINT_MAX (значение в uSumNum), — я делаю вид, будто Sum 
никогда не вызывалась. 
    И последнее, что хотелось бы обсудить: почему я выделил функцию Sum в 
отдельный поток вместо того, чтобы просто ввести SEH-фрейм в первичный поток и 
вызывать Sum из его блока try. На то есть 3 причины. 
    Во-первых, всякий раз, когда создается поток, он получает свой стек 
размером 1 Мб. Если бы я вызывал Sum из первичного потока, часть стекового 
пространства уже была бы занята, и функция не смогла бы использовать весь 
объем стека. Согласен, моя программа очень проста и занимает не слишком 
большое стековое пространство. А если программа посложнее? Легко представить 
ситуацию, когда Sum подсчитывает сумму целых чисел от ^ 

    _________________Глава 16. Структурная обработка исключений 563 

    до 1000 и стек вдруг оказывается чем-то занят — тогда его переполнение 
произойдет, скажем, еще при вычислении суммы от 0 до 750. Таким образом, 
работа функции Sum будет надежнее, если обеспечить ее полным стеком, не 
используемым другим кодом. 
    Вторая причина в том, что поток уведомляется об исключении «переполнение 
стека» лишь однажды. Если бы я вызывал Sum из первичного потока и произошло бы 
переполнение стека, то это исключение было бы перехвачено и корректно 
обработано. Но к тому моменту физическая память была бы передана под все 
зарезервированное адресное пространство стека, и в нем уже не осталось бы 
страниц с флагом защиты PAGE_GUARD. Начни пользователь новое суммирование, и 
функция Sum переполнит стек, а соответствующее исключение не будет возбуждено. 
Вместо этого возникло бы исключение «нарушение доступа», и корректно 
обработать эту ситуацию уже не удалось бы. 
    И последнее, почему я использую отдельный стек: физическую память, 
отведенную для него, можно освободить. Рассмотрим такой сценарий: пользователь 
просит функцию Sum вычислить сумму целых чисел от 0 до 30000. Это требует 
передачи региону стека весьма ощутимого объема памяти. Затем пользователь 
проводит несколько операций суммирования — максимум до 5000. И окажется, что 
стеку передан порядочный объем памяти, который больше не используется. А ведь 
эта физическая память выделяется из страничного файла. Так что лучше бы 
освободить ее и вернуть системе и другим процессам. Ну а завершая выполнение 
потока SumThreadFunc, система автоматически освобождает физическую память, 
переданную региону стека. 

    SEHSUM.C 
    /.*,.*****.,*»**..*»**..,.,**.*.*,,*.**..,,*.***.....**»*.*.*.***..*.,*,.*,*,*. 
    Модуль: SEHSum.C 
    Автор: Copyright © 1995-1997, Джеффри Рихтер (Jeffrey Richter) 
    ..*,..*.**..*.**.*,**..*,*..,.***...,.**.........*,...,****.,.,.,*„„...**,.*/ 
    ftinclude "..\CmnHdr.H"              /* см. приложение В */ 
    ftinclude <windows.h> 
    ftinclude <windowsx.h> 
    tfinclude <limits.h> 
    tfinclude <process.h>                // для _Peginthreadex 
    ftinclude <tchar.h> 
    «include "Resource.H" 
    ////////////////////////////////У////1//////'///,//////,'//.'// '/Л//7//Л '.•/:,'•'///• 
    
    II Пример вызова Sum для uNum от О до 9 
    // uNum: 0123456789... 
    // Sum: 0 1 3 6 10 15 21 28 36 45 ... 
    UINT Sum (UINT uNum) { 
    if (uNum == 0) 
    return(O); 
    // рекурсивный вызов Sum 
    return(uNum + Su(n(uNuin - 1)); 
    } 

    см. след. стр. 

    Рис. 16-5. Листинг программы SEHSum 

    564 _____________________________Windows для профессионалов 

    РИС. 16-5  (продолжение) 
    1111111111111111111111111111111111111111111111111111111111111111111111111111111 
    long FilterFunc (DWORD dwExceptionCode) { 
    
    // предположим, мы не знаем, как обработать 
    // это исключение; пусть система продолжает 
    // поиск обработчика 
    long IRet = EXCEPTION_CONTINUE_SEARCH; 
    
    if (dwExceptionCode == STATUS_STACK_OVERFLOW) { 
    // если исключение возбуждено из-за переполнения 
    // стека, то мы знаем, как его обработать 
    IRet = EXCEPTION_EXECUTE_HANDLER; 
    1 
    return(lRet); 
    } 
    lllll lllllllllll lllllllllll lllllllllll lllllliili illllllllil/' HIHIII Iiiililliil 
    
    II Отдельный поток, отвечающий за вычисление суммы. 
    // Я используй его по следующим причинам: 
    
    // 1, Отдельный поток получает собственный мегабайт стекового пространства. 
    // 2. Поток уведомляется о переполнении стека лишь однажды. 
    // 3. Память, выделенная для стека, освобождается по завершении потока. 
    DWORD WINAPI SumThreadFunc (PVOID p) { 
    
    // параметр р (типа DINT) определяет 
    // количество суммируемых чисел 
    UINT uSumNuffi = (UINT) р; 
    
    // uSum содержит сумму чисел от 0 до uSumNum. Если сумму вычислить 
    // не удалось, возвращается значение UINT_MAX. 
    UINT uSufn = UINT_MAX; 
    „try { 
    // для перехвата исключения "переполнение стека" 
    // функцию Sum надо выполнять в SEH-фрейме 
    uSum = Sum(uSumNum); 
    } 
    __except (FilterFunc(GetExceptionCodeO)) { 
    // Если мы попали сюда, то это потому, что перехватили 
    // переполнение стека. Здесь можно сделать все, что 
    // надо для корректного возобновления работы. Но, так как 
    // от этого примера больше ничего не требуется, кода 
    // в блоке обработчика нет. 

    см. след. стр. 

    .Глава 16. Структурная обработка исключений 565 

    РИС. 16-5  (продолжение) 
    
    I / кодом завершения потока является либо сумма первых uSumNum 
    // чисел, либо UINT_MAX в случае переполнения стека 
    return(uSum); 
    } 
    
    /ll//l//IIIHII/ll/l/ll///l///llllllllll/l/lll/ll/l/llllllill/llll///ll//lli/ll 
    BOOL Dlg_OnInitDialog (HWND hwnd, HWND hwndFocus, LPARAM IParam) { 
    
    // связываем значок с диалоговым окном 
    chSETDLGICONS(hwnd. IDI_SEHSUM, IDI_SEHSUM); 
    return(TRUE); 
    } 
    11 /111 /111 II I / / I /1 /! I! I! /1 / i / / i ' !! / /! /1 / i! i! 
    void Dlg_OnCo[iimand (HWND hwnd, int id, HWND hwndCtl. UINT codeNotify) { 
    DINT uSumNum, uSum; 
    BOOL fTranslated; 
    DWORD dwThreadId; 
    HANDLE hThread; 
    switch (id) { 
    case IDC_CALC: 
    // получаем количество целых чисел, которые 
    // пользователь хочет просуммировать 
    uSumNum = GetDlgItemInt(hwnd, IDC_SUMNUM, &fTranslated, FALSE); 
    :           // создаем поток (с собственным стеком), 
    // отвечающий за суммирование 
    hThread = chBEGINTHREADEX(NULL, 0, SumThreadFunc, 
    (PVOID) uSumNum, 0, &dwThreadId): 
    // код завершения - результат суммирования 
    
    // сначала надо дождаться, когда завершится поток 
    WaitForSingleObject(hThread, INFINITE); 
    
    // теперь можно получить код завершения потока 
    GetExitCodeThread(hThread, (PDWORD) &uSum): 
    // закончив, закрываем описатель потока, 
    // чтобы система могла разрушить объект-поток 
    CloseHandle(hThread); 
    // обновляем содержимое диалогового окна 
    if (uSum == UINT_MAX) { 

    566 _________________________...__. Windows для профессионалов 


    РИС. 16-5  (продолжение) 

    I / Если код завершения потока - UINT_MAX, значит, 
    // произошло переполнение стека. Обновляем диалоговое 
    // окно и выводим сообщение. 
    SetDlgItemText(hwnd, IDC„ANSWER, __TEXT("Error")); 
    chMB(_TEXT("The number is too big, ") 
    __TEXT("please enter a smaller number")); 
    } else { 
    // сумма вычислена успешно: 
    // обновляем диалоговое окно 
    SetDlgIteniInt(hwnd, IDC_ANSWER. uSum. FALSE): 
    } 
    break; 
    case IDCANCEL: 
    EndDialog(hwnd, id); 
    break: 
    } 
    /////////////////7/////////7////'//У///7///////////////////7///////////////////'/ 
    BOOL CALLBACK Dlg_Proc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM IParam) { 
    switch (uMsg) { 
    chHANDLE_DLGMSG(hwnd, WM_INITDIALOG, 01g_OnInitDialog); 
    chHANDLE_DLGMSG(hwnd, WM_COMMAND, Dlg_OnCommand); 
    } 
    return(FALSE): 
    } 
    /////////////////////'////////////////////////////////////////У//'/////////////// 
    int WINAPI „tWinMain (HINSTANCE hinstExe, 
    HINSTANCE hinstPrev, LPTSTR pszCmdLine, int nCmdShow) { 
    chWARNIFUNICODEUNDERWIN95(); 
    DialogBox(hinstExe, MAKEINTRESOURCE(IDO_SUMMATION), NULL, Dlg_Proc); 
    return(O); 
    } 
    111/lllll////llltl/lll//l//lll/lll конец файла / I I I I I / I f / I I I I 


    Программные исключения 

    До сих пор мы рассматривали обработку аппаратных исключений, при которой 
процессор перехватывает событие и возбуждает исключение. Зачастую полезно 
генерировать программные исключения; при этом операционная система или Ваше 
приложение возбуждает 

    _________________Глава 16. Структурная обработка исключений 567 

    свои исключения. Хороший пример — функция НеарАПос. При ее вызове можно 
указать флаг HEAP_GENERATE_EXCEPTIONS, и тогда НеарАНос, не сумев выполнить 
запрос на выделение памяти, возбудит программное исключение STATUS_NO_MEMORY. 
    Чтобы использовать это исключение, напишите код блока try так, будто 
выделение памяти всегда будет успешным; затем — в случае ошибки при выполнении 
данной операции — Вы сможете либо обработать исключение в блоке except, либо 
заставить функцию провести очистку, дополнив блок try блоком finally. 
    Программе незачем знать, какое исключение она обрабатывает: программное 
или аппаратное, так что блоки try-except и try-finally в обоих случаях 
реализуются одинаково. Вместе с тем можно самостоятельно возбуждать исключения 
в коде — так, как это делает НеарАПос. Для этого предназначена функция RaiseException: 

    VOID RaiseException(DWORD dwExceptionCode, DWORD dwExceptlonFlags, DWORD 
cArguments, LPDWORD IpArguments); 

    Первый параметр, dwExceptionCode, — код возбуждаемого исключения. НеарАПос 
передает в качестве этого параметра значение STATUS_NO_MEMORY. Используя 
собственные коды исключений, следуйте формату стандартных кодов ошибок, 
принятых в Win32 (см. таблицу в разделе «Еще несколько слов о фильтрах исключений»). 
    Определяя собственные коды исключений, заполните 4 поля переменной типа 
DWORD так: биты 31 и 30 должны содержать код степени «тяжести», бит 29 
устанавливается в 1 (0 зарезервирован для исключений, определенных Microsoft; 
например, STATUS_NO_MEMORY в функции HeapAlloc), бит 28 должен быть 0, биты с 
27 по 16 должны содержать код подсистемы (возможные варианты этих кодов 
определены Microsoft), а биты с 15 по О — какое-то значение, выбранное Вами 
для идентификации раздела приложения, в котором возникло исключение. 
    Второй параметр функции RaiseException — dwExceptionFlags — должен быть 
либо О, либо EXCEPTION_NONCONTINUABLE. Последнее значение сообщает системе, 
что после данного исключения возобновление работы программы недопустимо. 
Операционная система использует этот флаг, сигнализируя о критических 
(фатальных) ошибках. 
    HeapAlloc устанавливает флаг EXCEPTION_NONCONTINUABLE при возбуждении 
исключения STATUS_NO_MEMORY, чтобы указать системе: выполнение продолжить 
нельзя, и фильтр исключений не должен возвращать код 
EXCEPTION_CONTINUE_EXECUTION. Если возбуждается исключение такого типа, а 
фильтр все же возвращает ЕХСЕРTION_CONTINUE_EXECUTION, система генерирует 
новое исключение EXCEPTION_NONCONTINUABLE_EXCEPTION. 
    Все правильно: при обработке программой одного исключения вполне вероятно 
возбуждение нового исключения. И смысл в этом есть. Раз уж мы здесь 
остановились, замечу, что нарушение доступа к памяти возможно и в блоке 
finally, и в фильтре исключений, и в обработчике исключений. Когда происходит 
нечто подобное, система создает список исключений. Помните функцию 
GetExceptionInformation7 Она возвращает адрес структуры EXCEPTION_POINTERS. Ее 
элемент ExceptionRecord указывает на структуру EXCEPTION_RECORD, которая в 
свою очередь тоже содержит элемент ExceptionRecord. Он указывает на другую 
структуру EXCEPTION_RECORD, где содержится информация о предыдущем исключении. 
    Обычно система единовременно обрабатывает только одно исключение, и 
ExceptionRecord равен NULL. Но если исключение возбуждается при обработке 
другого исключения, то в первую структуру помещается информация о последнем 
исключении, а ее элемент ExceptionRecord указывает на аналогичную структуру с 
аналогичными данными о предыдущем исключении. Если есть и другие 
необработанные исключения, можно продолжить просмотр этого связанного списка 
структур EXCEPTION_RECORD, чтобы определить, как обработать конкретное исключение. 

    568 _____________________________Windows для профессионалов 

    Третий и четвертый параметры (cArguments и IpArguments) позволяют передать 
дополнительные данные о генерируемом исключении. Если Вам это не нужно, 
передайте NULL в IpArguments, тогда RaiseException проигнорирует параметр 
cArguments. А если Вы передаете дополнительные аргументы, cArguments должен 
содержать число элементов в массиве значений DWORD, на который указывает 
IpArguments. Значение cArguments не может превышать константы 
EXCEPTION_MAXIMUM_PARAMETERS (в файле WINNT.H она определена как 15). 
    При обработке исключения написанный Вами фильтр — чтобы узнать значения 
cArguments и IpArguments — может ссылаться на элементы NumberParameters и 
ExceptionInformation структуры EXCEPTION_RECORD. 
    Собственные программные исключения генерируют в приложениях по целому ряду 
причин. Например, чтобы посылать информационные сообщения в системный журнал 
событий (system event log). Как только Ваша программа «почувствует» в себе 
какую-то проблему, можно вызвать RaiseException; при этом обработчик 
исключений разместите выше по дереву вызовов, тогда — в зависимости от типа 
исключения — он будет либо заносить его в журнал событий, либо сообщать о нем 
пользователю. Вполне допустимо возбуждать программные исключения и для 
сигнализации о внутренних фатальных ошибках в приложении. Это гораздо проще, 
чем пытаться возвращать коды ошибок через все дерево вызовов. 

    Программа-пример SEHSoft 

    Программа SEHSoft (SEHSOFT.EXE) (см. листинг на рис. 16-6) демонстрирует, 
как создавать и использовать собственные программные исключения. Файлы 
исходного кода и ресурсов для этой программы находятся в каталоге SEHSOFT на 
компакт-диске, прилагаемом к книге. За ее основу взята рассмотренная ранее 
программа SEHExcpt. При запуске SEHSoft на экране появляется диалоговое окно: 

    ||Clicking Execute reserves an array of 50 ^"KB structures and 
    ijpandomly reads and writes to elements in the army 
    
    ;||yumberof reads/writes to perlornr      |ДДД|ExecuteI 
    : execution lofl: 

    Оно очень похоже на диалоговое окно программы SEHExcpt, но данная 
программа будет не только записывать в массив, но и читать из него. 
    Цикл в функции Dlg_ReserveArrayAndAccessIt (обращающийся к случайному 
элементу массива) изменен так, чтобы он сам выбирал следующее случайное число. 
Тем самым он узнает, должна ли программа записывать в элемент массива или 
читать из него. 

    _________________Глава 16. Структурная обработка исключений 569 

    В чем же смысл чтения элемента массива, если массив не инициализирован? 
Дело в том, что я модернизировал программу так, чтобы она автоматически 
обнуляла элемент массива, если передача памяти вызвана попыткой его чтения. И 
сделал это через программное исключение. 
    Вместо вызова CommitMemory в функции ExpFilter, как было в SEHExcpt, я 
поместил ее вызов внутрь блока try. Выражение в фильтре блока except, 
дополняющего этот блок try, проверяет, произошло ли исключение в результате 
вызова CommitMemory и равен ли код исключения значению SE_ZERO_ELEM. 
    SE_ZERO_ELEM определяется через #define и идентифицирует программное 
исключение, которое я описываю в начале файла SEHSOFT.C: 
    
    // полезный макрос для определения собственных кодов программных исключений 
    ffdefine MAKESOFTWAREEXCEPTION(Severity, Facility, Exception) \ 
    
    ((DWORD) ( \ 
    /* код степени "тяжести" */         (Severity      ) ! \ 
    /* MS(0) или пользователь^)*/      (1         «29) : \ 
    /* зарезервирован(О) */             (0         «28) ! \ 
    /* код подсистемы */                (Facility «16) ', \ 
    /* код исключения */                (Exception « 0))) 
    
    // Наше собственное программное исключение. Оно возбуждается, 
    // когда элемент массива должен быть инициализирован нулями. 
    ftdefine SE_ZERO_ELEM\ 
    MAKESOFTWAREEXCEPTION(ERROR_SEVERITY_ERROR,\ 
    FACILITY_NULL, 1) 
    Выражение в фильтре исключений выглядит так: 
    „except ((GetExceptionCodeO == SE_ZERO_ELEM) ? 
    (Saved ExceptRec = *((Get Except ion Information())->Except ion Record), 
    FXCEPTION_EXECUTE_HANDLER) : EXCEPTION_CONTINUE_SEARCH) { 
    } 

    Этот обработчик предназначен только для обработки исключений SE_ZERO_ELEM. 
Если GetExceptionCode возвращает другой код исключения, результатом работы 
фильтра будет EXCEPTION_CONTINUE_SEARCH. Если же код исключения равен 
SE_ZERO_ELEM, должно быть возвращено значение EXCEPTION_EXECUTE_HANDLER — 
чтобы выполнить код в блоке except. Так как блоку except нужна информация об 
исключении, перед возвратом вызывается GetExceptionInformation. Значение 
EXCEPTION_EXECUTE_HANDLER и информация, содержащаяся в структуре 
EXCEPTION_RECORD, сохраняются в локальной переменной SavedExceptRec. 
    Теперь, когда исполняется код в блоке except, из SavedExceptRec выбирается 
адрес элемента массива, подлежащего обнулению. Для выполнения последней 
операции вызывается функция memset. 
    Единственное, что осталось рассмотреть, — как генерируется исключение. 
Предназначенный для этого код находится в конце функции CommitMemory. 

    if (IfAttemptedWrite) { 
    // Программа пытается прочитать еще не созданный элемент 
    // массива. Возбуждаем собственное программное исключение 
    // для обнуления этого элемента перед использованием. 
    RaiseException(SE_ZERO_ELEM, 0, 1, (LPDWORD) &lpAttemptedAddr); 
    } 

    Попытка прочитать элемент заставляет программу вызвать RaiseException с 
передачей кода программного исключения SE_ZERO_ELEM и флага с нулевым 
значением. Кроме того, 

    570 _____________________________Windows для профессионалов 

    используя третий и четвертый параметры функции RaiseException, можно 
передать в фильтр исключений максимум EXCEPTION_MAXIMUM_PARAMETERS (15) 
параметров. В этом примере я хочу передать только один параметр — адрес 
обнуляемого элемента массива. Для этого я записываю 1 в третий параметр и 
адрес элемента — в четвертый. Вот как выглядит окно SEHSoft после выполнения программы: 

    Clicking Execute reserves an array of 50 4-KB structures and          | 
randomly reads and writes to elements in the array                  j 
    ЫиглЬег of reads/Writes to perform:      Ffoo|f "Execute I 
    • Execution log; 
    Execution started^| 
    :,. Writing index; 41                                          J 
    : —> Committing memory (write attempted) 
    Reading index: 34                                            ^ 
    —> Committing memory (read attempted)                       :•;:!: 
    —> Zeroed array element                                     dis: 
    Reading Index: 19                                         M 
    —> Committing memory (read attempted)                      j;' 
    —> Zeroed array element                                    : "f 
    , Reading index. 2B                                          , :; 
    ' —> Committing memory (read attempted)                      g 
    ;—> Zeroed array element                                   ||1 
    Reading index'12                                            '"!>. 
    —> Committing memory (read attempted) 
    —> Zeroed array element                                 J^J 

    Всякий раз, когда возбуждается программное исключение, обработчик вносит в 
список Execution Log запись о том, что этот элемент массива обнулен. 

    SEHSOFT.C 
    Модуль: SEHSoft.С 
    Автор: Copyright ® 1995-1997, Джеффри Рихтер (Jeffrey Richter) 
    ...,....,.,*.*.*****.*.*...*,.**..»...,,**»*******,.*.,***.*,.******.*****..**/ 
    ((include "..\CmnHdr.H"              /* см. приложение В */ 
    ((include <windows.h> 
    «include <windowsx.h> 
    ((include <tchar.h> 
    ((include <stdio.h>                  // для sprintf 
    #include <stdlib.h>                 //для rand 
    ((include "Resource.H" 
    /////////////////////////////////////7///////////////////////////////////////// 
    ((define NUMELEMENTS (50) 
    // объявляем массив с элементами по 4 Кб 
    typedef struct { 
    BYTE bReserved[4 * 1024]; 
    } ELEMENT, *LPELEMENT; 

    см. след. стр. 

    Рис. 16-6. Листинг программы SEHSoft 

    ______________Глава 16. Структурная обработка исключений 571 


    РИС. 16-6  (продолжение) 

    II полезный макрос для определения собственных кодов программных исключений 
    «define MAKESOFTWAREEXCEPTION(Severity, Facility, Exception) \ 
    ((DWORD) ( \ 
    /* код степени "тяжести" */         (Severity      ) ] \ 
    /* MS(0) или пользователь(1)*/      (1         «29) ; \ 
    /* зарезервировано) */            (0        «28) \ \ 
    I* код подсистемы */                (Facility «16) ; \ 
    /* код исключения */                (Exception « 0))) 
    
    // Наше собственное программное исключение. Оно возбуждается, 
    // если элемент массива должен быть инициализирован нулями. 
    ftdefine SE_2ERO_ELEM \ 
    MAKESOFTWAREEXCEPTION(ERROR_SEVERITY_ERROR, \ 
    FACILITY_NULL, 1) 
    lilll ttllllll llllllllll ll/llll/ll lllllinilll/ll lltlHIIIIIIIII IIIIIIIIIIIIIIII 
    
    void CoiiimitMeffiory(HWNO hwndLog, LPEXCEPTION_POINTERS IpEP, 
    LPBYTE IpbAttemptedAddr) { 
    BOOL fAttemptedWrite; 
    TCHAR sz8uf[100]; 
    
    // определяем, была ли попытка чтения или записи в память 
    fAttemptedWrite = (BOOL) lpEP->ExceptionRecord->ExceptionInformation[0]; 
    
    // добавляем строку в список Execution Log 
    _stprintf(szBuf, __TEXT("--> Committing memory (%s attempted)"), 
    fAttemptedWrite ? __TEXT("write") : __TEXT("read")); 
    LlstBox_AddString(hwndLog, szBuf); 
    // Попытка доступа к памяти была, когда программа обращалась 
    // к элементу нашего массива. Попробуем передать память для 
    // отдельного элемента массива. 
    VirtualAlloc(lpbAttemptedAddr, sizeof(ELEMENT), MEM_COMMIT, PAGE_READWRITE); 
    if (!fAttemptedWrite) { 
    // Программа пытается прочитать еще не созданный элемент 
    // массива. Возбуждаем собственное программное исключение, 
    // чтобы обнулить этот элемент перед использованием. 
    RaiseExceptlon(SE_ZERO_ELEM, 0. 1. (LPDWORD) SIpbAttemptedAddr); 
    / 
    llllllllll/llllll/llllllllllllillllllllllllllllllllllllllllllllllillllHHIIIII 
    int ExpFilter (LPEXCEPTION_POINTERS IpEP, LPBYTE IpbArray, 
    LONG INumBytesInArray, HWND hwndLog) { 
    LPBYTE IpbAttemptedAddr = NULL: 

    гм. след. стр. 

    572 _____________________________Windows для профессионалов 


    РИС. 16-6  (продолжение) 

    // получаем код исключения, объясняющий причину вызова фильтра 
    DWORD dwExceptionCode = lpEP->ExceptionRecord->ExceptionCode; 
    
    // предполагаем, что этот фильтр НЕ обработает исключение 
    // и позволит системе продолжить поиск других фильтров 
    int nFllterResult = EXCEPTION_CONTINUE_SEARCH; 
    „try { 
    
    // Объявляем структуру EXCEPTION_RECORD, локальную в этом 
    // блоке __try. Она используется ниже, в блоке „except. 
    EXCEPTION_RECORD SavedExceptRec; 
    
    // Сначала надо определить, произошло ли данное исключение 
    // из-за попытки обращения к нашему массиву элементов. Этот 
    // фильтр и обработчик не обрабатывают исключения другого типа. 
    if (dwExceptionCode != EXGEPTION_AGCESS_VIOLATION) { 
    // Если исключение не связано с нарушением доступа к 
    // памяти, значит, оно произошло не из-за попытки обращения 
    // к элементу массива. Системе следует продолжить поиск 
    // другого фильтра исключений. 
    nFilterResult = EXCEPTION_CONTINUE_SEARCH; 
    „leave; 
    } 
    if (IpbArray == NULL) { 
    // исключение произошло до попытки резервирования адресного 
    // пространства, или эта попытка была неудачной 
    nFilterResult = EXCEPTION_CONTINUE_SEARCH: 
    _leave; 
    } 
    // получаемм адрес, по которому было обращение к памяти 
    IpbAttemptedAddr = (LPBYTE) 
    lpEP->ExceptionRecord->ExceptionInfonnation[1]; 
    if ((IpbAttemptedAddr < IpbArray) ',', 
    ((IpbArray + INumBytesInArray) < IpbAttemptedAddr)) { 
    // Адрес обращения расположен либо ДО зарезервированного 
    // для массива адресного пространства,либо ПОСЛЕ этого 
    // пространства. Пусть исключение обработает другой фильтр. 
    nFllterResult = EXCEPTION_CONTINUE_SEARCH: 
    „leave; 
    } 
    // *** исключение должен обработать этот фильтр 
    „try { 
    // Вызываем функцию, передающую память для элемента 
    // массива. Она возбудит программное исключение при 
    // попытке чтения. Тогда мы обнулим этот элемент 
    // массива перед тем. как продолжить чтение. 
    ComnutMemory(hwndLog, IpEP, IpbAttemptedAddr); 

    см. след. стр. 

    ________________Глава 16. Структурная обработка исключений 573 

    РИС. 16-6 (продолжение) 

    I / Мы хотим обработать только собственное программное 
    // исключение, которое указывает нам обнулить элемент 
    // массива. В этом случае мы должны сохранить 
    // дополнительную информацию, предоставляемую нам 
    // исключением SE_ZERO_ELEM, чтобы обработчик знал, 
    // какой из элементов массива надо обнулить. 
    „except ((GetExceptionCodeO == SE_ZERO_ELEM) ? (SavedExceptRec = 
    *((Get Exceptioninfоrmation())->ExceptionRecord), 
    EXCEPTION_EXECUTE_HANDLER) : EXCEPTION_CONTINUE_SEARCH) { 
    // получаем адрес обнуляемого элемента 
    
    LPELEMENT IpArrayElementToZero = (LPELEMENT) 
    SavedExceptRec.Exceptioninfоrmation[0]: 
    
    // обнуляем элемент массива, прежде чем прочитать его 
    chINITSTRUCT(*lpArrayElernentToZero. FALSE); 
    ListBox_AddString(hwndLog, __ТЕХТ("--> Zeroed array element")); 
    } 
    
    // Теперь память передана. Еще раз выполним инструкцию, 
    // вызвавшую исключение, На этот раз все пройдет нормально, 
    // и нового исключения не будет. 
    nFilterResult = EXCEPTION_CONTINUE_EXECUTION; 
    } 
    „finally { 
    } 
    // память передана, и мы можем возобновить выполнение 
    // с инструкции, вызвавшей исключение 
    return(nFilterResult); 
    } 
    1111111111111111111111111111111111111111111111111111111111111111111111111111111 
    void Dlg_ReserveArrayAndAccessIt (HWND hwndLog, int nNumAccesses) { 
    LPELEMENT IpArray = NULL: 
    ELEMENT Element; 
    TCHAR szBuf[100]; 
    int nElernentNum = 0; 
    const LONG INumBytesInArray = sizeof(ELEMENT) * NUMELEMENTS; 
    
    // очищаем окно списка Execution Log 
    ListBox_ResetContent(hwndLog): 
    ListBox_AddString(hwndLog, __TEXT("Execution started")); 
    __try { 
    
    // резервируем адресное пространство, достаточное 
    // для размещения в нем NUMELEMENTS структур ELEMENT 

    см. след. стр. 

    574 _____________________________Windows для профессионалов 

    РИС. 16~6  (продолжение) 
    
    IpArray = VirtualAlloc(NULL, INumBytesInArray, 
    MEM_RESERVE, PAGE_NOACCESS); 
    while (nNumAccesses—) { 
    
    // получаем случайный индекс элемента 
    nElementNum = rand() % NUMELEMENTS: 
    
    // вероятность чтения/записи - 50 на 50 
    if ((randO % 2) == 0) { 
    // попытка чтения 
    _stprintf(szBuf, __TEXT("Reading index: %d"), nElementNum); 
    ListBox_AddString(hwndLog, szBuf); 
    
    // исключение возникнет в этой строке 
    Element = lpArray[nElenientNuni]; 
    } else { 
    
    // попытка записи 
    _stprintf(szBuf, __TEXT( "Writing index: %d"), nElementNum); 
    ListBox_AddString(hwndLog, szBuf): 
    
    // исключение возникнет в этой строке 
    lpArray[nElementNum] = Element: 
    } 
    } // while 
    
    // выполнение закончено 
    ListBox_AddString(hwndLog, __TEXT("Execution ended")); 
    
    // возвращаем память и освобождаем массив ELEMENT 
    VirtualFree(lpArray, О, MEM_RELEASE); 
    } // „try 
    „except ( 
    
    ExpFilter(GetExceptionInformation(), (LPBYTE) IpArray. 
    INumBytesInArray, hwndLog)) { 
    
    // поскольку фильтр никогда не возвращает 
    // EXCEPTION_EXECUTE_HANDLER, больше здесь 
    // делать нечего 
    } // „except 
    } 
    /////////////////////////////////У///////////////////////////////////////////// 
    BOOL Dlg_OnInitDialog (HWND hwnd, HWND hwndFocus, LPARAM IParam) { 

    см. след. стр. 

    ________________Глава 16. Структурная обработка исключений 575 


    РИС. 16-6  (продолжение) 
    
    II связываем значок с диалоговым окном 
    chSETDLGICONS(hwnd, IDI_SEHSOFT, IDI_SEHSOFT); 
    
    // число попыток обращения к памяти по умолчанию - 100 
    SetDlgItemInt(hwnd, IDC_NUMACCESSES, 100, FALSE); 
    return(TRUE); 
    i 
    f 
    1111/llllltlll/llll/ll/ll/l/llllllllllllllllllillllll/lllillil/iillillllllllill 
    void Dlg_OnCommand (HWND hwnd, int id, HWND hwndCtI, UINT codeNotify) { 
    int nNumAccesses; 
    BOOL ^Translated; 
    switch (id) { 
    case IDOK: 
    nNumAccesses = GetDlgItemInt(hwnd, IDC_NUMACCESSES, ^Translated, FALSE); 
    If (fTranslated) { 
    Dlg_ReserveArrayAndAccessIt( 
    Get01gltem(hwnd. IDC_LOG), nNumAccesses); 
    } else { 
    chMB(__TEXT("Invalid number of accesses.")); 
    } 
    break: 
    case IDCANCEL: 
    EndDialog(hwnd, id); 
    break; 
    111111111111111/lllllllllllil/llll/ll/llll/l/llllillillllll/lllllll/lllllll/lll 
    
    BOOL CALLBACK Dlg_Proc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM 
IParam) { 
    switch (uMsg) { 
    chHANDLE_DLGMSG(hwnd, WM_INITDIALOG, Dlg_OnInitDialog); 
    chHANOLE_DLGMSG(hwnd. WM_COMMAND, Dlg_OnCommand); 
    } 
    return(FALSE); 
    } 
    1111111111111111111111111111111111111111111111111111111111111111111111111111111 
    int WINAPI _tWinMain (HINSTANCE hinstExe, 
    HINSTANCE hinstPrev, LPTSTR pszCmdLine, int nCmdShow) { 
    chWARNIFUNICODEUNDERWIN95(): 
    D.i.alogBox(hinstExe, MAKEINTRESOURCE(IDD_SEHSOFT), NULL, Dlg_Proc); 
    return(O); 
    ) 
    ////////////////////////////////// конец файла I I I I I / I / I I I 1 И / 

    576 _____________________________Windows для профессионалов 


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

    Я постоянно твердил Вам, что при возбуждении исключения система пытается 
найти фильтр исключений, способный его обработать. Все так, но на самом деле 
это не первое, что делает система при исключении. Сначала она проверяет, 
выполняется ли данный процесс под управлением отладчика. Нет — ищет фильтры 
исключений. Да — уведомляет отладчик об отладочном событии и заполняет 
структуру EXCEPTION_DEBUG_INFO: 

    typedef struct _EXCEPTION_DEBUG_INFO { 
    EXCEPTION_RECORD ExceptionRecord; 
    DWORD dwFirstChance; 
    } EXCEPTION_DEBUG_INFO: 

    Она информирует отладчик об исключении. Элемент ExceptionRecord содержит 
те же данные, что можно было бы получить вызовом GetExceptionInformation. На 
основе этой информации отладчик определяет, как обработать данное исключение. 
Элемент dwFirstChance будет приравнен какому-нибудь ненулевому значению. 
Обычно отладчик перехватывает исключения типа «точка останова» или «пошаговое 
исполнение» и не пропускает их в Вашу программу. 
    Если процесс «идет» под управлением отладчика и тот обрабатывает 
исключение, процессу разрешается продолжить выполнение. Отладчик может также 
приостановить потоки Вашего процесса и дать возможность выяснить причину 
исключения. Если же исключение не обрабатывается отладчиком, система сканирует 
Ваш поток в поисках фильтра исключений, который мог бы вернуть значение 
EXCEPTION_EXECUTE_HANDLER или EXCEPTION_CONTINUE_EXECUTION. Когда фильтр 
возвращает одно из этих значений, выполнение продолжается так, как уже было 
описано в этой главе. 
    Если система, просмотрев весь поток, не нашла фильтра, способного 
обработать данное исключение, она снова передает сообщение отладчику. На этот 
раз элемент dwFirstChance структуры EXCEPTION_DEBUG_INFO будет равен 0. Теперь 
отладчик «знает», что в одном из потоков процесса есть необработанное 
исключение, и, уведомив Вас об этом, позволит перейти к отладке. 

    Необработанные исключения в отсутствие отладчика 

    А если все фильтры исключений вернут EXCEPTION_CONTINUE_SEARCH и процесс 
выполняется без отладчика? Просмотрев поток от начала до конца и не найдя 
фильтра, способного обработать данное исключение, система использует 
SEH-фрейм, инициализированный в системной функции StartOfThread (см. главу 4), 
чтобы вызвать встроенный фильтр UnhandledExceptionFilter. 

    LONG UnhandledExceptionFilter 
    LPEXCEPTION_POINTERS IpexpExceptionInfo): 

    Первое, что делает функция UnhandledExceptionFilter, — проверяет, 
присутствует ли отладчик, и, если да, возвращает EXCEPTION_CONTINUE_SEARCH, 
что приводит в итоге к уведомлению отладчика об исключении. Если же процесс 
работает самостоятельно, система уведомляет об исключении самого пользователя, 
используя для этого информационное окно. В Windows 95 это окно выглядит 
примерно так: 

    _________________Глава 16. Структурная обработка исключений 577 


    О This program has performed an illegal operation      n -••-•_.__-•••,[ 
    ' and will be shut down.                            ^.........М"^-,,....! 
    If the problem persists, contact the program 
    vendor                                           i———————i 
    3EHTERM caused an invalid page fault in           ^ 
    module SEHTERM.EXB at; 0137:00401774. 
    Registers; 
    EAX=00000000 CS=0137 BIP=004Q1774 ЕРЬвЗ=ОП010206 
    BBX=00540000 33=013f Eap=0064fd94 EBP=0064fda8 
    ЕСХ°815744ЭО D3=013f ESI=ei574d64 Fa=2iSb7 
    EDX=ei576278 63=013f EDI=81575e34 83=0000 
    Bytes at C3:EIP:                                         •',"; 
    8a 00 BB 15 ?8 6a 00 68 14 10 40 00 6» 00 6a 01   »| : -".':. 
    А в Windows NT — так: 
    0 The inaruction at "0х00401774" referenced memory' at "0х00000000" The memory 
    could not be "read". 
    Click an OK to terminate the application 
    Click on CANCEL to debug the application 
    [i"OK'l| Cancel | 
    11-----—1 —————»J 

    В первом абзаце этого окна Windows NT сообщает тип исключения и адрес 
вызвавшей его инструкции в адресном пространстве процесса. У меня окно 
появилось из-за нарушения доступа к памяти, поэтому система сообщила и адрес, 
где произошла ошибка, и тип доступа — чтение. UnhandledExceptionFilter 
получает эту информацию из элемента Exceptionlnformation структуры 
EXCEPTION_RECORD, инициализированной для этого исключения. 
    В информационном окне можно сделать одно из двух. Во-первых, щелкнуть 
кнопку ОК, и тогда UnhandledExceptionFilter вернет значение 
EXCEPTION_EXECUTE_HANDLER. Это приведет к исполнению встроенного системного 
обработчика исключений, который завершит процесс вызовом: 

    ExitProcess(GetExceptionCodeO): 

    Во-вторых, щелкнуть кнопку Cancel (самые смелые мечты программистов 
начинают сбываться). В этом случае UnhandledExceptionFilter попытается 
запустить отладчик и подключить его к процессу. Тогда Вы сможете просматривать 
состояние переменных, расставлять точки останова, перезапускать процесс и 
вообще делать все, что делается при отладке процесса. 
    Но самое главное, что проблему в программе можно исследовать в момент ее 
возникновения. В большинстве других операционных систем для отладки процесса 
сначала запускается отладчик. При генерации исключения в процессе, выполняемом 
в любой из таких систем, этот процесс надо завершить, запустить отладчик и 
прогнать программу уже под отладчиком. Проблема, правда, в том, что ошибку 
надо сначала воспроизвести, и лишь потом пытаться ее исправить. А кто знает, 
какие значения были у переменных, когда Вы впервые заметили ошибку? Поэтому 
найти ошибку таким способом гораздо труднее. Возможность динамически 
подключать отладчик к уже запущенному процессу — одно из лучших качеств Win32. 

    578 ____________________________Windows для профессионалов 

    Для вызова отладчика функция UnhandkdExceptionFilter просматривает реестр 
— точнее, раздел, содержащий командную строку, которую 
UnhandledExceptionFilter и выполняет: 

    HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\AeDebug\Debugger 

    При установке Visual C++ 4.х в этом разделе появляется примерно такой параметр: 

    F:\MSDEV\BIN\MSDEV.EXE -p %ld -e %ld 

    Эта строка сообщает системе, какую программу запускать (MSDEV.EXE) и где 
ее искать (на моей машине — в каталоге F:\MSDEV\BIN). UnhandledExceptionFilter 
передает MSDEV.EXE два параметра в командной строке: первый — это 
идентификатор процесса, подлежащего отладке, а второй идентифицирует 
наследуемое событие со сбросом вручную, которое создается и переводится в 
занятое состояние функцией UnhandledExceptionFilter. MSDEV распознает ключи -p 
и -e как идентификатор процесса и описатель события. 
    Подставив в строку идентификатор процесса и описатель события, 
UnhandledExceptionFilter запускает отладчик вызовом CreateProcess и ждет 
освобождения события. Далее отладчик подключается к процессу, вызывая 
DebugActiveProcess и передавая ей идентификатор отлаживаемого процесса: 

    BOOL DebugActlveProcess(DWORD idPr-ocess); 

    После этого система посылает отладочные события, сообщая отладчику о 
состоянии процесса (например, информацию об активных потоках процесса и о 
динамически подключаемых библиотеках, спроецированных на его адресное пространство). 
    Пока отладчик инициализируется, потоки отлаживаемого процесса 
приостанавливаются, ожидая освобождения события. Как только отладчик готов к 
работе, он вызывает SetEvent, передавая ей описатель события. Он может 
использовать этот описатель напрямую, потому что событие было создано 
наследуемым — любым дочерним процессом отлаживаемого процесса. А отладчик как 
раз и является таким процессом, потому что CreateProcess, с помощью которой он 
запущен, вызвана функцией UnhandledExceptionFilter из отлаживаемого процесса. 
    Обнаружив, что событие со сбросом вручную перешло в свободное состояние, 
отлаживаемый процесс пробуждается, а UnhandledExceptionFilter возвращает 
EXCEPTION_CONTINUE_SEARCH, что снова приводит к фильтрации необработанного 
исключения. Но теперь процесс выполняется под отладчиком, и последний будет 
уведомлен об этом исключении. 

    Отключение вывода сообщений об исключении 

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

    UINT SetErrorMode(UINT fuErrorMode); 

    передав ей идентификатор SEMJMOGPFAULTERRORBOX. Тогда 
UnhandledExceptionFilter сразу завершит Ваше приложение. Пользователь не 
получит предупреждения, приложение просто исчезнет. 
    Во-вторых, можно поместить все содержимое функции WinMain в блок 
try-except. Убедитесь, что фильтр исключений всегда возвращает 
EXCEPTION_EXECUTE_HANDLER и что он действительно обрабатывает исключения; это 
предотвратит вызов UnhandledExceptionFilter. В обработчике исключений функции 
WinMain можно выводить на экран диалоговое окно с какой-нибудь диагностической 
информацией. Пользователь может скопировать 

    _________________Глава 16. Структурная обработка исключений 579 

    эту информацию и передать ее в службу технической поддержки программного 
продукта, что поможет выявить источник проблем в Вашем приложении. Это 
диалоговое окно следует разработать так, чтобы пользователь мог завершить 
приложение, но не отлаживать. 
    Проблема с этим способом в том, что он позволяет перехватывать только те 
исключения, которые возникают в первичном потоке. Если исключение произойдет в 
любом другом потоке процесса, система вызовет встроенную функцию 
UnhandledExceptionFilter. Чтобы вывернуться из этой ситуации, придется 
включить блоки try-except как в WinMain, так и во все функции потоков Вашего процесса. 
    Поскольку забыть о таких вещах очень легко, Microsoft специально добавила 
в Win32 API функцию SetUnhandledExceptionFilter. 

    LPTOP_LEVEL_EXCEPTION_FILTER SetUnhandledExceptionFilter( 
    LPTOP_LEVEL_EXCEPTION_FILTER IpTopLevelExceptionFllter); 

    После ее вызова необработанное исключение, возникшее в любом из потоков 
процесса, приведет к вызову Вашего фильтра исключений. Адрес фильтра следует 
передать в единственном параметре функции SetUnhandledExceptionFilter. 
Прототип этой функции-фильтра должен выглядеть так: 

    LONG UnhandledExceptionFilter(LPEXCEPTION_POINTERS IpexpExceptionInfo); 

    По форме она идентична встроенной функции UnhandledExceptionFilter. Внутри 
фильтра можно проводить любую обработку, а возвращаемым значением должен быть 
один из идентификаторов типа EXCEPTION_*. Вот что происходит в случае возврата 
каждого из идентификаторов: 

    Идентификатор                     Что происходит 

    EXCEPTION_EXECUTE_HANDLER      Процесс просто завершается, так как система 
не выполняет никаких операций в своем обработчике исключений. 
    EXCEPTION_CONTINUE_EXECUTION Выполнение продолжается с инструкции, 
вызвавшей исключение. Вы можете модифицировать информацию, на которую 
указывает параметр типа LPEXCEPTION_POINTERS. 
    EXCEPTION_CONTINUE_SEARCH      Вызывается стандартная \Уш32-функция 
UnhandledExceptionFilter. 

    Чтобы функция UnhandledExceptionFilter вновь стала фильтром по умолчанию, 
вызовите SetUnhandledExceptionFilter со значением NULL. Отметим также, что 
всякий раз, когда устанавливается новый фильтр для необработанных исключений, 
SetUnhandledExceptionFilter возвращает адрес предыдущего фильтра. Если таким 
фильтром была UnhandledExceptionFilter, возвращается NULL. 
    И последний способ для отключения окна, выводимого функцией 
UnhandledExceptionFilter, предназначен только для разработчика, но не для 
конечного пользователя. Дело в том, что на UnhandledExceptionFilter влияет еще 
один параметр реестра: 

    HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\AeDebug\Auto 

    Значение параметра Auto может быть либо 0, либо 1. В последнем случае 
UnhandledExceptionFilter не выводит окно, но сразу же вызывает отладчик. А при 
нулевом значении функция выводит сообщения и работает, как описано в 
предыдущем разделе. 

    580 _____________________________Windows для профессионалов 

    Явный вызов функции UnhandfedExcepf'ionFUter 

    UnhandledExceptionFilter — обычная \Уш32-функция, которую можно вызывать 
прямо из Вашей программы. Вот пример ее использования: 

    void Funcadelic (void) { 
    „try { 
    } 
    „except (ExpFltr(GetExceptlonInformation())) { 
    } 
    LONG ExpFltr (LPEXCEPTION_POINTERS IpEP) { 
    DWORD dwExceptionCode = lpEP->ExceptionRecord.ExceptionCode; 
    if (dwExceptionCode == EXCEPTION_ACCESS_VIOLATION) { 
    // здесь что-то делаем... 
    return(EXCEPTION_CONTINUE_EXECUTION): 
    } 
    return(UnhandledException Filter(IpEP)): 
    } 

    Исключение в блоке try функции Funcadelic приводит к вызову ExpFltr. Ей 
передается значение, возвращаемое GetExceptionInformation. Внутри фильтра 
определяется код исключения и сравнивается с EXCEPTION_ACCESS_VIOLATION. Если 
было нарушение доступа, фильтр исправляет ситуацию и возвращает 
EXCEPTION_CONTINUE_EXECUTION. Это значение заставляет систему возобновить 
выполнение программы с инструкции, вызвавшей исключение. 
    Если произошло какое-то другое исключение, ExpFltr вызывает 
UnhandledExceptionFilter, передавая ей адрес структуры EXCEPTION_POINTERS. 
UnhandledExceptionFilter открывает окно, позволяющее завершить процесс или 
начать отладку. Функция ExpFltr возвращает значение — код возврата из 
UnhandledExceptionFilter. 

    Специфика Windows NT: необработанные исключения в режиме ядра 

    До сих пор мы рассматривали исключения, возбуждаемые потоком, выполняемым 
в режиме User, но в режиме ядра поток тоже может генерировать исключения — они 
обрабатываются так же, как и в режиме User. Если низкоуровневая функция для 
работы с виртуальной памятью возбуждает исключение, система проверяет, есть ли 
фильтр исключений для режима ядра, готовый обработать это исключение. Если 
такого фильтра нет, оно остается необработанным. Если это произошло в режиме 
ядра, необработанное исключение окажется в операционной системе, а не в 
приложении. Это уже серьезно! 
    Так как дальнейшая работа системы после необработанного исключения в 
режиме ядра небезопасна, Windows NT не вызывает UnhandledExceptionFilter. 
Вместо этого дисплей переключается в текстовый режим, выводится какая-то 
отладочная информация, и система останавливается. Вам следует записать 
выведенную информацию и отправить ее в Microsoft, чтобы там исправили код в 
будущих версиях операционной системы. Прежде чем продолжить работу, придется 
перезапустить машину; при этом вся несохраненная информация теряется. 
