Foreversoft.ru

IT Справочник
0 просмотров
Рейтинг статьи
1 звезда2 звезды3 звезды4 звезды5 звезд
Загрузка...

Ошибка функции win32 api

Блог GunSmoker-а (переводы)

. when altering one’s mind becomes as easy as programming a computer, what does it mean to be human.

пятница, 14 января 2011 г.

Как WinAPI сообщает об ошибках

Это перевод The Way WinAPI Shows Errors. Автор: Christian Wimmer.

Я увидел интересный ответ в обсуждении на форуме, в котором я участвовал. Вопрос был не на тему этого поста, но ответ с кодом был достаточно интересным, чтобы написать эту статью. Код был написан на Delphi, но, очевидно, писался в стиле простого C (даже с использованием goto!).

Однако сейчас я хочу поговорить о том, как WinAPI обычно возвращает ошибки, и почему код в ответе — не самый удачный способ.

Тот код выглядел не в точности так. Я просто выделил из него существенные части для вашего удобства (исходный код напоминал C ещё больше, чем этот пример): В чём проблема с возвращаемым значением этой функции? Во-первых, возвращаемое значение каждой WinAPI функции проверяется и функция возвращает управление, если вызываемая WinAPI функция возвращает ошибку. Это — очень хорошо, потому что я видел кучу кода, который ничем таким не озаботились. Но здесь есть другая проблема. Существует два возможных набора результатов, которые мы бы хотели сообщить нашему вызывающему:

  • Является ли пользователь членом группы. Это, очевидно, двоичное значение (скажем, True/False ).
  • Произошла ли ошибка. Аналогично, это двоичное значение ( True/False ).

Диапазон возвращаемых значений вашей функции варьируется от False(0) до True(1) . Функция проецирует два набора с разным смыслом в один набор — результат функции. Если один из вызовов WinAPI завершиться неудачей, то функция выйдет и вернёт False . Очевидно, что значение False используется для двух совершенно разных по смыслу результатов. Одно из которых говорит вызывающему, что пользователь не входит в группу, а другое — что функция почему-то завершилась с ошибкой. А мы соединили эти два смысла в одно значение. Т.е. мы потеряли информацию! Теперь мы не можем отличить один случай от другого без дополнительной (мета) информации. Всегда думайте об этом, когда вы проектируете заголовок функции. Но, конечно же, эта информация есть у нас в другом месте. В самом WinAPI. GetLastError даст вам значение ошибки, которое (я надеюсь) позволит нам решить нашу проблему. Так что всё нормально? Хотелось бы верить! Посмотрите на это: Функция IsUserMemberOf вызывается после успешного вызова GetMyUserName , которая первым делом просит WinAPI-функцию GetUserName вернуть размер строки, содержащей имя пользователя. В этом случае GetLastError вернёт ERROR_INSUFFICIENT_BUFFER (122dec), которая говорит нам, что необходимо использовать больший блок памяти.

В итоге GetMyUserName вернёт нам имя пользователя (успешно). Предположим, что IsUserMemberOf также будет успешна, но при этом она говорит нам, что пользователь не входит в группу. Тогда возвращаемое значение будет False , а тот факт, что большинство WinAPI функций не изменяют значение LastError при успешном вызове, говорит нам, что GetLastError всё ещё будет равно ERROR_INSUFFICIENT_BUFFER (122dec): поэтому когда мы будем проверять bIsMember — мы попадём на ветку кода с обработкой ошибки, хотя никакой ошибки не было.

Как вы видите, даже хотя мы можем получить дополнительные сведения от GetLastError , но эта функция реализована так, что мы не можем воспользоваться этой информацией.

Если вы планируете написать такую функцию, я призываю вас не использовать техники языка C. Почему? Просто посмотрите как выглядит этот стиль: Выглядит сложно, не так ли? Я не собирался писать точную реализацию GetUserName (положа руку на сердце — я её не знаю), а вместо этого показал возможную реализацию. Так что WinAPI использует результат функции только как указание на ошибку. False означает провал операции, а GetLastError сообщит вам, что именно пошло не так. True означает, что всё в порядке.

WinApi

Обработка ошибок, возникающих при вызове функций Win32 API

Как мы уже говорили ранее, не бывает программ без ошибок. Если ошибка возникает при выполнении кода процедур и функций VBA, — ошибка периода выполнения (run time error), — то появляется окно сообщения об ошибке. Если ошибка периода выполнения появляется при работе функции Win32 API, то прерывания работы программы не происходит, окно сообщения об ошибке не появляется. Вместо этого функция возвращает значение 0 в качестве результата, свидетельствующее об ошибке периода выполнения. Тем не менее, большинство функций Win32 API сохраняют информацию о возникшей ошибке. Эту информацию можно получить стандартным способом, используя VBA объект Err . Свойство LastDLLErr этого объекта возвращает номер последней ошибки, возникшей в DLL . К сожалению, сам по себе номер мало что говорит. Необходимо знать описание ошибки, соответствующее этому номеру. Частично причину ошибки можно понять по имени константы, которую можно найти в уже неоднократно упоминавшемся файле Win32API.txt, используемом в API Viewer. Опять-таки, к сожалению, возможные значения констант приводятся независимо от функций, в которых они возникают. И, несмотря на то, что все такие константы начинаются со слова ERROR найти константу по ее значению не так то просто. Можно, конечно, воспользоваться возможностью создания базы данных по текстовому файлу и организовать специальный запрос, позволяющий найти имя константы по ее значению. Естественно, что лучше всего иметь полную информацию об используемых функциях Win32 API, включающую, в том числе, и сведения о возможных ошибках периода выполнения данных функций. Эту информацию можно найти, если под рукой есть подходящая литература, например, справочник программиста Win32, или поискать на упоминавшемся сервере Microsoft для разработчиков.

Естественно, что пример ошибки времени выполнения в процессе работы DLL у нас уже под рукой. Нам и изобретать его не было необходимости. Как Вы помните, в последнем примере мы сетовали на возникновение подобной ошибки в процессе поиска описателя окна по его заголовку при вызове функции FindWindowW , работающей в Unicode кодировке. Давайте вернемся к этому примеру и попробуем обработать эту ошибку. В раздел объявлений ранее созданного модуля Unicode мы добавили объявление констант и функций и теперь он выглядит так:

Читать еще:  Getlasterror коды ошибок

Приведем теперь процедуру, в которой вызывается функция FindWindowW , приводящая к ошибке периода выполнения:

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

Прокомментируем теперь работу программы и полученные результаты:

  • Вначале мы попытались найти окно с заведомо существующим заголовком, — окно документа, содержащего тестовые примеры. В процессе работы функции Win32 API FindWindowW возникла ошибка периода выполнения, функция вернула нулевой результат. Ошибка была обработана, и как показывает константа ERROR_INVALID_NAME , причиной является ошибка в задании имени (передаваемый формат в виде массива байтов не годится для цели поиска и сравнения строк), о чем свидетельствует отладочная информация.
  • Далее проводится еще один эксперимент на ту же тему. Для активного окна находится заголовок, используя функцию GetWindowTextW , возвращающую строку в виде массива байтов. Тут же этот массив используется для поиска окна по заголовку. Однако ничего не помогает и снова при поиске окна возникает ошибка. Она обрабатывается, о чем выдается соответствующее сообщение.

Функции API и вызов Callback функций

Мы уже говорили о функциях обратного вызова, называемых Callback функциями. Для «многослойного» способа построения программных систем, характерного для программирования, функции внешнего слоя могут вызывать функции ядра без особых проблем. Однако паритета между ядром и внешним слоем нет. Вызов функций внешнего слоя из ядра затруднен. Чтобы как-то решить эту проблему и вводятся функции обратного вызова . Если функции ядра, в ответ на ее вызов из внешнего слоя, в свою очередь необходимо вызвать функцию внешнего слоя, то ядро диктует условия, каким должна удовлетворять вызываемая функция. Есть специальные механизмы, обеспечивающие вызов таких Callback функций, но во всех случаях заголовок вызываемой функции жестко фиксирован и известен ядру. Этот механизм Callback функций применяется и для обеспечения двусторонней связи между функциями VBA и функциями Win32 API, которым в процессе их работы требуется обратный вызов функций VBA.

Заметьте, в предыдущих версиях VBA не было возможности явным образом работать с функциями Win32 API, требующими вызова Callback функций. Теперь такая возможность появилась, благодаря включению в язык возможности передачи указателя функции в качестве параметра процедур и функций. Явное введение в язык конструкции AddressOf , возвращающей указатель на функцию, дало возможность при вызове функции Win32 API передать ей в качестве аргумента имя Callback функции. Попробуем разобраться в деталях того, как вызываются функции Win32 API, требующие Callback функции для своей работы, как пишутся такие функции на VBA, как передается информация между функциями, — как это все, в конечном итоге, согласуется между собой. Начнем, прежде всего, с ответа на вопрос, а как узнать, что функция Win32 API требует для своей работы вызова Callback функции. Подсказку можно получить от обозревателя, если проанализировать оператор Declare , созданный API Viewer. Когда имя параметра начинается префиксом lp, а заканчивается окончанием Func , это означает, что соответствующий аргумент является ссылкой на имя Callback функции. К сожалению, обозреватель не содержит необходимой информации о том, каким должен быть заголовок функции обратного вызова , так что необходимо обращаться к документации по Win32 API или идти на сервер. Заметьте, документация, как правило, ориентирована на C программистов, поэтому необходимо самому корректно транслировать заголовок к виду, понимаемому VBA. Ошибки в задании типов аргументов, пропуск описателя ByVal могут дорого стоить. Пожалуй, одна из наиболее сложных задач при работе с Callback функцией состоит в том, чтобы найти ее описание, а затем, используя документацию, ориентированную на язык C/C++, корректно описать на VBA заголовок этой функции.

Еще одна, важная для понимания задача состоит в организации правильного обмена информацией между процедурой VBA, вызываемой ею функцией Win32 API и вызываемой ею Callback функцией. Прежде всего, следует понимать, что программисту никогда не приходится вызывать самому Callback функцию. Ее всегда вызывает соответствующая функция Win32 API. Она же передает ей текущие значения аргументов, необходимые для работы функции обратного вызова . Но, конечно же, в большинстве случаев Callback функция производит изменения в мире объектов VBA программы и, следовательно, она должна быть каким-то образом связана с этим миром. Иногда это делается за счет того, что в функции Win32 API предусмотрен специальный параметр, который вызывающая ее программа передает ей, а она, в свою очередь, передает его функции обратного вызова . Недостаток такого способа состоит в том, что передаваемый параметр один, а информация, связывающая функцию обратного вызова с миром VBA, может быть разнородной. В этих условиях более предпочтительным может быть способ передачи и получения данных в Callback функцию через глобальные переменные. Именно этот способ мы использовали в наших примерах. Прежде, чем перейти к примерам, давайте подведем итоги и еще раз сформулируем основные этапы организации работы при вызове функций Win32 API, требующих Callback функций. Итак, необходимо:

Ошибка функции win32 api

Как мы уже говорили ранее, не бывает программ без ошибок. Если ошибка возникает при выполнении кода процедур и функций VBA, — ошибка периода выполнения (run time error), — то появляется окно сообщения об ошибке. Если ошибка периода выполнения появляется при работе функции Win32 API, то прерывания работы программы не происходит, окно сообщения об ошибке не появляется. Вместо этого функция возвращает значение 0 в качестве результата, свидетельствующее об ошибке периода выполнения. Тем не менее, большинство функций Win32 API сохраняют информацию о возникшей ошибке. Эту информацию можно получить стандартным способом, используя VBA объект Err . Свойство LastDLLErr этого объекта возвращает номер последней ошибки, возникшей в DLL . К сожалению, сам по себе номер мало что говорит. Необходимо знать описание ошибки, соответствующее этому номеру. Частично причину ошибки можно понять по имени константы, которую можно найти в уже неоднократно упоминавшемся файле Win32API.txt, используемом в API Viewer. Опять-таки, к сожалению, возможные значения констант приводятся независимо от функций, в которых они возникают. И, несмотря на то, что все такие константы начинаются со слова ERROR найти константу по ее значению не так то просто. Можно, конечно, воспользоваться возможностью создания базы данных по текстовому файлу и организовать специальный запрос, позволяющий найти имя константы по ее значению. Естественно, что лучше всего иметь полную информацию об используемых функциях Win32 API, включающую, в том числе, и сведения о возможных ошибках периода выполнения данных функций. Эту информацию можно найти, если под рукой есть подходящая литература, например, справочник программиста Win32, или поискать на упоминавшемся сервере Microsoft для разработчиков.

Читать еще:  Ошибка 18456 серьезность 14 состояние 8

Естественно, что пример ошибки времени выполнения в процессе работы DLL у нас уже под рукой. Нам и изобретать его не было необходимости. Как Вы помните, в последнем примере мы сетовали на возникновение подобной ошибки в процессе поиска описателя окна по его заголовку при вызове функции FindWindowW , работающей в Unicode кодировке. Давайте вернемся к этому примеру и попробуем обработать эту ошибку. В раздел объявлений ранее созданного модуля Unicode мы добавили объявление констант и функций и теперь он выглядит так:

Приведем теперь процедуру, в которой вызывается функция FindWindowW , приводящая к ошибке периода выполнения:

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

Прокомментируем теперь работу программы и полученные результаты:

  • Вначале мы попытались найти окно с заведомо существующим заголовком, — окно документа, содержащего тестовые примеры. В процессе работы функции Win32 API FindWindowW возникла ошибка периода выполнения, функция вернула нулевой результат. Ошибка была обработана, и как показывает константа ERROR_INVALID_NAME , причиной является ошибка в задании имени (передаваемый формат в виде массива байтов не годится для цели поиска и сравнения строк), о чем свидетельствует отладочная информация.
  • Далее проводится еще один эксперимент на ту же тему. Для активного окна находится заголовок, используя функцию GetWindowTextW , возвращающую строку в виде массива байтов. Тут же этот массив используется для поиска окна по заголовку. Однако ничего не помогает и снова при поиске окна возникает ошибка. Она обрабатывается, о чем выдается соответствующее сообщение.

Функции API и вызов Callback функций

Мы уже говорили о функциях обратного вызова, называемых Callback функциями. Для «многослойного» способа построения программных систем, характерного для программирования, функции внешнего слоя могут вызывать функции ядра без особых проблем. Однако паритета между ядром и внешним слоем нет. Вызов функций внешнего слоя из ядра затруднен. Чтобы как-то решить эту проблему и вводятся функции обратного вызова. Если функции ядра, в ответ на ее вызов из внешнего слоя, в свою очередь необходимо вызвать функцию внешнего слоя, то ядро диктует условия, каким должна удовлетворять вызываемая функция. Есть специальные механизмы, обеспечивающие вызов таких Callback функций, но во всех случаях заголовок вызываемой функции жестко фиксирован и известен ядру. Этот механизм Callback функций применяется и для обеспечения двусторонней связи между функциями VBA и функциями Win32 API, которым в процессе их работы требуется обратный вызов функций VBA.

Заметьте, в предыдущих версиях VBA не было возможности явным образом работать с функциями Win32 API, требующими вызова Callback функций. Теперь такая возможность появилась, благодаря включению в язык возможности передачи указателя функции в качестве параметра процедур и функций. Явное введение в язык конструкции AddressOf , возвращающей указатель на функцию, дало возможность при вызове функции Win32 API передать ей в качестве аргумента имя Callback функции. Попробуем разобраться в деталях того, как вызываются функции Win32 API, требующие Callback функции для своей работы, как пишутся такие функции на VBA, как передается информация между функциями, — как это все, в конечном итоге, согласуется между собой. Начнем, прежде всего, с ответа на вопрос, а как узнать, что функция Win32 API требует для своей работы вызова Callback функции. Подсказку можно получить от обозревателя, если проанализировать оператор Declare , созданный API Viewer. Когда имя параметра начинается префиксом lp, а заканчивается окончанием Func , это означает, что соответствующий аргумент является ссылкой на имя Callback функции. К сожалению, обозреватель не содержит необходимой информации о том, каким должен быть заголовок функции обратного вызова, так что необходимо обращаться к документации по Win32 API или идти на сервер. Заметьте, документация, как правило, ориентирована на C программистов, поэтому необходимо самому корректно транслировать заголовок к виду, понимаемому VBA. Ошибки в задании типов аргументов, пропуск описателя ByVal могут дорого стоить. Пожалуй, одна из наиболее сложных задач при работе с Callback функцией состоит в том, чтобы найти ее описание, а затем, используя документацию, ориентированную на язык C/C++, корректно описать на VBA заголовок этой функции.

Еще одна, важная для понимания задача состоит в организации правильного обмена информацией между процедурой VBA, вызываемой ею функцией Win32 API и вызываемой ею Callback функцией. Прежде всего, следует понимать, что программисту никогда не приходится вызывать самому Callback функцию. Ее всегда вызывает соответствующая функция Win32 API. Она же передает ей текущие значения аргументов, необходимые для работы функции обратного вызова. Но, конечно же, в большинстве случаев Callback функция производит изменения в мире объектов VBA программы и, следовательно, она должна быть каким-то образом связана с этим миром. Иногда это делается за счет того, что в функции Win32 API предусмотрен специальный параметр, который вызывающая ее программа передает ей, а она, в свою очередь, передает его функции обратного вызова. Недостаток такого способа состоит в том, что передаваемый параметр один, а информация, связывающая функцию обратного вызова с миром VBA, может быть разнородной. В этих условиях более предпочтительным может быть способ передачи и получения данных в Callback функцию через глобальные переменные. Именно этот способ мы использовали в наших примерах. Прежде, чем перейти к примерам, давайте подведем итоги и еще раз сформулируем основные этапы организации работы при вызове функций Win32 API, требующих Callback функций. Итак, необходимо:

  1. Определить, что функция Win32 API требует вызова Callback функции.
  2. Найти документацию по этой функции, описывающую требования к заголовку этой функции. Если эта документация ориентирована на язык C/C++, то привести ее к виду, требуемому VBA.
  3. Понять, как передать информацию об объектах VBA в Callback функцию.
  4. Написать одну или несколько реализаций функций обратного вызова. Обращаем внимание, что функций обратного вызова может быть несколько. Имя функции не является жестко зафиксированным. Оно передается функции Win32 API как аргумент в момент вызова. Поэтому в зависимости от контекста одну и ту же функцию Win32 API можно вызывать с различными Callback функциями.
  5. Вызвать функцию Win32 API, передав ей в момент вызова имя Callback функции и другие необходимые аргументы.
Читать еще:  Application deployment ошибки

Некоторые функции Win32

На прошлом уроке мы написали первое оконное приложение на ассемблере. На прошлом уроке я вам рассказал структуру ЛЮБОГО оконного приложения. Сегодня я вам расскажу (т.е. опишу) несколько функций API для работы с памятью.

Как я вам уже рассказывал, в Win32 есть только API функции. ЛЮБОЕ приложение в ЛЮБОМ случае должно использовать АПИ функции (по крайней мере, для того чтобы оно могло нормально завершиться). Эти функции находятся в библиотеках kernel32.dll, user32.dll, gdi32.dll (3 классические библиотеки) и др. Функции библиотеки kernel32.dll самые главные, они отвечают за работу с файлами памятью, от них никуда не денешься, user32.dll отвечает за окна и интерфейс пользователя, а gdi32.dll за «рисование» и мультимедиа. Для того, что бы использовать функции этих библиотек надо сначала их загрузить (kernel32.dll загружать не надо).

Каждое приложение проецируется в своё собственное виртуальное адресное пространство размером в 4 ГБ, верхние 2 из которых недоступны для него. Это пространство (вернее нижние 2 ГБ) подготавливается для него: в него загружаются 2 библиотеки kernel32.dll и ntdll.dll (для Win NT) всегда по одинаковым адресам, подготавливается стек для главной нити процесса и ещё много чего. В эту память загружаются код и данные приложения. Для того, что бы операционная система знала по каким адресам надо загружать данные и код приложения она смотрит дополнительную информацию о секциях приложения в «экзе» файле. Если она этого не сделает, то она просто не будет знать, куда спроецировать код и данные, следовательно, все обращения к памяти будут неверными. (Мы, конечно, помним что под записью mov eax, [value] понимается помещение в регистр eax содержимого некого адреса памяти, которую подразумевает метка value, эта метка подразумевает некий фиксированный адрес, все адреса рассчитываются при компиляции программы и вам об этом задумываться не надо).

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

Как мы уже знаем слово VirtualAlloc будет заменено на некоторый адрес. Этот адрес будет соответствовать адресу точки входа функции VirtualAlloc. Потом эта функция передаёт управление функциям Native API которые находятся в библиотеке ntdll.dll. Функции Native API подготавливают процесс к переходу в режим ядра и командой sysenter переводят процесс в режим ядра и т.д. Дальше я не буду рассказывать, потому что это слишком много для одного урока.

Теперь я опишу несколько API функций для работы с памятью.

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

Вот описание этой функции из MS SDK в оригинале

  • lpAddress — начальный адрес региона
  • dwSize — размер региона
  • flAllocationType — тип резервирования может быть указана одна из 3х констант :
    • MEM_COMMIT эта память может быть сброшена в файл подкачки
    • MEM_RESERVE этот участок памяти не может быть сброшен в файл подкачки он будет присутствовать в физической памяти всегда, пока эту память процесс не ввысвободит.
    • MEM_TOP_DOWN не пойму для чего нужна эта константа, но она есть, она позволяет зарезервировать виртуальную память, которая будет соответствовать максимально возможному физическому адресу.
  • flProtect тип доступа к памяти. Могут быть использованы эти константы или их комбинации.
    • PAGE_READONLY
    • PAGE_READWRITE
    • PAGE_EXECUTE позволяет передавать управление этому региону попытка чтения или записи приводит к нарушению прав доступа.
    • PAGE_EXECUTE_READ
    • PAGE_EXECUTE_READWRITE
    • PAGE_NOACCESS

Мне кажется всё и так понятно, кому не понятно читайте MS SDK.

Функция VirtualFree обратна функции VirtualAlloc высвобождает заданный диапазон памяти.

dwFreeType метод высвобождения:

если укажете MEM_RELEASE то размер указывать не надо, если MEM_DECOMMIT, то размер надо указывать остальных различий, если указано MEM_RELEASE, то эта память высвобождается полностью.

Если функция завершилась нормально, то результат отличен от нуля.

Функция VirtualProtect позволяет изменить тип доступа к зарезервированной памяти, но не так всё просто надо ещё указать старый тип доступа.

Вроде всё понятно.

Функция VirtualLock блокирует участок памяти, при этом доступ к нему невозможен и одновременно не приводит к ошибке.

При удачном исходе результат не равен нулю.

Функция VirtualUnlock обратная предыдущей функции.

При удачном исходе результат не равен нулю.

Все указанные выше функции могут работать только с памятью текущего процесса.

На сегодня хватит. Для этих целей существует MS SDK. Я объяснил только несколько функций. Вот и конец 15 урока моего туториала.

Ссылка на основную публикацию
Adblock
detector