2006 г.
Биллиг Владимир Арнольдович
Интернет-Университет Информационных Технологий, INTUIT.ru
Назад Оглавление Вперёд
Обработка ошибок, возникающих при вызове функций 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 мы добавили объявление констант и функций и теперь он выглядит так:
Option Explicit
Public Const ERROR_INVALID_NAME = 123&
'Объявление вызываемых функций
Public Declare Function FindWindowA Lib "user32" _
(ByVal lpClassName As String, ByVal lpWindowName As String) As Long
'Функции в Unicode кодировке
'Тип string заменен на Any. Передача аргумента по ссылке
Public Declare Function FindWindowW Lib "user32" _
(lpClassName As Any, lpWindowName As Any) As Long
Public Declare Function GetWindowText Lib "user32" Alias "GetWindowTextW" _
(ByVal hwnd As Long, lpString As Any, ByVal cch As Long) As Long
Public Declare Function SetWindowText Lib "user32" Alias "SetWindowTextW" _
(ByVal hwnd As Long, lpString As Any) As Long
Public Declare Function GetActiveWindow Lib "user32" () As Long
Public ArCapt() As Byte 'Объявление динамического массива
Приведем теперь процедуру, в которой вызывается функция FindWindowW
, приводящая к ошибке периода выполнения:
Пример 6: html, txt
Приведем результаты печати , появившиеся в окне отладки при выполнении этой процедуры:
DocOne6 - Microsoft Word
Не корректно задано имя при вызове Unicode FindWindowW функции!
Microsoft Visual Basic - DocOne6 [running] - [Unicode (Code)]
Не корректно задано имя при вызове 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
функций. Итак, необходимо:
- Определить, что функция Win32 API требует вызова
Callback
функции. - Найти документацию по этой функции, описывающую требования к заголовку этой функции. Если эта документация ориентирована на язык C/C++, то привести ее к виду, требуемому VBA.
- Понять, как передать информацию об объектах VBA в
Callback
функцию. - Написать одну или несколько реализаций функций обратного вызова. Обращаем внимание, что функций обратного вызова может быть несколько. Имя функции не является жестко зафиксированным. Оно передается функции Win32 API как аргумент в момент вызова. Поэтому в зависимости от контекста одну и ту же функцию Win32 API можно вызывать с различными
Callback
функциями. - Вызвать функцию Win32 API, передав ей в момент вызова имя
Callback
функции и другие необходимые аргументы.
Назад Оглавление Вперёд