Системное программирование в среде Win32

Автор работы: Пользователь скрыл имя, 25 Марта 2014 в 14:49, лекция

Краткое описание

Конспект лекций содержит описание технологии системного программирования под Windows с использованием функций Win32 API. В первой части конспекта лекций рассмотрены особенности архитектуры ОС Windows, специфика интерфейса прикладного программирования Win32, структура приложений для Windows. Подробно рассмотрены API функции и основные структуры данных для работы с дисками, каталогами, файлами. Отдельная глава посвящена структурной обработке исключений SEH.

Прикрепленные файлы: 1 файл

Win32_лек_часть1.doc

— 811.00 Кб (Скачать документ)

При исключении eXCEption_access_violation элемент массива ExceptionInformation [0] содержит флаг, указывающий тип операции, которая вызвала нарушение доступа.  Если его значение равно 0, поток пытается читать недоступные ему данные; 1 – записывать данные по недоступному ему адресу. Элемент ExceptionInformation [1] определяет адрес недоступных данных. Пример функции фильтра см. ниже.

 

Второй элемент exception_pointers - ContextRecord - содержит зависимую от процессора информацию. С помощью этой структуры, в основном содержащей по одному элементу для каждого регистра процессора, можно получить дополнительную информацию о возникшем исключении. Есть разные структуры для каждого типа процессора; информацию об этом можно найти в <winnt.h>

Пример 6.3. Функция фильтра, использующая информацию структуры  exceptION_record

_try { ….

}

_except (ExpFltr(GetExceptionInformation()->ExceptionRecord)) {

….

}

 

LONG ExpFltr (PEXCEPTION_RECORD pER) {

char szBuf[300], *p;

DWORD dwExceptionCode = pER->ExceptionCode;

sprint (szBuf, “Code = %x, Address = %p,

dwExceptionCode, pER->ExceptionAddress);

// находим конец строки

p=strchr(szBuf, 0);

//использован  switch на случай, если MicroSoft  в будущем

//добавит  информацию для других исключений

switch (dwExceptionCode)  {

    case  eXCEption_access_violation :

sprintf (p, Ättempt to %s data at address %p”,

pER->ExceptionInformation[0] ? “write”: “read”,

pER->ExceptionInformation[1]);

   break;

default;

   break;

}

MessageBox (NULL, szBuf,  “Exception”,

MB_OK | MB_ICONEXCLAMATION);

return (exception_continue_search);

}

6.2. Исключения для операций с  плавающей точкой

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

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

EXCEPTION_FLT_DIVIDE_BY_ZERO – поток пытается поделить вещественное число на вещественный делитель, равный 0.

EXCEPTION_FLT_INEXTACT_RESULT – Результат операции над числами с плавающей точкой нельзя точно представить в виде десятичной дроби.

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

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

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

 

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

 

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

DWORD _controlfp (DWORD new, DWORD mask)

Фактическое значение маски плавающей запятой определяется ее текущим значением и двумя этими параметрами таким образом:

(current_mask  &  ~mask) | (new  & mask)

Примечание. Здесь & и  ~   - поразрядные операции И и НЕ соответственно,  |    - поразрядная операция ИЛИ.

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

Возвращаемое значение — это фактическая установка маски. Таким образом, если оба параметра имеют значение 0, функция возвращает прежнюю установку маски, что можно использовать для последующего восстановления маски. Обычно, чтобы разрешить исключения плавающей запятой, для mask применяется специальное значение mcw_em. Также обратите внимание, что после обработки исключение плавающей запятой должно быть очищено функцией _clearfp

 

#include <float.h>

DWORD FPOld, FPNew; /* Старое и новое значения маски. */

 

FPOld = controlfp (0, 0); /* Сохраним старую маску. */

/* Укажем  шесть исключений, которые нужно  разрешить. */

FPNew = FPOld & ~(EM_OVERFLOW | EM_UNDERFLOW | EM_INEXACT | EM_ZERODIVIDE | EM_DENORMAL | EM_INVALID) ;

/* Установим  новую маску. В MCW_EM комбинируются  все шесть

исключений из предыдущего оператора. */

_controlfp (FPNew, MCW_EM) ;

while (...) _try { /* Проведем вычисления с  плавающей запятой. */

...  /* Здесь может произойти исключение  ПЗ. */

}

_except (EXCEPTION_EXECUTE_HANDLER) {

... /* Обработка исключения ПЗ. */

_clearfp (); /* Очистка исключения. */

_controlfp (FPOld, OxFFFFFFFF); /* Восстановим маску. */

}

В этом примере разрешаются все возможные исключения с плавающей запятой. Седьмое исключение — переполнение стека плавающей запятой exception_Flt_stack_check — не имеет соответствующего значения маски, указанного в документации или файлах включения. Можно также разрешить лишь определенные исключения, используя маски для конкретных исключений, такие как EM_OVERFLOW.

 

6.3. Ошибки и исключения

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

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

Тем не менее, различие между ошибками и исключениями иногда стирается. Win32 может генерировать исключения в ходе резервирования памяти функциями HeapAlloc и HeapCreate, если объема памяти недостаточно (об управлении памятью см. Конспект лекций. Часть 2). Программы также могут вызывать собственные исключения с определенными программистом кодами с помощью функции RaiseException.

Обработчики исключений позволяют выходить из внутренних блоков или функций по логике программы, не прибегая для передачи управления к goto или longjmp. Эта возможность особенно ценна, если блок кода обращается к таким ресурсам, как открытые файлы, память или объекты синхронизации, так как обработчик может освободить их; кроме того, можно продолжить выполнение программы после обработчика исключения, не завершая ее. Также программа может восстанавливать при выходе из блока состояние системы, например маску плавающей запятой. Эти способы использования обработчиков иллюстрируются во многих примерах.

 

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

До сих пор мы рассматривали обработку аппаратных исключений, когда процессор перехватывает некоторое событие и возбуждает исключение. Но Вы можете и сами генерировать исключения. Это еще один способ для функции сообщить вызывающему коду о неудаче. Традиционно функции, которые могут закончиться неудачно, возвращают некоторое особое значение – признак ошибки. При этом предполагается, что вызывающий код проверяет, не вернула ли функция признак ошибки, и если да, выполняет какие-то альтернативные действия. Как правило, вызывающая функция проводит в таких случаях соответствующую очистку и в свою очередь тоже возвращает код ошибки. Подобная передача кодов по цепочке вызовов резко усложняет написание и сопровождение кода.

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

Программные исключения перехватываются точно так же, как и аппаратные.

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

voiD RaiseException ( DWORD dwExceptionCode,

DWORD dwExceptionFlags,

DWORD cArguments,

LPDWORD  lpArguments)

 

Параметры этой функции:

dwExceptionCode — код генерируемого исключения, определяемый пользователем. При определении кода необходимо придерживаться формата, применяемого для стандартных кодов ошибок  в Windows (см. выше табл. 6.1). Не используйте бит 28, который зарезервирован для системы (он должен быть равен 0). Код ошибки устанавливается в битах 27-0 (т.е. во всех, кроме самой старшей шестнадцатеричной цифры). Бит 29 устанавливается в 1 для обозначения «заказного» исключения (не Microsoft). Биты 31—30 указывают строгость исключения. Ниже показано, как это делается, причем начальная шестнадцатеричная цифра кода показана для случая, когда бит 29 установлен.

• 0 — успех (начальная цифра 2).

• 1 — информационный (начальная цифра 6).

• 2 — предупреждение (начальная цифра А).

• 3 — ошибка (начальная цифра Е).

Параметр dwExceptionFlags должен быть либо 0, либо exception_noncontinuable. Этот флаг указывает, может ли фильтр исключений вернуть exception_continue_execution в ответ на данное исключение. Если  значение флага равно 0, то – может, в противном случае – нет.  Если возбуждается исключение exception_noncontinuable , а фильтр все же возвращает exception_continue_execution, система генерирует новое исключение exception_noncontinuable_ exception.

Третий и четвертый параметры (cArguments,   lpArguments) позволяют передать дополнительные данные о генерируемом исключениии. Обычно это не требуется, и  lpArguments передается NULL, тогда  RaiseException игнорирует параметр cArguments. Если параметр lpArguments не имеет значения null, то он указывает на массив размера cArguments (третий параметр), содержащий 32-разрядные значения, которые будут переданы в выражение фильтра. Максимально допустимое количество параметров exception_maximum_parameters в настоящее время равно 15. Обращаться к этой структуре следует через GetExceptionInformation.

 

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

 

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

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

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

Например, программа  может выполнять операторы  return в середине блока, а обработчик завершения будет  выполнять работу по очистке. Таким образом, не требуется ни вставлять код очистки непосредственно в блок, ни прибегать к оператору goto, чтобы перейти к этому коду.

_try   {

/*   Блок кода   */

 

}

_finally  {

/*  Обработчик завершения (блок finally)  */

}

 

Выход из блока Try

Обработчик завершения выполняется всякий раз, когда управление выходит из блока try по любой из следующих причин:

  • достигается конец блока try и управление «проваливается» в обработчик завершения;
  • выполняется один из следующих операторов, которые вызывают выход из блока:

return

break

goto

longjmp

continue

_leave

  • исключение.

 

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

DWORD FuncaDoodleDoo( ) {

DWORD dwTemp =0;

while (dwTemp < 10) {

__try {

if (dwTemp == 2) continue;

if (dwTemp ==3)

break;

}

__finally {

dwTemp++;

}

dwTemp++;

}

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, что дает в итоге 14, — это и есть результат вызова функции.

Аварийное завершение

Завершение по любой другой причине, отличной от достижения конца блока и перехода к обработчику завершения или выполнения оператора _leave, считается аварийным.  Оператор  _leave встречается только в Microsoft С и предоставляет удобный способ выхода из блока try – finally без аварийного завершения. Этот оператор выполняет переход к концу блока   _try и вызов обработчика завершения, что более эффективно, чем goto, так как в этом случае не требуется разворачивать стек. В обработчике завершения можно применять следующую функцию, чтобы определить, как завершился блок try:

Информация о работе Системное программирование в среде Win32