2006 г.
Биллиг Владимир Арнольдович
Интернет-Университет Информационных Технологий, INTUIT.ru
Назад Оглавление Вперёд
Функции высших порядков и конструкция AddressOf
Функцией (процедурой) высших порядков в программировании называют функцию (процедуру), один из формальных параметров которой имеет тип функции или процедуры. Введение в язык функций высших порядков существенно повышает выразительную силу языка программирования. Классическим примером процедуры высшего порядка является процедура вычисления интеграла, одним из параметров которой выступает подынтегральная функция. Типичным примером функций высших порядков являются функции Win32 API, требующие вызова Callback
функций. Существует несколько способов введения функций высших порядков в язык программирования, среди которых лучшим, видимо, является способ, основанный на введении функционального типа. В этом случае можно объявлять переменные типа Func
или Proc
, а затем уже передавать такие переменные, как аргументы при вызове функции высшего порядка. Другой классический способ основан на работе с указателями. Такой типизированный указатель может хранить ссылку на функцию, - содержать ее адрес, и может быть передан в качестве аргумента при вызове функции высшего порядка. Именно этот способ и реализован в VBA. С этой целью в язык введена конструкция:
AddressOf имя
Параметр имя может быть именем процедуры, функции или процедуры - свойства (Property
). В качестве результата возвращается ссылка на объект с указанным именем.
К сожалению, введя долгожданную конструкцию AddressOf
, разработчики остановились на пол пути. С ее помощью можно передать функции высшего порядка в качестве аргумента имя функции. Однако, по-прежнему, нельзя описать на VB или VBA функцию высшего порядка. Такие функции должны быть внешними, как функции Win32 API или функции собственноручно разработанной DLL
на языке C/C++. Можно было бы предъявить и еще одно "законное" требование к этой конструкции. Было бы весьма полезно, если бы она позволяла получать ссылку на объект любого произвольного типа, не ограничиваясь только функциональным типом. В этом случае работа с указателями в языке VBA стала бы полноценной.
В настоящее время на конструкцию AddressOf
накладывается целый ряд ограничений:
- Эта конструкция может быть использована только в выражении, задающем вызов функции высшего порядка. Здесь она используется как аргумент, непосредственно предшествуя имени процедуры, передаваемой в качестве фактического параметра. Заметьте, эту конструкцию нельзя использовать при описании функции или процедуры, из-за чего невозможно определить функцию высшего порядка средствами VBA.
- Процедуры, функции и свойства, которые вызываются конструкцией
AddressOf
, должны быть в том же проекте, в котором помещено объявление и вызов функции высшего порядка. Вызываемые процедуры и функции должны быть расположены в стандартном модуле, они не могут находиться в модуле класса или в модуле формы. - Эту конструкцию можно использовать только для вызова собственных процедур и функций, - ее нельзя использовать для вызова внешних или стандартных функций.
Функции перечисления 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
. Ее параметры:
lpEnumFunc
- указатель на определенную в приложении Callback
функцию.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) и должно быть замещено именем функции, определенной в приложении.
Ее параметры:
hwnd
- описатель окна верхнего уровня.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
:
Пример 6: html, txt
Напомним, эта процедура вызывается автоматически в процессе работы процедуры EnumWindows
. Поскольку процедура всегда возвращает значение 1
, означающее успешность ее работы, то число ее вызовов определяется размером перечисляемого множества окон. Обратите внимание и на то, что в процедуре используется только первый параметр - описатель текущего окна, который передается вызываемым Win32 API функциям GetWindowText
и GetClassName
. Второй параметр вообще не используется, вместо этого напрямую происходит заполнение коллекций, заданных глобальными переменными. Так обеспечивается связь с внешним миром.
Чтобы закончить пример, нам осталось привести описание процедуры GetCaptions
, вызывающей EnumWindows
:
Пример 6: html, txt
Несколько комментариев к этой процедуре:
- Главное, на что следует обратить внимание, это на операцию "
AddressOf
" в момент вызова Win32 API функции EnumWindows
. В результате ее выполнения создается ссылка на Callback
функцию (адрес расположения функции в памяти). Заметьте, на имя передаваемой функции не накладывается ограничений, поэтому в разных вызовах могут быть разные имена, что и позволяет иметь при необходимости несколько Сallback
функций. - Мы передаем
0
в качестве значения второго параметра. Это своего рода заглушка, поскольку, как говорилось ранее, передача информации производится через глобальные переменные. В следующем примере мы продемонстрируем возможность передачи информации и через этот параметр. - После завершения работы процедуры перечисления начинается обработка коллекций, созданных в процессе ее работы. В данном случае обработка проста и сводится к печати числа элементов коллекции и первых десяти элементов каждой из коллекций. Возможно, было бы интересно посмотреть, сколько же объектов - окон существует в момент выполнения обычного приложения. Но полная распечатка всех имен классов и заголовков заняла бы несколько страниц текста. Ведь таких объектов несколько сотен.
Давайте познакомимся с начальными элементами коллекций, содержащих описатели, заголовки и имена классов объектов - окон, существующих в момент запуска процедуры GetCaptions
:
Пример 6: html, txt
Еще один пример работы с функцией EnumWindows
Наш следующий пример является упрощенным вариантом предыдущего примера. Его целью является демонстрация возможности передать информацию в Callback
процедуру EnumWindowsProc
через параметр lParam
. В нашем примере вместо трех коллекций будет использоваться только одна - коллекция описателей, именно она и будет передана стандартным способом через параметр lParam
. Коллекция будет передана в качестве аргумента при вызове процедуры EnumWindows
, а та, в свою очередь передаст его функции обратного вызова EnumWindowsProc
. Приведем текст модуля, содержащего процедуры нашего примера:
Пример 6: html, txt
Дадим несколько комментариев:
- В этом примере параллельно показаны оба способа передачи информации. Имена функций, оканчивающиеся на
1
, связаны с передачей информации через параметр lParam
. - В операторе
Declare
, описывающем функцию EnumWindows1
, тип параметра lParam
задан как Any
, и в данном случае параметр передается по ссылке, а не по значению. - При описании
Callback
функции EnumWindowsProc1
для этого параметра указан уже конкретный тип Collection
. - Аргумент
HandleCol1
типа Collection
передается при вызове EnumWindows
в процедуре GetHandles1
. Функция обратного вызова EnumWindowsProc1
заполнит эту коллекцию элементами.
Приведем результаты ее работы:
Number of windows -184
Their handles:
131826
131824
131854
131868
36504034
2359854
65636
262764
65690
65626
3539122
Назад Оглавление Вперёд