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

WinApi

Функции высших порядков и конструкция AddressOf

Функцией (процедурой) высших порядков в программировании называют функцию (процедуру), один из формальных параметров которой имеет тип функции или процедуры. Введение в язык функций высших порядков существенно повышает выразительную силу языка программирования. Классическим примером процедуры высшего порядка является процедура вычисления интеграла, одним из параметров которой выступает подынтегральная функция. Типичным примером функций высших порядков являются функции Win32 API, требующие вызова Callback функций. Существует несколько способов введения функций высших порядков в язык программирования, среди которых лучшим, видимо, является способ, основанный на введении функционального типа. В этом случае можно объявлять переменные типа Func или Proc, а затем уже передавать такие переменные, как аргументы при вызове функции высшего порядка. Другой классический способ основан на работе с указателями. Такой типизированный указатель может хранить ссылку на функцию, - содержать ее адрес, и может быть передан в качестве аргумента при вызове функции высшего порядка. Именно этот способ и реализован в VBA. С этой целью в язык введена конструкция:

AddressOf имя

Параметр имя может быть именем процедуры, функции или процедуры - свойства ( Property ). В качестве результата возвращается ссылка на объект с указанным именем.

К сожалению, введя долгожданную конструкцию AddressOf, разработчики остановились на пол пути. С ее помощью можно передать функции высшего порядка в качестве аргумента имя функции. Однако, по-прежнему, нельзя описать на VB или VBA функцию высшего порядка. Такие функции должны быть внешними, как функции Win32 API или функции собственноручно разработанной DLL на языке C/C++. Можно было бы предъявить и еще одно "законное" требование к этой конструкции. Было бы весьма полезно, если бы она позволяла получать ссылку на объект любого произвольного типа, не ограничиваясь только функциональным типом. В этом случае работа с указателями в языке VBA стала бы полноценной.

В настоящее время на конструкцию AddressOf накладывается целый ряд ограничений:

  1. Эта конструкция может быть использована только в выражении, задающем вызов функции высшего порядка. Здесь она используется как аргумент, непосредственно предшествуя имени процедуры, передаваемой в качестве фактического параметра. Заметьте, эту конструкцию нельзя использовать при описании функции или процедуры, из-за чего невозможно определить функцию высшего порядка средствами VBA.
  2. Процедуры, функции и свойства, которые вызываются конструкцией AddressOf, должны быть в том же проекте, в котором помещено объявление и вызов функции высшего порядка. Вызываемые процедуры и функции должны быть расположены в стандартном модуле, они не могут находиться в модуле класса или в модуле формы.
  3. Эту конструкцию можно использовать только для вызова собственных процедур и функций, - ее нельзя использовать для вызова внешних или стандартных функций.

Функции перечисления Win32 API

Группу функций Win32 API, требующих вызова Callback функций, составляют так называемые функции перечисления, - Enum функции. Эти функции позволяют перечислить в определенном порядке все объекты операционной системы заданной группы. Вызывая на каждом шаге перечисления функцию обратного вызова, функция Win32 API передает ей текущий объект группы в качестве аргумента. Функция обратного вызова уже может производить над этим объектом различные, но, естественно, допустимые действия. Тем самым у программиста появляется возможность работы с коллекцией объектов, возможность задать собственную обработку для каждого из объектов. К функциям перечисления относятся такие функции как: EnumWindows, EnumPrinters, EnumFontFamilies, EnumFonts, EnumPorts, EnumResourseNames и многие другие. В качестве примера, рассмотрим работу с одной из этих функций.

Функция EnumWindows

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

Начнем с описания функции EnumWindows в том виде, в каком оно представлено в документации Platform SDK:

BOOL EnumWindows(
	WNDENUMPROC lpEnumFunc,	// pointer to callback function
	LPARAM lParam		// application-defined value
);

Функция EnumWindows перечисляет все окна верхнего уровня, передавая текущий описатель окна Callback функции, определенной в приложении. Функция не перечисляет дочерние окна - Child Windows. Функция выполняется, пока перечисление не будет закончено или Callback функция не вернет значение False. Ее параметры:

  1. lpEnumFunc - указатель на определенную в приложении Callback функцию.
  2. lParam - определенное приложением значение, передаваемое в Callback функцию.

Функция возвращает значение 0 в случае неуспеха и ненулевое значение при благоприятном исходе.

Описание Callback функции EnumWindowsProc, полученное из той же документации, имеет вид:

BOOL CALLBACK EnumWindowsProc(
	HWND hwnd,		// handle to parent window
	LPARAM lParam	// application-defined value
);

Функция EnumWindowsProc является Callback функцией, определенной в приложении, используемой при вызове функций Win32 API EnumWindows или EnumDesktopWindows. Она получает при вызове описатель окна верхнего уровня. Тип WNDENUMPROC определяет указатель на эту Callback функцию. Имя EnumWindowsProc является держателем места (placeholder) и должно быть замещено именем функции, определенной в приложении.

Ее параметры:

  1. hwnd - описатель окна верхнего уровня.
  2. lParam - определенное приложением значение, данное в EnumWindows или EnumDesktopWindows.

Для продолжения перечисления функция возвращает значение True, для окончания - False.

Как видите, представленные описания функций ориентированы на язык C/C++ и нуждаются в преобразовании для их использования в программах на VB/VBA. Используя обозреватель API Viewer, можно получить оператор Declare для функции EnumWindows. Вот как выглядит заголовок этой функции после соответствующей трансляции:

Public Declare Function EnumWindows Lib "user32" _
		(ByVal lpEnumFunc As Long, ByVal lParam As Long) As Long

Заметьте, появились описатели ByVal, а все типы заменились в данном случае на тип Long. Поскольку API Viewer не помощник в деле преобразования описания Callback функций, то эту работу необходимо проделать самостоятельно. В результате, описание имеет вид:

Public Function EnumWindowsProc(ByVal HandleW As Long, _
		ByVal lParam As Long) As Long

Заметим, формальная трансляция не вызывает затруднений, - описатель Callback следует опустить, имя можно дать произвольное, а трансляция типов в данном случае достаточно проста. Вместе с тем, с описанием типа параметра lParam не все так просто. Ведь он должен служить для передачи произвольной информации, поэтому теоретически допускается задание любого произвольного типа для этого параметра, например, этот параметр может быть объектом. В этом случае следует быть особо внимательным, так первая наша попытка передать функции параметр, отличный от типа Long, привела к критической ошибке и прекращении работы приложения. Как я уже говорил ранее, можно применить альтернативный способ и передавать информацию, пользуясь глобальными переменными.

Перейдем теперь к описанию реализации нашего примера. В проекте тестового документа был создан модуль с именем " ОбратныйВызов ". Вот текст раздела объявлений этого модуля:

Option Explicit
'Операторы Declare вызываемых функций Win32 API

Public Declare Function EnumWindows Lib "user32" _
	(ByVal lpEnumFunc As Long, ByVal lParam As Long) As Long

Public Declare Function GetWindowText Lib "user32" Alias "GetWindowTextA" _
	(ByVal hwnd As Long, ByVal lpString As String, ByVal cch As Long) As Long

Public Declare Function GetClassName Lib "user32" Alias "GetClassNameA" _
	(ByVal hwnd As Long, ByVal lpClassName As String, ByVal nMaxCount As Long) As Long

'Описание глобальных переменных, обеспечивающих связь
'с функцией обратного вызова EnumWindowsProc
Public HandleCol As New Collection
Public CaptCol As New Collection
Public ClassNameCol As New Collection

Помимо функции EnumWindows, основной для нашего примера, но о которой уже много говорилось, в разделе приведено объявление новой, ранее не упоминавшейся Win32 API функции GetClassName. Она похожа на функцию GetWindowText и возвращает по описателю окна имя класса этого окна. Функцию GetWindowText мы объявляем повторно, соответствующий оператор Declare есть в разделе объявлений другого модуля проекта, но, заметьте, в этом есть необходимость, поскольку в других модулях эта функция использовалась с различными псевдонимами. Раздел объявлений модуля содержит объявление трех глобальных переменных - трех коллекций, с которыми будет работать Callback функция EnumWindowsProc, формируя на каждом шаге своего вызова очередной элемент каждой из коллекций. Коллекции будут содержать соответственно описатели окон, заголовки окон и имена классов. Заметьте, что все окна имеют имя класса, но не все окна имеют заголовок, так что число элементов в коллекциях будет различным в процессе работы. Приведем теперь текст Callback процедуры EnumWindowsProc:

Public Function EnumWindowsProc(ByVal HandleW As Long, _
		ByVal lParam As Long) As Long
 Dim TextW As String
 Dim LenTextW As Long
 Dim Res As Long
 
		'Добавить описатель в коллекцию
		HandleCol.Add HandleW
 
		'Получить заголовок окна.
		TextW = VBA.String$(255, vbNullChar)
		LenTextW = VBA.Len(TextW)
		Res = GetWindowText(HandleW, TextW, LenTextW)
		If Res > 0 Then
			'Добавить заголовок в коллекцию
			TextW = VBA.Left(TextW, Res)
			CaptCol.Add TextW
		End If
 
		'Получить класс окна.
		TextW = VBA.String$(255, vbNullChar)
		LenTextW = VBA.Len(TextW)
		Res = GetClassName(HandleW, TextW, LenTextW)
		If Res > 0 Then
			'Добавить имя класса в коллекцию
			TextW = VBA.Left(TextW, Res)
			ClassNameCol.Add TextW
		End If
	EnumWindowsProc = 1
End Function
6.7.

Напомним, эта процедура вызывается автоматически в процессе работы процедуры EnumWindows. Поскольку процедура всегда возвращает значение 1, означающее успешность ее работы, то число ее вызовов определяется размером перечисляемого множества окон. Обратите внимание и на то, что в процедуре используется только первый параметр - описатель текущего окна, который передается вызываемым Win32 API функциям GetWindowText и GetClassName. Второй параметр вообще не используется, вместо этого напрямую происходит заполнение коллекций, заданных глобальными переменными. Так обеспечивается связь с внешним миром.

Чтобы закончить пример, нам осталось привести описание процедуры GetCaptions, вызывающей EnumWindows:

Public Sub GetCaptions()
		'Вызов Win32 API функции EnumWindows,
		'вызывающей в свою очередь Callback функцию EnumWindowsProc
		Dim item As Variant
		Dim Res As Long
		
		Res = EnumWindows(AddressOf EnumWindowsProc, 0&)
		
		'Обработка глобальных переменных, определенных в
		'результате совместной работы EnumWindows и EnumWindowsProc
		Debug.Print "Число окон = ", HandleCol.Count
		Debug.Print "Описатели окон"
		Res = 0
		For Each item In HandleCol
			Debug.Print item
			Res = Res + 1
			If Res > 10 Then Exit For
		Next item
		
		Debug.Print "Число окон с заголовками= ", CaptCol.Count
		Debug.Print "Заголовки окон"
		Res = 0
		For Each item In CaptCol
			Debug.Print item
			Res = Res + 1
			If Res > 10 Then Exit For
		Next item
			
	Debug.Print "Число окон, возвращающих класс = ", ClassNameCol.Count
		Debug.Print "Имена классов окон"
		Res = 0
		For Each item In ClassNameCol
			Debug.Print item
			Res = Res + 1
			If Res > 10 Then Exit For
		Next item
End Sub
6.8.

Несколько комментариев к этой процедуре:

  1. Главное, на что следует обратить внимание, это на операцию " AddressOf " в момент вызова Win32 API функции EnumWindows. В результате ее выполнения создается ссылка на Callback функцию (адрес расположения функции в памяти). Заметьте, на имя передаваемой функции не накладывается ограничений, поэтому в разных вызовах могут быть разные имена, что и позволяет иметь при необходимости несколько Сallback функций.
  2. Мы передаем 0 в качестве значения второго параметра. Это своего рода заглушка, поскольку, как говорилось ранее, передача информации производится через глобальные переменные. В следующем примере мы продемонстрируем возможность передачи информации и через этот параметр.
  3. После завершения работы процедуры перечисления начинается обработка коллекций, созданных в процессе ее работы. В данном случае обработка проста и сводится к печати числа элементов коллекции и первых десяти элементов каждой из коллекций. Возможно, было бы интересно посмотреть, сколько же объектов - окон существует в момент выполнения обычного приложения. Но полная распечатка всех имен классов и заголовков заняла бы несколько страниц текста. Ведь таких объектов несколько сотен.

Давайте познакомимся с начальными элементами коллекций, содержащих описатели, заголовки и имена классов объектов - окон, существующих в момент запуска процедуры GetCaptions:

Число окон =	254 
Описатели окон
 3735790 
 131912 
 131888 
 131916 
 65684 
 40370412 
 917748 
 262866 
 852650 
 852668 
 131844 
Число окон с заголовками =76 
Заголовки окон
Continue
Microsoft Agent
Microsoft Office Shortcut Bar
Menu Parent Window
NetDDE Agent
Edit
Microsoft Visual Basic - DocOne6 [running] - [ОбратныйВызов (Code)]
Ch6 - Microsoft Word
Edit
Properties
Microsoft Office Shortcut Bar
Число окон, возвращающих класс =254 
Имена классов окон
OfficeTooltip
tooltips_class32
ComboLBox
tooltips_class32
tooltips_class32
AgentAnimBalloon
AgentAnim
tooltips_class32
tooltips_class32
tooltips_class32
tooltips_class32
6.9.
Еще один пример работы с функцией EnumWindows

Наш следующий пример является упрощенным вариантом предыдущего примера. Его целью является демонстрация возможности передать информацию в Callback процедуру EnumWindowsProc через параметр lParam. В нашем примере вместо трех коллекций будет использоваться только одна - коллекция описателей, именно она и будет передана стандартным способом через параметр lParam. Коллекция будет передана в качестве аргумента при вызове процедуры EnumWindows, а та, в свою очередь передаст его функции обратного вызова EnumWindowsProc. Приведем текст модуля, содержащего процедуры нашего примера:

Option Explicit

Public Declare Function EnumWindows Lib "user32" _
		(ByVal lpEnumFunc As Long, ByVal lParam As Long) As Long

Public Declare Function EnumWindows1 Lib "user32" Alias "EnumWindows" _
		(ByVal lpEnumFunc As Long, lParam As Any) As Long

Public HandleCol As New Collection
Public HandleCol1 As New Collection

Public Function EnumWindowsProc(ByVal HandleW As Long, _
		ByVal lParam As Long) As Long

	HandleCol.Add HandleW
	EnumWindowsProc = 1
End Function

Public Function EnumWindowsProc1(ByVal HandleW As Long, _
		lParam As Collection) As Long

	lParam.Add HandleW
	EnumWindowsProc1 = 1
End Function

Public Sub GetHandles()
	
	Dim item As Variant
	Dim Res As Long
		
	Res = EnumWindows(AddressOf EnumWindowsProc, 0&)

	Debug.Print "Number of windows - ", HandleCol.Count
	Debug.Print "Their handles: "
	Res = 0
	For Each item In HandleCol
		Debug.Print item
		Res = Res + 1
		If Res > 10 Then Exit For
	Next item
	
End Sub

Public Sub GetHandles1()
	
	Dim item As Variant
	Dim Res As Long

	Res = EnumWindows1(AddressOf EnumWindowsProc1, HandleCol1)
		
	Debug.Print "Number of windows - ", HandleCol1.Count
	Debug.Print "Their handles: "
	Res = 0
	For Each item In HandleCol1
		Debug.Print item
		Res = Res + 1
		If Res > 10 Then Exit For
	Next item

End Sub
6.10.

Дадим несколько комментариев:

  1. В этом примере параллельно показаны оба способа передачи информации. Имена функций, оканчивающиеся на 1, связаны с передачей информации через параметр lParam.
  2. В операторе Declare, описывающем функцию EnumWindows1, тип параметра lParam задан как Any, и в данном случае параметр передается по ссылке, а не по значению.
  3. При описании Callback функции EnumWindowsProc1 для этого параметра указан уже конкретный тип Collection.
  4. Аргумент HandleCol1 типа Collection передается при вызове EnumWindows в процедуре GetHandles1. Функция обратного вызова EnumWindowsProc1 заполнит эту коллекцию элементами.

Приведем результаты ее работы:

Number of windows -184 
Their handles: 
 131826 
 131824 
 131854 
 131868 
 36504034 
 2359854 
 65636 
 262764 
 65690 
 65626 
 3539122
полина есенкова
полина есенкова
Дмитрий Вологжин
Дмитрий Вологжин
Добрый день, прошел тесты с 1 по 9, 10 не сдал, стал читать лекцию и всё пройденные тесты с 1 по 9 сбросились, когда захотел пересдать 10 тест.