Тверской государственный университет
Опубликован: 13.09.2006 | Доступ: свободный | Студентов: 3490 / 369 | Оценка: 4.65 / 4.29 | Длительность: 30:37:00
Специальности: Программист, Менеджер
Лекция 7:

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 мы добавили объявление констант и функций и теперь он выглядит так:

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, приводящая к ошибке периода выполнения:

Public Sub WorkWithApiErr()
		Dim Res As Long
		Dim capt As String	'Заголовок
		Dim HandleW As Long 'Описатель окна
		
		'Поиск окна по заголовку
		capt = "DocOne6 - Microsoft Word"
		ArCapt = capt & vbNullChar
		Debug.Print ArCapt
		HandleW = FindWindowW(0&, ArCapt(0))
		If HandleW > 0 Then 'OK
			Debug.Print HandleW
		Else: MsgBox ("Не могу корректно вызвать UniCode FindWindowW")
			If Err.LastDllError = ERROR_INVALID_NAME Then
				Debug.Print "Не корректно задано имя при вызове Unicode 
				FindWindowW функции!"
			End If
		End If
		'Еще один эксперимент: вначале получим заголовок активного окна,
		'затем найдем окно по заголовку, работая в Unicode кодировке.
		HandleW = GetActiveWindow()
		
			'Получить заголовок окна
		ArCapt = VBA.String$(128, vbNullChar)
		
		Res = GetWindowText(HandleW, ArCapt(0), 128)
		If Res > 0 Then 'OK
			Debug.Print ArCapt
		Else: MsgBox ("не получен заголовок окна")
		End If
		
		ArCapt = VBA.Left(ArCapt, Res)
		HandleW = FindWindowW(0&, ArCapt(0))
		If HandleW > 0 Then 'OK
			Debug.Print HandleW
		Else: MsgBox ("Не могу корректно вызвать UniCode FindWindowW")
			If Err.LastDllError = ERROR_INVALID_NAME Then
				Debug.Print "Не корректно задано имя при вызове Unicode 
				FindWindowW функции!"
			End If
		End If
End Sub
6.6.

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

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 функций. Итак, необходимо:

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