Программный код большинства примеров данной лекции можно найти в проектах, доступных для просмотра: DocOne6, DocTwo6.
API (Application Programming Interface - интерфейс прикладных программ) - это множество функций, организованных, обычно, в виде DLL. Функции API позволяют организовать интерфейс между прикладной программой и средой, в которой работает эта программа. Вызов функций API позволяет программе получать доступ к ресурсам среды и управлять ее работой. Как правило, API задает стандарт взаимодействия среды и прикладной программы.
Win32 - это название интерфейса, ориентированного на 32-х разрядные приложения и реализованного на таких известных платформах как Windows 95, Windows 98, Windows NT,
Функции, составляющие Win32 интерфейс, организованы в виде нескольких динамически подключаемых библиотек ( DLL ) и исполняемых файлов. Говоря о Win32 API, следует в первую очередь упомянуть три основные библиотеки:
DLL.Заметьте, Win API функции находятся не только в этих библиотеках. С другой стороны API функции не обязательно входят в состав Win32 интерфейса. Например,
Как мы уже говорили, функции, образующие API, обычно, организованы в виде DLL - динамически подключаемых библиотеках. Одно из достоинств DLL состоит в том, что, сколько бы приложений (процессов) не работало с функциями одной и той же DLL, код DLL существует в единственном экземпляре.
Работая на VBA, неявно всегда приходится иметь дело с функциями Win32 API, только вызов их упрятан в вызываемых VBA функциях или методах объектов Office 2000. Так, например, при работе с объектом Shape так или иначе будут вызываться функции GDI32, обеспечивающие работу с графикой, при работе c функциями VBA.Interaction, например GetSettings, SaveSettings и другими, будет вызываться соответствующие функции работы с реестром Windows, хранящиеся в библиотеках User32 и advapi32. Такой косвенный вызов имеет свои преимущества, обеспечивая определенную безопасность в работе VBA программ. Но в ряде случаев VBA программисту необходим доступ ко всем возможностям операционной системы, предоставляемым Win32 API интерфейсом. Естественно, в этом случае он понимает, что на него ложится большая ответственность в обеспечении корректного вызова функций, поскольку ошибки в вызове могут привести к непредвиденным отказам в работе программы.
Элементы ActiveX, COM объекты могут экспонировать свой интерфейс, - свои свойства и методы. Это означает, что они уведомляют, предоставляют информацию клиентам о своем интерфейсе. Технически это обеспечивается тем, что эти объекты, наряду с DLL, сопровождаются TypeLib - библиотекой типов, в которой содержится в требуемом виде информация об ActiveX объекты, построенные на основе COM технологии. Они явно экспонируют свой интерфейс, именно поэтому нет проблем при работе с такими приложениями, вызовами свойств и методов их многочисленных объектов.
Библиотеки, составляющие Win32 интерфейс, не сопровождаются библиотеками типов TypeLib. Поэтому необходимо самому программисту уведомить VBA о том, где найти и как следует вызывать ту или иную функцию Win32 API Вызову каждой функции должен предшествовать оператор Declare, описывающий эту функцию. Этот оператор и сама схема вызова библиотечных функций используется при работе с любыми DLL, а не только с теми, которые содержат Win32 API функции. В общем случае в DLL могут храниться как функции, так и процедуры. Два варианта вызова этого оператора соответствуют ссылке на процедуру и на функцию, возвращающую значение. Первый вариант:
[Public | Private] Declare Sub имя Lib "имя-библиотеки" [Alias "псевдоним"] [([параметры])]
Во втором случае его синтаксис:
[Public | Private] Declare Function имя Lib "имя-библиотеки" [Alias "псевдоним"] [([параметры])] [As возвращаемый-тип]
В этих вызовах ключевые слова и параметры имеют следующий смысл:
Public используется, чтобы сделать объявляемую процедуру доступной всем модулям проекта; ключевое слово Private ограничивает доступ к объявленной процедуре лишь модулем, в котором она объявлена. Заметьте, в стандартном модуле можно использовать оба описателя, но в модуле класса разрешается использовать только описатель Private.Sub в первом случае означает, что речь идет о процедуре; Function во втором случае указывает на функцию, возвращающую значение, которое может быть использовано в выражениях.имя является именем объявляемой процедуры или функции. Это имя используется при вызовах в VBA программах. Оно может совпадать или отличаться от того имени, под которым процедура (функция) хранится в DLL. Заметьте, для имен функций Win32 API в отличие от Win 16 имеет значение различие между прописными и строчными буквами.Lib должно следовать в кавычках имя-библиотеки, содержащей объявляемую процедуру.Alias позволяет указать, что процедура имеет в DLL другое имя - " псевдоним ", благодаря чему можно избежать коллизии имен в программе. Если первый символ параметра " псевдоним " не является признаком числа ( # ), псевдоним понимается как имя входной точки DLL для данной процедуры; если же первый символ псевдонима - ( # ), следующие за ним число должно задавать порядковый номер входной точки процедуры в DLL. Дело в том, что процедура может иметь несколько точек входа.параметры задает список параметров, передаваемых процедуре при вызове.As возвращаемый-тип во втором варианте оператора задает тип значения, возвращаемого функцией; им может быть любой из параметры - это список разделенных запятыми параметров процедуры; каждый элемент этого списка имеет вид:[Optional] [ByVal | ByRef] [ParamArray] переменная[()] [As тип]
Optional означает, что данный параметр необязателен; при этом все следующие в списке параметры должны быть необязательными и сопровождаться этим же ключом Optional. Этот ключ нельзя применять, если среди параметров есть массив параметров ParamArray.ByVal и ByRef указывают на то, что параметр передается по значению или по ссылке; по умолчанию в VBA предполагается передача значения по ссылке ( ByRef ).ParamArray позволяет задать массив элементов типа Variant ; этот параметр должен быть последним в списке и не должен перед собой иметь ключей ByVal, ByRef или Optional ; такой массив позволяет передавать в процедуру произвольное (заранее неизвестное) число параметров.Переменная - произвольное допустимое в VBA имя переменной; пустые скобки () после имени переменной означают, что соответствующий параметр - массив.As тип задает тип параметра, значения которого могут быть такими же, как и у описанного выше определения возвращаемый-тип. Задав произвольный тип ( As Any ), Вы исключите проверку при передаче параметра процедуре.Вот пример задания оператора Declare для двух функций Win32 API:
Private Declare Function CreateRectRgn Lib "gdi32" Alias "CreateRectRgn" _ (ByVal X1 As Long, ByVal Y1 As Long, ByVal X2 As Long, ByVal Y2 As Long) As Long Private Declare Function GetTempPath Lib "kernel32" _ Alias "GetTempPathA" (ByVal nBufferLength As Long, _ ByVal lpBuffer As String) As Long
Синтаксически оператор Declare прост и понятен, нужно указать библиотеку, имя функции, под которым она будет вызываться в VBA программе, ее имя (псевдоним) под которым она записана в библиотеке и параметры функции в привычном синтаксисе. Однако реальная жизнь не так проста и в организации вызова функций API есть много подводных камней. Дело в том, что DLL служат средством межязыкового взаимодействия. Сама DLL может быть разработана на одном языке, а вызываться в другом. Тогда возникает проблема правильной передачи параметров, поскольку может не быть точного соответствия между типами данных двух используемых языков. Функции Win32 API разработаны в ориентации на синтаксис языка C и C++. Поэтому при записи оператора Declare требуется корректно указать типы параметров, так чтобы они соответствовали типам, используемым в языке C. Еще одна проблема состоит в том, что помимо оператора Declare, в ряде случаев необходимо предварительно описать требуемые типы данных и константы, необходимые в процессе вызова функции. Так что, прежде чем вызвать функцию из DLL, необходимо корректно задать оператор Declare, описать необходимые типы и константы, - все это может быть не столь простой задачей.
Win32 API функции могут существовать в двух вариантах, ориентированных на две кодировки - ANSI, когда символы строк кодируются одним байтом, и Unicode, предполагающей двухбайтную кодировку. По этой причине в DLL возможны три варианта для имен функций:
Имя. В этом случае кодировка не играет значения.ИмяA. Окончание A означает, что функция использует ANSI кодировку.ИмяW. Окончание W (от Wide) свидетельствует об Unicode кодировке.Чтобы выбрать функцию в нужной кодировке, нужно правильно задать Alias имя функции. Заметьте, что в VBA вызываются функции Win32 в ANSI кодировке, поэтому следует выбирать имена с окончанием A в тех случаях, когда при вызове функции используются строки и кодировка имеет значение.
Мы уже говорили об одной из основных проблем, возникающих при вызове Win32 API функций, - необходимо обеспечить корректное задание оператора Declare, описание типов данных и констант, для чего нужно уметь корректно транслировать описание этих объектов из языка C в язык VBA. Чтобы облегчить решение этой задачи, можно воспользоваться специальным инструментальным средством, называемым API Viewer. Это средство доступно в поставке Office 2000 Developer и в Microsoft Visual Basic. Этот обозреватель позволяет:
Declare.Declare.Взгляните на начальный этап работы с обозревателем, где выбирается текстовый файл для просмотра. Обратите внимание, что обозреватель позволяет работать с текстовыми файлами, содержащими информацию о различных интерфейсах, в частности Win32 и
(рис 6.1) Выбор файла Win32API для просмотра в API ViewerНа следующем рисунке можно увидеть результаты нашей дальнейшей работы с API Viewer. Вначале из верхнего выпадающего списка API Types мы выбрали элемент Declares, в окне Available Items из списка поочередно выбрали интересующие нас функции Win32 API, - соответствующая этим функциям корректная форма записи оператора Declare появилась в окне Selected Items. На следующем шаге из верхнего списка был выбран элемент Types, из списка типов был выбран тип, в нашем примере MenuItemInfo, и корректное для VBA его определение появилось в окне Selected Items. Аналогично, можно получить корректное определение констант. Обычная технология Copy - Paste позволяет перенести информацию непосредственно в программу VBA. Взгляните, как выглядят окна обозревателя на этом этапе работы:
(рис 6.2) Получение корректной формы записи оператора Declare в окне API ViewerХотя, несомненно, обозреватель полезен, у него есть один недостаток. Было бы полезнее, если бы выбрав имя функции из списка, вся необходимая для вызова этой функции информация, - оператор
Для того чтобы получить справку по функции Win32 API, следует воспользоваться режимом Search. В окне поиска следует набрать имя нужной функции, предварительно задав требуемую область поиска. При задании области поиска необходимо, по крайней мере, включить флажки Platform SDK Documentation и Others SDK Documentation. При расширении этой области можно получить не только описание функций, но и статьи с примерами их использования. Заметьте, на этом сервере Вы всегда найдете много полезной информации.
Итак, чтобы корректно вызывать Win32 API функции на VBA, следует разбираться, чем отличаются вызовы функций в языках C и Visual Basic (VBA). Даже, если используется обозреватель API Viewer, который автоматически транслирует типы языка C в типы языка VBA, знание этих различий необходимо во многих ситуациях.
Нижеследующая таблица описывает соответствие между некоторыми, простыми типами данных двух языков:
| C/C++ тип данных | Венгерская нотация | Описание | Тип языка VBA |
|---|---|---|---|
BOOL |
b | 8-и битное булево значение. Значение 0 эквивалентно False, ненулевое значение - True |
Boolean или Long |
BYTE |
ch | 8-и битное целое без знака | Byte |
HANDLE |
h | 32 -х битное целое без знака, задающее описатель Windows -объектов | Long |
int |
n | 2-х байтное целое со знаком | Integer |
UINT |
u | 2-х байтное целое без знака | Long |
DWORD |
dw | 4-х байтное целое без знака | Long |
long |
l | 4-х байтное целое со знаком | Long |
LP |
lp | 32-х битный указатель на C/C++ структуры, строки, функции или другие данные в памяти | Long |
LPZSTR |
lpsz | 32-х битный указатель на C строку, завершаемую нулем | Long |
При преобразовании данных между типами UINT и DWORD и типом Long могут возникнуть проблемы, если заданы некорректные значения этих данных. Понятно, что если параметр функции API объявлен как UINT, то возникнет ошибка при попытке передать через тип Long отрицательное значение или длинное целое, превосходящее значение, допустимое для типа UINT. Конечно, нужно быть или осторожным или ввести собственные типы данных, где все необходимые проверки будут выполняться.
В языке C можно определять записи - совокупность данных разного типа. Такие записи в языке C называются структурами. Обычно, вначале определяется соответствующий структурный тип, а затем имя этого типа используется при объявлении конкретных Type…End Type, называемая типом, определенным пользователем. Переменные такого типа передаются вызываемой функции обычным способом: X As T, где T - имя пользовательского типа. Позже мы приведем пример вызова функции Win32 API, которой передается структура в качестве аргумента.
В языке C особенно при работе с объектами Windows, широко используется тип Handle - задающий описатели объектов. Когда создаются объекты ядра операционной системы и такие основные объекты, как окна, - все они снабжаются описателем, имеющим тип Handle. Описатель представляет длинное целое и однозначно идентифицирует объект. Всегда, когда в функцию Win32 API необходимо передать такой объект, то реально передается его описатель. При трансляции описателей в тип языка VBA ему ставится в соответствие тип Long. При этом желательно понимать, что речь все-таки идет об описателях, создаваемых операционной системой, в момент создания соответствующего объекта. Соответствующие примеры будут даны чуть позже.
В языке С формально нет процедур, есть только функции. В тех случаях, когда по существу речь идет о процедурах, вычисляющих не один скалярный результат, а имеющих несколько выходных параметров, как правило, используется функция, результат которой говорит об успешности выполнения процедуры. Если результат функции имеет значение True, то все выходные параметры благополучно вычислены, в противном случае следует проанализировать причину неуспеха. Типично для языка C то, что вызов функций является условием оператора If, - оператор If задает упаковку вызова функции, позволяя не только вызвать функцию, но и проверить корректность завершения ее работы. При трансляции заголовков таких функций в язык VBA, они естественно транслируются в функции VBA и для работы с ними можно сохранить стиль языка C.
Однако в языке C используются и функции, не возвращающие результата. Результат таких функций задается описателем Void, по существу, они являются процедурами. И при трансляции их в VBA их и следует задавать в виде процедур.
В языке C++ основным способом передачи параметров является передача их по значению, в VBA - по ссылке. Конечно, в обоих языках применяются оба способа. Тем не менее, нужно понимать, что описатель ByVal очень часто будет встречаться при вызове Win32 API функций, значительно чаще, чем при вызове обычных VBA функций. Если используется API Viewer, то этот описатель автоматически будет появляться для тех параметров, где необходима подобная форма вызова. Следует обратить внимание на одно обстоятельство. Несмотря на то, что такой описатель может появляться и для строковых аргументов, строки всегда передаются в функции Win32 API по ссылке. Последнее обстоятельство также связано со спецификой работы со строками в функциях языка C, на которых следует остановиться подробнее.
Как мы уже говорили, строки передаются по ссылке, даже если у параметра указан описатель ByVal. Не возникает никаких проблем при передаче строки в функцию Win32 API. Передаваемый аргумент может быть произвольным строковым выражением, в том числе переменной типа String или строковой константой. Сложнее дело обстоит, если функция должна вернуть строку, в качестве результата. Прежде всего, напомним, что в функциях Win32 API тип возвращаемого значения никогда не является строкой, - это всегда целочисленное значение, чаще всего булево значение, указывающее на то, удачно ли завершилось выполнение функции. Поэтому, если нужно получить строку в качестве результата, то функции передается два параметра - строка и ее длина, описанные чаще всего следующим образом:
ByVal lpResultStr As String, ByVal LenResultString As Long
Поскольку результат будет формироваться непосредственно в области памяти, отведенной строке lpResultStr, то перед вызовом эта строка должна удовлетворять следующим условиям:
LenResultString должен указывать максимально допустимое число символов результирующей строки.С учетом этих требований перед вызовом функций, возвращающих строковые значения, как правило, передаваемая функции строка инициализируется и набивается подходящим количеством нулевых символов. Напомним, нулевой символ задается константой vbNullChar. Обычно, это делается следующим образом:
Const MaxSize = 255 As Long lpResultString = String$(MaxSize, vbNullChar) LenResultString = Len(lpResultString)
После того, как функция благополучно завершит работу, приходится выделять из строки возвращенное значение. Если строка до вызова состояла сплошь из нулевых символов, то результат после вызова представляет ее префикс, заканчивающийся первым нулевым значением. В этом случае результат легко выделяется и это причина того, почему целесообразно строку инициализировать нулевыми символами.
Иногда, параметр функции API может принимать значения различных типов. В этом случае в операторе Declare нельзя указать один конкретный тип этого параметра. Для решения подобной проблемы и был введен специальный тип Any. Если параметр имеет этот тип, то в период трансляции не проверяется правильность соответствия типов и аргумент может принимать значения любых типов. Конечно, нужно иметь в виду, что функция может принимать значения хоть и нескольких, но совершенно определенных типов. Поэтому при работе с типом Any на программиста возлагается вся ответственность за корректную передачу значения функции API. Альтернативой применения типа Any является задание для одной функции нескольких операторов Declare, - по одному на каждый возможный тип параметра. Тогда в каждой конкретной ситуации будет вызываться соответствующая функция с нужным типом параметра.
Разговор об особенностях вызова Win32 API функций еще не закончен. Тем не менее, пора прервать общее изложение и обратиться к примерам. Многое из того, что было сказано, целесообразно проиллюстрировать примерами работы с конкретными функциями Win 32 API. Каждый из наших примеров будет посвящен работе с некоторой группой функций.
Как мы уже говорили, окна - это один из основных объектов операционной системы. Функции для работы с ними находятся, в основном, в библиотеке User32. Из большого множества функций мы отобрали несколько функций, позволяющих продемонстрировать, как можно получать описатели окон, как, зная описатель, можно получать характеристики окон и как можно изменять характеристики окон. Начнем с приведения программного текста, а уж потом подробно прокомментируем его. Заметим, что для получения корректного описания операторов Declare, используемых типов данных и констант мы использовали API Viewer и описание функций, которое можно найти на уже упоминавшемся сервере Microsoft.
В проекте нашего тестового документа был создан модуль "Окна" и в разделе его объявлений помещен следующий текст:
Option Explicit 'Константы Public Const SW_HIDE = 0 Public Const SW_SHOWNORMAL = 1 Public Const SW_SHOWMINIMIZED = 2 Public Const SW_SHOWMAXIMIZED = 3 'Типы Public Type RECT Left As Long Top As Long Right As Long Bottom As Long End Type 'Функции Public Declare Function GetActiveWindow Lib "user32" () As Long Public Declare Function GetWindowRect Lib "user32" (ByVal hwnd As Long, _ lpRect As RECT) 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 FindWindow Lib "user32" Alias "FindWindowA" _ (ByVal lpClassName As String, ByVal lpWindowName As String) As Long Public Declare Function ShowWindow Lib "user32" (ByVal hwnd As Long, _ ByVal nCmdShow As Long) As Long Public Declare Function SetWindowText Lib "user32" Alias "SetWindowTextA" _ (ByVal hwnd As Long, ByVal lpString As String) As Long
Дадим краткую характеристику используемых функций:
GetActiveWindow возвращает описатель активного окна.GetWindowRect получает в качестве входного параметра описатель окна hwnd и возвращает значения полей структуры Rect, переданной функции в качестве второго параметра lpRect. Заметьте, тип Rect должен быть предварительно определен. Обратите внимание, первый параметр передается по значению, а второй по ссылке. Передача по ссылке позволяет функции заполнить значениями поля переданной ей структуры. Значения этих полей задают координаты прямоугольника, определяющего положение окна на экране дисплея. Возвращаемый функцией результат будет равен нулю, если выполнение функции закончится неуспехом, например, при некорректном задании описателя.GetWindowText по описателю окна возвращает его заголовок. Поскольку функция должна вернуть строку, то, как мы говорили ранее, ей передаются два параметра - lpString и cch , задающие строку и число символов, доступных для заполнения. Обратите внимание, здесь используется псевдоним с окончанием A, указывающим на использование кодировки ANSI. Опять-таки, результат, возвращаемый функцией, зависит от успеха операции.FindWindow возвращает описатель окна. Функция ищет окно и возвращает в случае успеха описатель этого окна. Окно ищется либо по имени класса, заданного параметром lpClassName, либо по заголовку, заданному параметром lpWindowName. При вызове может быть задан только один из этих параметров, второй может быть указателем с неопределенным значением.. Поскольку параметр может быть либо указателем, имеющим тип Long, либо строкой, то в предыдущих версиях по этой причине для обоих параметров следовало указывать тип Any. Теперь указывается тип String, поскольку введена специальная константа vbNullString, формально имеющая тип String, но позволяющая передать указатель со значением Null.ShowWindow позволяет сделать видимым или невидимым окно, заданное описателем, может также минимизировать, максимизировать или нормализовать его размеры. Соответствующее действие определяется вторым параметром, значения которого задаются соответствующей константой. В разделе объявлений модуля мы определили возможные значения этих констант, которые будут использованы в процессе вызова этой функции.SetWindowText позволяет установить новый заголовок окна. Является функцией, парной к функции GetWindowText, но, поскольку здесь строка заголовка передается функции, то функция устроена проще, имеет два, а не три параметра и передавать ей можно константу, что и будет продемонстрировано в нашем примере.Приведем теперь процедуру, в которой поочередно запускаются описанные здесь функции (В примерах используются документы: DocOne6, DocTwo6
Public Sub WorkWithWindows()
Dim Res As Long 'Результат выполнения функции
Dim HandleAW As Long 'Описатель активного окна
Dim RectAW As RECT 'Структура, задающая прямоугольник окна
Dim TextAW As String 'Заголовок активного окна
Dim LenTextAW As Long 'Длина строки
Dim HandleW As Long 'Описатель окна
Dim TextW As String 'Заголовок окна
'Получить описатель активного окна
HandleAW = GetActiveWindow
Debug.Print HandleAW
'Получить прямоугольник, задающий положение активного окна
Res = GetWindowRect(HandleAW, RectAW)
Debug.Print Res
If Res > 0 Then 'OK
Debug.Print "Размеры окна: Left = ", RectAW.Left, " Top = ", _
RectAW.Top, " Right = ", RectAW.Right, " Bottom = ", RectAW.Bottom
Else:
MsgBox ("Не удалось получить размеры активного окна")
End If
'Получить заголовок окна
'Предварительная набивка результирующей строки нулевыми символами
TextAW = VBA.String$(255, vbNullChar)
LenTextAW = VBA.Len(TextAW)
Res = GetWindowText(HandleAW, TextAW, LenTextAW)
Debug.Print Res
If Res > 0 Then 'OK
TextAW = VBA.Left(TextAW, VBA.InStr(1, TextAW, vbNullChar) - 1)
Debug.Print TextAW
Else:
MsgBox ("Не удалось получить заголовок активного окна")
End If
'Поиск окна документа по его заголовку
'Возвращается описатель окна
TextW = "DocOne6 - Microsoft Word"
HandleW = FindWindow(vbNullString, TextW)
If HandleW > 0 Then 'OK
Debug.Print HandleW
Else:
MsgBox ("Не удалось найти окно с указанным заголовком" _
vbCrLf TextW)
End If
'Минимизация и нормализация окна документа
Res = ShowWindow(HandleW, SW_SHOWMINIMIZED)
If Res > 0 Then Debug.Print "Окно минимизировано"
Res = ShowWindow(HandleW, SW_SHOWNORMAL)
If Res > 0 Then Debug.Print "Окно в нормальном состоянии"
'Изменение заголовка окна
TextW = "Document1 - Microsoft Word"
HandleW = FindWindow(vbNullString, TextW)
If HandleW > 0 Then 'OK
Debug.Print HandleW
Else:
MsgBox ("Не удалось найти окно с указанным заголовком" _
vbCrLf TextW)
End If
Res = SetWindowText(HandleW, "DocTwo6 - Microsoft Word")
End Sub
Приведем сразу же и результаты отладочной печати. Заметим, что все функции выполнились успешно, поэтому не появлялись сообщения в диалоговых окнах, предусмотренные на случай неуспеха.
655706 1 Размеры окна: Left = 8 Top = 73 Right = 769 Bottom = 580 58 Microsoft Visual Basic - DocOne6 [running] - [Окна (Code)] 6684884 Окно минимизировано Окно в нормальном состоянии 191103490
Дадим комментарии к работе этой процедуры:
GetActiveWindow был получен описатель активного окна. Заметим, что это было окно кода выполняемой процедуры. Значение этого описателя равно 655706.GetWindowRect, был получен прямоугольник, определяющий положение окна на экране. Функция успешно завершила работу и вернула результат, равный 1 (истина). Как и в остальных случаях, по окончании выполнения функции мы проверили результат на успешность завершения.58, который вернула функция GetWindowText, задает число символов результирующей строки. Обратите внимание, перед вызовом функции передаваемая ей строка была инициализирована нулевыми символами. После успешного завершения из строки был выделен результат, задающий заголовок. Его печать показывает, что активным в момент запуска функции было выполняемое окно кода. Для выделения заголовка из строки использовался нулевой символ, как признак окончания заголовка. Для решения этой задачи можно было использовать и число возвращаемых символов - значение, возвращаемое функцией.FindWindow нашла такое окно и вернула его описатель, равный 6684884. Обратите внимание, первый параметр был задан константой vbNullString.ShowWindow с различными значениями констант.FindWindow для нахождения описателя вновь открытого документа. Затем при вызове функции SetWindowText был изменен заголовок этого окна. Заметьте, новое значение заголовка передавалось функции в виде обычной строковой константы.Наш следующий пример показывает, как получить, используя вызов Win32 API функций, следующие характеристики окружения:
Функции, используемые в этом примере, принадлежат библиотеке Kernel32. Заметьте, что в отличие от большинства случаев в данном примере будут вызываться не только функции, но и процедуры, хранящиеся в библиотеке Kernel32. Начнем с описания констант, типов и операторов Declare, предшествующих вызову API функций:
Option Explicit 'Константы Public Const PROCESSOR_INTEL_386 = 386 Public Const PROCESSOR_INTEL_486 = 486 Public Const PROCESSOR_INTEL_PENTIUM = 586 Public Const PROCESSOR_MIPS_R4000 = 4000 Public Const PROCESSOR_ALPHA_21064 = 21064 'Типы Type SYSTEM_INFO dwOemID As Long dwPageSize As Long lpMinimumApplicationAddress As Long lpMaximumApplicationAddress As Long dwActiveProcessorMask As Long dwNumberOrfProcessors As Long dwProcessorType As Long dwAllocationGranularity As Long dwReserved As Long End Type Type OSVERSIONINFO dwOSVersionInfoSize As Long dwMajorVersion As Long dwMinorVersion As Long dwBuildNumber As Long dwPlatformId As Long szCSDVersion As String * 128 End Type Type MEMORYSTATUS dwLength As Long dwMemoryLoad As Long dwTotalPhys As Long dwAvailPhys As Long dwTotalPageFile As Long dwAvailPageFile As Long dwTotalVirtual As Long dwAvailVirtual As Long End Type 'Операторы Declare Declare Function GetVersionEx Lib "kernel32" Alias "GetVersionExA" _ (LpVersionInformation As OSVERSIONINFO) As Long Declare Sub GlobalMemoryStatus Lib "kernel32" (lpBuffer As _ MEMORYSTATUS) Declare Sub GetSystemInfo Lib "kernel32" (lpSystemInfo As _ SYSTEM_INFO)
Для сбора информации об окружении - используемом процессоре, операционной системе, имеющейся памяти и других характеристиках компьютера используем три следующие функции (процедуры) Win32 API:
GetVersionEx имеет в качестве единственного параметра, передаваемого по ссылке, структуру типа OSVERSIONINFO. В результате выполнения функции будут заполнены поля этой структуры, содержащие всю необходимую информацию об используемой версии операционной системы. Конечно, необходимо в раздел объявлений включить и объявление типа OSVERSIONINFO. Возвращаемый функцией результат позволяет проанализировать успешность ее выполнения.GlobalMemoryStatus имеет единственный, передаваемый по ссылке параметр типа MEMORYSTATUS. В результате выполнения процедуры поля структуры заполняются собранной информацией об объеме физической и виртуальной памяти, общей и доступной для использования в текущий момент, размере слова и некоторых других характеристиках памяти.GetSystemInfo имеет единственный, передаваемый по ссылке параметр типа SYSTEM_INFO. В результате выполнения процедуры поля структуры заполняются собранной системной информацией о типе процессора, числе процессоров, используемом размере страницы и других характеристиках компьютера. Нужно ли говорить, что для корректного вызова функции требуется объявление типа SYSTEM_INFO и констант, необходимых для анализа значений поля dwProcessorType, определяющего тип центрального процессора.Приведем теперь процедуру, в которой вызываются указанные функции API, обрабатывается полученная информация, конечные результаты выводятся в окно отладки:
Public Sub WorkWithStatus()
Dim res As Long 'Результат выполнения функции
Dim msg As String ' Формируемое сообщение
Dim verinfo As OSVERSIONINFO 'Информация об ОС и ее версиях
Dim sysinfo As SYSTEM_INFO 'Системная информация
Dim memstatus As MEMORYSTATUS 'Информация о статусе памяти
verinfo.dwOSVersionInfoSize = Len(verinfo)
res = GetVersionEx(verinfo)
If res > 0 Then
Select Case verinfo.dwPlatformId
Case 0
msg = "Windows 32s "
Case 1
msg = "Windows 95/98 "
Case 2
msg = "Windows NT "
End Select
msg = msg verinfo.dwMajorVersion "." verinfo.dwMinorVersion
msg = msg " (Build " verinfo.dwBuildNumber ")" vbCrLf
Debug.Print msg
Else
MsgBox ("Не могу получить версию операционной системы")
End If
' определение типа процессора
GetSystemInfo sysinfo
msg = "Процессор: "
Select Case sysinfo.dwProcessorType
Case PROCESSOR_INTEL_386
msg = msg "Intel 386" vbCrLf
Case PROCESSOR_INTEL_486
msg = msg "Intel 486" vbCrLf
Case PROCESSOR_INTEL_PENTIUM
msg = msg "Intel Pentium" vbCrLf
Case PROCESSOR_MIPS_R4000
msg = msg "MIPS R4000" vbCrLf
Case PROCESSOR_ALPHA_21064
msg = msg "DEC Alpha 21064" vbCrLf
Case Else
msg = msg "(unknown)" vbCrLf
End Select
Debug.Print msg
msg = "Число процессоров: " sysinfo.dwNumberOrfProcessors vbCrLf
Debug.Print msg
msg = "Размер страницы: " sysinfo.dwPageSize vbCrLf
Debug.Print msg
msg = "Минимальный адрес приложения: " sysinfo.lpMinimumApplicationAddress vbCrLf
Debug.Print msg
msg = "Максимальный адрес приложения: " sysinfo.lpMaximumApplicationAddress vbCrLf
Debug.Print msg
' Получение характеристик памяти
GlobalMemoryStatus memstatus
msg = "Физическая память. Всего: " _
VBA.Format$(memstatus.dwTotalPhys \ 1024, "###,###,###") "K" vbCrLf
Debug.Print msg
msg = "Физическая память. Доступно: " _
VBA.Format$(memstatus.dwAvailPhys \ 1024, "###,###,###") "K" vbCrLf
Debug.Print msg
msg = "Виртуальная память. Всего: " _
VBA.Format$(memstatus.dwTotalVirtual \ 1024, "###,###,###") "K" vbCrLf
Debug.Print msg
msg = "Виртуальная память. Доступно: " _
VBA.Format$(memstatus.dwAvailVirtual \ 1024, "###,###,###") "K" vbCrLf
Debug.Print msg
msg = "Длина слова: " memstatus.dwLength vbCrLf
Debug.Print msg
msg = "Загрузка памяти: " memstatus.dwMemoryLoad vbCrLf
Debug.Print msg
End Sub
Программа, по-видимому, не нуждается в особых комментариях, и мы ограничимся приведением результатов отладки, которые показывают мое рабочее окружение:
Windows NT 4.0 (Build 1381) Процессор: Intel Pentium Число процессоров: 1 Размер страницы: 4096 Минимальный адрес приложения: 65536 Максимальный адрес приложения: 2147418111 Физическая память. Всего: 32 180K Физическая память. Доступно: 4 772K Виртуальная память. Всего: 2 097 024K Виртуальная память. Доступно: 1 982 872K Длина слова: 32 Загрузка памяти: 0
Уже говорилось, что функции API, работающие со строками, вызываются в ANSI кодировке. Сейчас мы попытаемся объяснить причину этого факта, а, с другой стороны, покажем, как можно вызывать функции Win32 API, использующие Unicode кодировку. Заметим, что это может быть важным не столько для функций Win32 API, сколько для других внешних функций, которые могут существовать в кодировке Unicode и не иметь ANSI варианта. Начнем с объяснения ситуации, - почему в VBA внешние функции вызываются в кодировке ANSI. Следует понимать, что строки VBA хранятся в Unicode кодировке и передача строк при вызове внутренних функций внутри VBA происходит в кодировке Unicode. Однако VB и VBA предполагают, что внешний мир устроен по-другому и до сих пор использует кодировку ANSI. Поэтому всякий раз, когда вызываются внешние функции, при вызове происходит преобразование и строковая информация передается и возвращается в кодировке ANSI. По этой причине нельзя вызвать функцию в кодировке Unicode простым изменением псевдонима, задав у него окончание W. Покажем сейчас, как можно "обмануть" VBA, заставив его не выполнять указанных преобразований, что и позволит вызывать функции, корректно работающие в Unicode кодировке. Покажем также, что, как и всякий обман, не всегда все заканчивается благополучно. Тем не менее, с предлагаемым приемом полезно познакомиться. Решение задачи основывается на следующем:
Declare необходимо тип String изменить на тип Any, что обеспечит отсутствие проверок и преобразований.Declare для строкового параметра указан спецификатор ByVal, то его необходимо удалить или изменить на ByRef, явно указав передачу параметра по ссылке.Остальные детали рассмотрим после приведения соответствующей программы. В качестве примера мы воспользуемся уже рассмотренными функциями:
FindWindow, которая позволяет найти окно по его заголовку, вернув описатель окна в качестве результата,GetWindowText, SetWindowText, позволяющие получить и установить новый заголовок окна.Заметим сразу же, что нам удалось успешно вызвать и корректно работать с двумя последними функциями в Unicode кодировке. Однако этот прием не работает при вызове функции FindWindowW. Несмотря на все попытки, переданная для поиска строка заголовка не приводила к успешному завершению поиска. Но обо всем по порядку. Приведем вначале раздел объявлений модуля с именем Unicode, созданного для работы с этим примером:
Option Explicit 'Объявление вызываемых функций в Unicode кодировке 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 ArCapt() As Byte 'Объявление динамического массива
Все пояснения уже сделаны и поэтому приведем процедуру этого модуля, вызывающую функции API:
Public Sub WorkWithUniFunc()
Dim res As Long
Dim Capt As String 'Заголовок
'Динамический массив байтов для передачи строки заголовка
Dim HandleW As Long 'Описатель окна
'Поиск окна по заголовку
Capt = "Document1 - Microsoft Word"
HandleW = FindWindowA(vbNullString, Capt)
If HandleW > 0 Then 'OK
Debug.Print HandleW
Else: MsgBox ("FindWindowA не может найти окно с заголовком" vbCrLf Capt)
End If
'Попытки использовать для поиска Unicode функцию
'FindWindowW не увенчались успехом
' ReDim ArCapt(0 To 2 * VBA.Len(Capt)) As Byte
' ArCapt = Capt vbNullChar
' Debug.Print ArCapt
' HandleW = FindWindowW(0, ArCapt(0))
' If HandleW > 0 Then 'OK
' Debug.Print HandleW
' Else: MsgBox ("Не могу вызвать UniCode FindWindowW")
' End If
'Получить заголовок окна
ArCapt = VBA.String$(128, vbNullChar)
res = GetWindowText(HandleW, ArCapt(0), 128)
If res > 0 Then 'OK
Debug.Print ArCapt
Else: MsgBox ("не получен заголовок окна")
End If
'Изменить заголовок окна
Capt = "NewDoc"
ArCapt = Capt vbNullChar
res = SetWindowText(HandleW, ArCapt(0))
'Повторно получить заголовок окна
ArCapt = VBA.String$(128, vbNullChar)
res = GetWindowText(HandleW, ArCapt(0), 128)
If res > 0 Then 'OK
Debug.Print ArCapt
Else: MsgBox ("не получен заголовок окна")
End If
End Sub
Приведем результаты отладочной печати:
327894 Document1 - Microsoft Word Document1 - Microsoft Word NewDoc
Дадим краткие комментарии к тексту процедуры:
FindWindowA, работающей в кодировке ANSI. Она успешно находит окно, заголовок которого задан переменной Capt. Функция возвращает его описатель.FindWindowW, работающую в кодировке Unicode. В тексте нашел отражение один из вариантов решения. К сожалению, поиск во всех случаях заканчивался неуспехом, хотя, как показал побайтный анализ в окне Watch и как показывает ArCapt содержит Unicode копию строки заголовкаGetWindowTextW. Функция корректно работала, используя ссылку на переданный ей массив байтов ArCapt.SetWindowTextW.GetWindowTextW. Неудача в вызове функции FindWindowW может быть вызвана разными причинами. Вероятнее всего, при выполнении поиска и проведения операций сравнения строк, применяемый способ не корректен, или, по крайней мере, требует дополнительных уточнений, возможно связанных с длиной строки. С другой стороны, сам прием является некоторой уловкой. Существует более честный способ, хотя, возможно, и более трудоемкий. Для решения задачи можно создать библиотеку типов TypeLib, содержащую описание функций Win32 API в Unicode кодировке, включить ссылку на эту библиотеку и вызывать функции без всяких уловок.
Как мы уже говорили ранее, не бывает программ без ошибок. Если ошибка возникает при выполнении кода процедур и функций 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
Приведем результаты печати , появившиеся в окне отладки при выполнении этой процедуры:
DocOne6 - Microsoft Word Не корректно задано имя при вызове Unicode FindWindowW функции! Microsoft Visual Basic - DocOne6 [running] - [Unicode (Code)] Не корректно задано имя при вызове Unicode FindWindowW функции!
Прокомментируем теперь работу программы и полученные результаты:
FindWindowW возникла ошибка периода выполнения, функция вернула нулевой результат. Ошибка была обработана, и как показывает константа ERROR_INVALID_NAME, причиной является ошибка в задании имени (передаваемый формат в виде массива байтов не годится для цели поиска и сравнения строк), о чем свидетельствует отладочная информация.GetWindowTextW, возвращающую строку в виде массива байтов. Тут же этот массив используется для поиска окна по заголовку. Однако ничего не помогает и снова при поиске окна возникает ошибка. Она обрабатывается, о чем выдается соответствующее сообщение.Мы уже говорили о функциях обратного вызова, называемых функциями. Для "многослойного" способа построения программных систем, характерного для программирования, функции внешнего слоя могут вызывать функции ядра без особых проблем. Однако паритета между ядром и внешним слоем нет. Вызов функций внешнего слоя из ядра затруднен. Чтобы как-то решить эту проблему и вводятся функций, но во всех случаях заголовок вызываемой функции жестко фиксирован и известен ядру. Этот механизм функций применяется и для обеспечения двусторонней связи между функциями VBA и функциями Win32 API, которым в процессе их работы требуется обратный вызов функций VBA.
Заметьте, в предыдущих версиях VBA не было возможности явным образом работать с функциями Win32 API, требующими вызова функций. Теперь такая возможность появилась, благодаря включению в язык возможности передачи указателя функции в качестве параметра процедур и функций. Явное введение в язык конструкции AddressOf, возвращающей указатель на функцию, дало возможность при вызове функции Win32 API передать ей в качестве аргумента имя функции. Попробуем разобраться в деталях того, как вызываются функции Win32 API, требующие функции для своей работы, как пишутся такие функции на VBA, как передается информация между функциями, - как это все, в конечном итоге, согласуется между собой. Начнем, прежде всего, с ответа на вопрос, а как узнать, что функция Win32 API требует для своей работы вызова функции. Подсказку можно получить от обозревателя, если проанализировать оператор Declare, созданный API Viewer. Когда имя параметра начинается префиксом lp, а заканчивается окончанием Func, это означает, что соответствующий аргумент является ссылкой на имя функции. К сожалению, обозреватель не содержит необходимой информации о том, каким должен быть заголовок ByVal могут дорого стоить. Пожалуй, одна из наиболее сложных задач при работе с функцией состоит в том, чтобы найти ее описание, а затем, используя документацию, ориентированную на язык C/C++, корректно описать на VBA заголовок этой функции.
Еще одна, важная для понимания задача состоит в организации правильного обмена информацией между процедурой VBA, вызываемой ею функцией Win32 API и вызываемой ею функцией. Прежде всего, следует понимать, что программисту никогда не приходится вызывать самому функцию. Ее всегда вызывает соответствующая функция Win32 API. Она же передает ей текущие значения аргументов, необходимые для работы функция производит изменения в мире объектов VBA программы и, следовательно, она должна быть каким-то образом связана с этим миром. Иногда это делается за счет того, что в функции Win32 API предусмотрен специальный параметр, который вызывающая ее программа передает ей, а она, в свою очередь, передает его функцию через глобальные переменные. Именно этот способ мы использовали в наших примерах. Прежде, чем перейти к примерам, давайте подведем итоги и еще раз сформулируем основные этапы организации работы при вызове функций Win32 API, требующих функций. Итак, необходимо:
Callback функции.Callback функцию.Callback функциями.Callback функции и другие необходимые аргументы.Функцией (процедурой) высших порядков в программировании называют функцию (процедуру), один из формальных параметров которой имеет тип функции или процедуры. Введение в язык функций высших порядков существенно повышает выразительную силу языка программирования. Классическим примером процедуры высшего порядка является процедура вычисления интеграла, одним из параметров которой выступает подынтегральная функция. Типичным примером функций высших порядков являются функции Win32 API, требующие вызова функций. Существует несколько способов введения функций высших порядков в язык программирования, среди которых лучшим, видимо, является способ, основанный на введении функционального типа. В этом случае можно объявлять переменные типа Func или Proc, а затем уже передавать такие переменные, как аргументы при вызове функции высшего порядка. Другой классический способ основан на работе с указателями. Такой
AddressOf имя
Параметр имя может быть именем процедуры, функции или процедуры - свойства ( Property ). В качестве результата возвращается ссылка на объект с указанным именем.
К сожалению, введя долгожданную конструкцию AddressOf, разработчики остановились на пол пути. С ее помощью можно передать функции высшего порядка в качестве аргумента имя функции. Однако, по-прежнему, нельзя описать на VB или VBA функцию высшего порядка. Такие функции должны быть внешними, как функции Win32 API или функции собственноручно разработанной DLL на языке C/C++. Можно было бы предъявить и еще одно "законное" требование к этой конструкции. Было бы весьма полезно, если бы она позволяла получать ссылку на объект любого произвольного типа, не ограничиваясь только функциональным типом. В этом случае работа с указателями в языке VBA стала бы полноценной.
В настоящее время на конструкцию AddressOf накладывается целый ряд ограничений:
AddressOf, должны быть в том же проекте, в котором помещено объявление и вызов функции высшего порядка. Вызываемые процедуры и функции должны быть расположены в стандартном модуле, они не могут находиться в модуле класса или в модуле формы.Группу функций Win32 API, требующих вызова функций, составляют так называемые функции перечисления, - Enum функции. Эти функции позволяют перечислить в определенном порядке все объекты операционной системы заданной группы. Вызывая на каждом шаге перечисления EnumWindows, EnumPrinters, EnumFontFamilies, EnumFonts, EnumPorts, EnumResourseNames и многие другие. В качестве примера, рассмотрим работу с одной из этих функций.
Окна являются одним из основных объектов операционной системы. Обычно, программист и не подозревает, сколь много таких объектов существует в процессе работы его программы. Функция EnumWindows позволяет перечислить все такие объекты. В нашем примере мы в результате работы с окнами сформируем три коллекции, - описателей окон, имен классов окон, заголовков окон. Задачей функции.
Начнем с описания функции EnumWindows в том виде, в каком оно представлено в документации Platform SDK:
BOOL EnumWindows( WNDENUMPROC lpEnumFunc, // pointer to callback function LPARAM lParam // application-defined value );
Функция EnumWindows перечисляет все окна верхнего уровня, передавая текущий описатель окна функции, определенной в приложении. Функция не перечисляет дочерние окна - Функция выполняется, пока перечисление не будет закончено или функция не вернет значение False. Ее параметры:
lpEnumFunc - указатель на определенную в приложении Callback функцию.lParam - определенное приложением значение, передаваемое в Callback функцию.Функция возвращает значение 0 в случае неуспеха и ненулевое значение при благоприятном исходе.
Описание функции EnumWindowsProc, полученное из той же документации, имеет вид:
BOOL CALLBACK EnumWindowsProc( HWND hwnd, // handle to parent window LPARAM lParam // application-defined value );
Функция EnumWindowsProc является функцией, определенной в приложении, используемой при вызове функций Win32 API EnumWindows или EnumDesktopWindows. Она получает при вызове описатель окна верхнего уровня. Тип WNDENUMPROC определяет указатель на эту функцию. Имя EnumWindowsProc является держателем места (
Ее параметры:
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 не помощник в деле преобразования описания функций, то эту работу необходимо проделать самостоятельно. В результате, описание имеет вид:
Public Function EnumWindowsProc(ByVal HandleW As Long, _ ByVal lParam As Long) As Long
Заметим, формальная трансляция не вызывает затруднений, - описатель следует опустить, имя можно дать произвольное, а трансляция типов в данном случае достаточно проста. Вместе с тем, с описанием типа параметра 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 есть в разделе объявлений другого модуля проекта, но, заметьте, в этом есть необходимость, поскольку в других модулях эта функция использовалась с различными псевдонимами. Раздел объявлений модуля содержит объявление трех глобальных переменных - трех коллекций, с которыми будет работать функция EnumWindowsProc, формируя на каждом шаге своего вызова очередной элемент каждой из коллекций. Коллекции будут содержать соответственно описатели окон, заголовки окон и имена классов. Заметьте, что все окна имеют имя класса, но не все окна имеют заголовок, так что число элементов в коллекциях будет различным в процессе работы. Приведем теперь текст процедуры 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
Напомним, эта процедура вызывается автоматически в процессе работы процедуры 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
Несколько комментариев к этой процедуре:
AddressOf " в момент вызова Win32 API функции EnumWindows. В результате ее выполнения создается ссылка на Callback функцию (адрес расположения функции в памяти). Заметьте, на имя передаваемой функции не накладывается ограничений, поэтому в разных вызовах могут быть разные имена, что и позволяет иметь при необходимости несколько Сallback функций.0 в качестве значения второго параметра. Это своего рода заглушка, поскольку, как говорилось ранее, передача информации производится через глобальные переменные. В следующем примере мы продемонстрируем возможность передачи информации и через этот параметр.Давайте познакомимся с начальными элементами коллекций, содержащих описатели, заголовки и имена классов объектов - окон, существующих в момент запуска процедуры 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
Наш следующий пример является упрощенным вариантом предыдущего примера. Его целью является демонстрация возможности передать информацию в процедуру 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
Дадим несколько комментариев:
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
Кроме функций перечисления, требующих в процессе своей работы вызов функций, другим известным примером является функция SetTimer, создающая таймер. Во многих приложениях возникает необходимость синхронизировать его работу в соответствии с регулярно поступающими сообщениями от таймера. Общая схема такова: таймер посылает сообщения приложению с заданным интервалом, в ответ приложение выполняет определенную работу, вызывая ту или иную функцию ( функцию). Класс таких диспетчерских приложений, регулярно обрабатывающих вновь поступившие заявки, весьма велик. При работе в приложении Access для этих целей введен специальный элемент управления - Timer. В приложениях Word или Excel такого элемента нет, но всегда можно воспользоваться соответствующими функциями Win32 API, чтобы создать один или несколько собственных таймеров и организовать работу приложения, реагирующего на их сообщения. Заметьте, несмотря на то, что физический таймер один, логических таймеров, посылающих приложению свои сообщения, может быть несколько.
Эта функция создает таймер, посылающий сообщения с заданным интервалом. Ее описание, которое можно найти на Platform SDK, имеет вид:
UINT SetTimer( HWND hWnd, // handle to window for timer messages UINT nIDEvent, // timer identifier UINT uElapse, // time-out value TIMERPROC lpTimerFunc // pointer to timer procedure );
Ее параметры:
hwnd - Описатель окна, которому будут посылаться сообщения таймера. В VBA программах таймер не связывается с окном и значение этого параметр задается как NULL.nIDEvent - Задает uElapse - Задает интервал, с которым таймер будет посылать свои сообщения. Интервал задается в миллисекундах, так что значение 1000 соответствует одной секунде.lpTimerFunc - Указатель на Callback функцию, которая будет вызываться всякий раз, когда обрабатывается сообщение WM_Timer, поступающее от таймера.Если функция успешно завершает свою работу и создает таймер, то в качестве результата она возвращает уникальный идентификатор этого таймера, идентифицирующий его. Этот идентификатор запоминается и используется для уничтожения таймера при вызове Win32 API функции KillTimer. В случае неуспеха возвращается значение 0.
Заметьте, функция SetTimer только создает таймер. В отличие от функций перечисления вызов функции не происходит в ее теле. Вызов осуществляется более сложным путем. WM_Timer автоматически вызывается
Оператор Declare, задающий VBA описание этой функции имеет соответственно вид:
Public Declare Function SetTimer Lib "user32" (ByVal hwnd As Long, _ ByVal nIDEvent As Long, ByVal uElapse As Long, ByVal lpTimerFunc As Long) As Long
Функция TimerProc является функцией, определенной приложением и вызываемой при обработке сообщений, поступающих от таймера. Ее определение имеет вид:
VOID CALLBACK TimerProc( HWND hwnd, // handle of window for timer messages UINT uMsg, // WM_TIMER message UINT idEvent, // timer identifier DWORD dwTime // current system time );
Ее параметры:
hwnd - Описатель окна.uMsg - Указывает WM_Timer сообщение.idEvent - dwTime - Задает текущее системное время, возвращаемое функцией GetTickCount.Заметьте, имя TimerProc является лишь держателем места. В конкретной ситуации необходимо будет определить одну или несколько функций с подходящими именами. Поскольку вызов каждой из этих функций производится автоматически, то нет необходимости заботиться о корректной передаче аргументов в момент вызова. Необходимо лишь позаботиться о корректной трансляции приведенного определения, взятого из справочной системы Platform SDK, к виду, воспринимаемому в программах на VBA. Вот как выглядит возможное определение:
Public Sub TimerProc(ByVal HandleW As Long, ByVal msg As Long, _ ByVal idEvent As Long, ByVal TimeSys As Long)
Обратите внимание, мы транслировали функцию в процедуру, поскольку функция TimerProc не возвращает значения. Все типы данных преобразованы в тип Long, в том числе UINT и DWORD. В данной ситуации нет причин для беспокойства о возможной некорректности передаваемых значений, поскольку их передачу обеспечивает сама система.
Поскольку одновременно могут существовать несколько таймеров, также как и при необходимости заменить один таймер другим, - по разным причинам возникает необходимость удаления уже не нужных таймеров. Для этого и используется функция Win32 API KillTimer. Вот ее стандартное описание:
BOOL KillTimer( HWND hWnd, // handle to window that installed timer UINT nIDEvent, // timer identifier );
Ее параметры:
hwnd - Описатель окна, ассоциированного с таймером, совпадающий по значению с соответствующим параметром функции SetTimer. Напомним, в VBA программах таймер не связывается с окном и значение этого параметр задается как NULL.nIDEvent - Задает NULL, его значение задается идентификатором, возвращенным в качестве результата по окончании работы функции SetTimer.Если функция успешно завершает свою работу и удаляет таймер, то в качестве результата она возвращает ненулевое значение. В случае неуспеха возвращается значение 0.
В свое время в книге по языку Visual C++ , демонстрируя работу с таймером и соответствующими функциями Win32 API, мы разработали проект " Жизнь ". В этом проекте моделируется известная компьютерная игра, где можно задать начальную конфигурацию "жизни". Затем эта конфигурация начинает жить, изменяя свое состояние по заданным правилам. Изменение состояния происходит в качестве ответной реакции на сообщения таймера. Другим подобным примером, по существу вариацией на эту же тему, является создание экранных заставок. Сейчас мы решили обойтись более простым примером, демонстрирующим суть проблемы, но не имеющим эффектной формы. В нашем тестовом примере есть две командные кнопки Start и Finish. В ответ на нажатие первой кнопки создается таймер, соответствующая ему функция ведет подсчет числа ее вызов и уведомляет об этом, печатая значение счетчика в окне отладки. При нажатии кнопки Finish таймер удаляется. Кнопки можно нажимать многократно. Все процедуры обработки помещены в модуль Таймер. Вот его текст:
'Функции работы с таймером
Public Declare Function SetTimer Lib "user32" (ByVal hwnd As Long, _
ByVal nIDEvent As Long, ByVal uElapse As Long, ByVal lpTimerFunc As Long) As Long
Public Declare Function KillTimer Lib "user32" (ByVal hwnd As Long, ByVal nIDEvent As Long) As Long
'Глобальная информация
Public Counter As Long 'Счетчик числа вызовов Callback функции
Public IdEv As Long
Public Sub HowManyProc(ByVal HandleW As Long, ByVal msg As Long, _
ByVal idEvent As Long, ByVal TimeSys As Long)
'Функция обратного вызова. Вызывается при обработке сообщения WM_Timer,
'посылаемого таймером, созданным процедурой SetTimer
Counter = Counter + 1
Debug.Print "Hi", Counter
End Sub
Public Sub Start()
'Создает таймер, вызывая Win32 Api функцию SetTimer
Counter = 0
IdEv = SetTimer(0, 0, 10000, AddressOf HowManyProc)
If IdEv = 0 Then
MsgBox ("Не удалось создать таймер!")
Else
Debug.Print "Создан Таймер: Идентификатор = ", IdEv
End If
End Sub
Public Sub Finish()
'Удаляет таймер
If IdEv > 0 Then
Call KillTimer(0, IdEv)
Debug.Print "Удален Таймер: Идентификатор = ", IdEv
IdEv = 0
End If
End Sub
Комментариев, приведенных в тексте, полагаем достаточно для понимания всех деталей. Приведем еще результаты печати, периодически появляющиеся в окне отладки. Следует только сказать, что дважды были поочередно нажаты кнопки Start и Finish:
Создан Таймер: Идентификатор =32578 Hi 1 Hi 2 Hi 3 Удален Таймер: Идентификатор =32578 Создан Таймер: Идентификатор =32573 Hi 1 Hi 2 Hi 3 Hi 4 Удален Таймер: Идентификатор =32573
Заметьте, в нашей реализации кнопки нужно нажимать поочередно, поскольку хранится только последнее значение Start, то будет создано несколько таймеров, но при последующих нескольких нажатиях кнопки Finish будет удален только один, последний
В предыдущих лекциях мы много говорили о классах и, в частности, рассматривали возможность применения класса в качестве упаковки тех или иных стандартных средств. Такой прием позволяет инкапсулировать в классе стандартные средства, - функции API, элементы управления, - скрыть многие детали работы с ними, а, зачастую, и предоставить конечному пользователю дополнительный сервис. Этот полезный прием целесообразно применять всякий раз, когда предполагается работа с функциями Win32 API. Все эти вопросы объявления функций, трансляции из одной формы описания в другую, описание многих параметров, не используемых по существу задачи, следует скрыть в описании класса, организовав интерфейс класса, более разумный с позиций конечного пользователя.
Каждый из наших примеров работы с функциями Win32 API достоин лучшей упаковки и представления его в виде класса. Мы ограничимся одним последним примером и представим в виде класса работу с таймером. Пожалуй, этот пример наиболее интересен, поскольку он представляет особый случай из-за наличия функции, которая, конечно же, не может быть стандартизована и должна быть задана конечным пользователем, в интересах которого и строится класс.
Поскольку мы уже хорошо знакомы с тем, как строятся классы и как работать с функциями Win32 API, то нам осталось рассказать, как объединить эти две вещи в единое целое. Никаких особых деталей здесь нет. Общая стратегия такова:
Применим эту общую схему для создания класса ВашТаймер. Начнем с проектирования его интерфейса. Естественно, целью класса является предоставление пользователю возможности создавать таймер, посылающий сообщения с заданным интервалом, и удалять его, когда необходимость в нем исчезнет. Пользователь не должен ничего знать о функциях Win32 API, об операторах Declare, преобразовании типов. Кажется естественным с этих позиций в интерфейс класса включить два метода: " СоздатьТаймер " и " УдалитьТаймер " и свойство " ИнтервалТаймера ", доступное для чтения и записи. Методы не имеют параметров, что облегчает работу с ними. Чтобы
Рассматриваемый нами случай упаковки функций Win32 API особый, поскольку одна из этих функций требует вызова функции. Заметьте, ВашТаймер. Чтобы не возникала соблазна поместить в класс ВашТаймер:
Option Explicit
'Класс ВашТаймер служит упаковкой функций WIN32 API работы с таймером
'Интерфейс класса будут составлять две функции:
'СоздатьТаймер, УдалитьТаймер и свойство ИнтервалТаймера
'При работе с классом необходимо описать Callback функцию по следующему образцу:
'Public Sub TimerProc(ByVal HandleW As Long, ByVal msg As Long, _
' ByVal idEvent As Long, ByVal TimeSys As Long)
' 'Функция обратного вызова. Вызывается при обработке сообщения WM_Timer,
' 'посылаемого таймером, созданным процедурой SetTimer
'
' 'Поместите здесь свой код!
'
'End Sub
'Функции Win32 API для работы с таймером
Private Declare Function SetTimer Lib "user32" (ByVal hwnd As Long, _
ByVal nIDEvent As Long, ByVal uElapse As Long, ByVal lpTimerFunc As Long) As Long
Private Declare Function KillTimer Lib "user32" (ByVal hwnd As Long, ByVal nIDEvent As Long) As Long
'Свойства: Интервал - хранит значение интервала посылки сообщений
Private Интервал As Long
'Идентификатор таймера
Private IdEv As Long
Public Sub СоздатьТаймер()
'Создает таймер, вызывая Win32 Api функцию SetTimer
IdEv = SetTimer(0, 0, Интервал, AddressOf TimerProc)
If IdEv = 0 Then
MsgBox ("Не удалось создать таймер!")
Else
Debug.Print "Создан Таймер: Идентификатор = ", IdEv
End If
End Sub
Public Sub УдалитьТаймер()
'Удаляет таймер
If IdEv > 0 Then
Call KillTimer(0, IdEv)
Debug.Print "Удален Таймер: Идентификатор = ", IdEv
IdEv = 0
End If
End Sub
Public Property Get ИнтервалТаймера() As Long
ИнтервалТаймера = Интервал
End Property
Public Property Let ИнтервалТаймера(ByVal NewValue As Long)
Интервал = NewValue
End Property
Private Sub Class_Initialize()
Интервал = 1000
End Sub
Private Sub Class_Terminate()
УдалитьТаймер
End Sub
Некоторые комментарии к этому тексту:
СоздатьТаймер и УдалитьТаймер, а также процедуры - свойства Property Get и Property Let ИнтервалТаймера, позволяющие взаимодействовать с закрытым свойством Интервал.Declare, описывающие функции Win32 API SetTimer и KillTimer, уже упомянутое свойство Интервал и свойство IdEv, хранящее Class_Initialize и Class_Terminate. В конструкторе инициализируется свойство Таймер, значение которого устанавливается по умолчанию, равным одной секунде. В TimerProc. Процедура с таким именем и уже упоминавшимися требованиями к ее заголовку должна быть описана в стандартном модуле класса, созданным конечным пользователем.Рассмотрим, как работать с классом ВашТаймер. Мы не стали изобретать ничего нового, - в тестовый документ добавили две кнопки Start1 и Finish1, которые работают также как и их тезки Start и Finish, но вызывают для этого методы и свойства объекта MyTimer класса ВашТаймер. В модуле, где объявлен соответствующий объект, находится и процедура обратного вызова TimerProc. Вот соответствующий текст этого модуля:
Option Explicit 'Модуль Таймер1 'Глобальная информация Public Counter As Long 'Счетчик числа вызовов Callback функции Public MyTimer As New ВашТаймер Public Sub Start1() MyTimer.ИнтервалТаймера = 5000 MyTimer.СоздатьТаймер End Sub Public Sub Finish1() MyTimer.УдалитьТаймер End Sub Public Sub TimerProc(ByVal HandleW As Long, ByVal msg As Long, _ ByVal idEvent As Long, ByVal TimeSys As Long) 'Функция обратного вызова. Вызывается при обработке сообщения WM_Timer, 'посылаемого таймером, созданным процедурой SetTimer Counter = Counter + 1 Debug.Print "Hi", Counter End Sub
Комментируя этот текст, следует заметить, что введение обертывающего класса облегчает работу с таймером. Единственной проблемой остается достаточно сложное и возможно непонятное конечному пользователю описание заголовка функции TimerProc. Чтобы облегчить ее решение, можно, как это сделано в нашем примере, заготовку этой функции включить в описание класса в качестве комментария.
В заключение приведем результаты эксперимента с нажатием кнопок Start1 и Finish1:
Создан Таймер: Идентификатор =31711 Hi 1 Hi 2 Hi 3 Удален Таймер: Идентификатор =31711 Создан Таймер: Идентификатор =31704 Hi 4 Hi 5 Hi 6 Hi 7 Удален Таймер: Идентификатор =31704
На этом мы закончим рассмотрение темы работы с функциями Win32 API.
Программный код большинства примеров данной лекции можно найти в проектах, доступных для просмотра: DocOne6, DocTwo6.
API (Application Programming Interface - интерфейс прикладных программ) - это множество функций, организованных, обычно, в виде DLL. Функции API позволяют организовать интерфейс между прикладной программой и средой, в которой работает эта программа. Вызов функций API позволяет программе получать доступ к ресурсам среды и управлять ее работой. Как правило, API задает стандарт взаимодействия среды и прикладной программы.
Win32 - это название интерфейса, ориентированного на 32-х разрядные приложения и реализованного на таких известных платформах как Windows 95, Windows 98, Windows NT,
Функции, составляющие Win32 интерфейс, организованы в виде нескольких динамически подключаемых библиотек ( DLL ) и исполняемых файлов. Говоря о Win32 API, следует в первую очередь упомянуть три основные библиотеки:
DLL.Заметьте, Win API функции находятся не только в этих библиотеках. С другой стороны API функции не обязательно входят в состав Win32 интерфейса. Например,
Как мы уже говорили, функции, образующие API, обычно, организованы в виде DLL - динамически подключаемых библиотеках. Одно из достоинств DLL состоит в том, что, сколько бы приложений (процессов) не работало с функциями одной и той же DLL, код DLL существует в единственном экземпляре.
Работая на VBA, неявно всегда приходится иметь дело с функциями Win32 API, только вызов их упрятан в вызываемых VBA функциях или методах объектов Office 2000. Так, например, при работе с объектом Shape так или иначе будут вызываться функции GDI32, обеспечивающие работу с графикой, при работе c функциями VBA.Interaction, например GetSettings, SaveSettings и другими, будет вызываться соответствующие функции работы с реестром Windows, хранящиеся в библиотеках User32 и advapi32. Такой косвенный вызов имеет свои преимущества, обеспечивая определенную безопасность в работе VBA программ. Но в ряде случаев VBA программисту необходим доступ ко всем возможностям операционной системы, предоставляемым Win32 API интерфейсом. Естественно, в этом случае он понимает, что на него ложится большая ответственность в обеспечении корректного вызова функций, поскольку ошибки в вызове могут привести к непредвиденным отказам в работе программы.
Элементы ActiveX, COM объекты могут экспонировать свой интерфейс, - свои свойства и методы. Это означает, что они уведомляют, предоставляют информацию клиентам о своем интерфейсе. Технически это обеспечивается тем, что эти объекты, наряду с DLL, сопровождаются TypeLib - библиотекой типов, в которой содержится в требуемом виде информация об ActiveX объекты, построенные на основе COM технологии. Они явно экспонируют свой интерфейс, именно поэтому нет проблем при работе с такими приложениями, вызовами свойств и методов их многочисленных объектов.
Библиотеки, составляющие Win32 интерфейс, не сопровождаются библиотеками типов TypeLib. Поэтому необходимо самому программисту уведомить VBA о том, где найти и как следует вызывать ту или иную функцию Win32 API Вызову каждой функции должен предшествовать оператор Declare, описывающий эту функцию. Этот оператор и сама схема вызова библиотечных функций используется при работе с любыми DLL, а не только с теми, которые содержат Win32 API функции. В общем случае в DLL могут храниться как функции, так и процедуры. Два варианта вызова этого оператора соответствуют ссылке на процедуру и на функцию, возвращающую значение. Первый вариант:
[Public | Private] Declare Sub имя Lib "имя-библиотеки" [Alias "псевдоним"] [([параметры])]
Во втором случае его синтаксис:
[Public | Private] Declare Function имя Lib "имя-библиотеки" [Alias "псевдоним"] [([параметры])] [As возвращаемый-тип]
В этих вызовах ключевые слова и параметры имеют следующий смысл:
Public используется, чтобы сделать объявляемую процедуру доступной всем модулям проекта; ключевое слово Private ограничивает доступ к объявленной процедуре лишь модулем, в котором она объявлена. Заметьте, в стандартном модуле можно использовать оба описателя, но в модуле класса разрешается использовать только описатель Private.Sub в первом случае означает, что речь идет о процедуре; Function во втором случае указывает на функцию, возвращающую значение, которое может быть использовано в выражениях.имя является именем объявляемой процедуры или функции. Это имя используется при вызовах в VBA программах. Оно может совпадать или отличаться от того имени, под которым процедура (функция) хранится в DLL. Заметьте, для имен функций Win32 API в отличие от Win 16 имеет значение различие между прописными и строчными буквами.Lib должно следовать в кавычках имя-библиотеки, содержащей объявляемую процедуру.Alias позволяет указать, что процедура имеет в DLL другое имя - " псевдоним ", благодаря чему можно избежать коллизии имен в программе. Если первый символ параметра " псевдоним " не является признаком числа ( # ), псевдоним понимается как имя входной точки DLL для данной процедуры; если же первый символ псевдонима - ( # ), следующие за ним число должно задавать порядковый номер входной точки процедуры в DLL. Дело в том, что процедура может иметь несколько точек входа.параметры задает список параметров, передаваемых процедуре при вызове.As возвращаемый-тип во втором варианте оператора задает тип значения, возвращаемого функцией; им может быть любой из параметры - это список разделенных запятыми параметров процедуры; каждый элемент этого списка имеет вид:[Optional] [ByVal | ByRef] [ParamArray] переменная[()] [As тип]
Optional означает, что данный параметр необязателен; при этом все следующие в списке параметры должны быть необязательными и сопровождаться этим же ключом Optional. Этот ключ нельзя применять, если среди параметров есть массив параметров ParamArray.ByVal и ByRef указывают на то, что параметр передается по значению или по ссылке; по умолчанию в VBA предполагается передача значения по ссылке ( ByRef ).ParamArray позволяет задать массив элементов типа Variant ; этот параметр должен быть последним в списке и не должен перед собой иметь ключей ByVal, ByRef или Optional ; такой массив позволяет передавать в процедуру произвольное (заранее неизвестное) число параметров.Переменная - произвольное допустимое в VBA имя переменной; пустые скобки () после имени переменной означают, что соответствующий параметр - массив.As тип задает тип параметра, значения которого могут быть такими же, как и у описанного выше определения возвращаемый-тип. Задав произвольный тип ( As Any ), Вы исключите проверку при передаче параметра процедуре.Вот пример задания оператора Declare для двух функций Win32 API:
Private Declare Function CreateRectRgn Lib "gdi32" Alias "CreateRectRgn" _ (ByVal X1 As Long, ByVal Y1 As Long, ByVal X2 As Long, ByVal Y2 As Long) As Long Private Declare Function GetTempPath Lib "kernel32" _ Alias "GetTempPathA" (ByVal nBufferLength As Long, _ ByVal lpBuffer As String) As Long
Синтаксически оператор Declare прост и понятен, нужно указать библиотеку, имя функции, под которым она будет вызываться в VBA программе, ее имя (псевдоним) под которым она записана в библиотеке и параметры функции в привычном синтаксисе. Однако реальная жизнь не так проста и в организации вызова функций API есть много подводных камней. Дело в том, что DLL служат средством межязыкового взаимодействия. Сама DLL может быть разработана на одном языке, а вызываться в другом. Тогда возникает проблема правильной передачи параметров, поскольку может не быть точного соответствия между типами данных двух используемых языков. Функции Win32 API разработаны в ориентации на синтаксис языка C и C++. Поэтому при записи оператора Declare требуется корректно указать типы параметров, так чтобы они соответствовали типам, используемым в языке C. Еще одна проблема состоит в том, что помимо оператора Declare, в ряде случаев необходимо предварительно описать требуемые типы данных и константы, необходимые в процессе вызова функции. Так что, прежде чем вызвать функцию из DLL, необходимо корректно задать оператор Declare, описать необходимые типы и константы, - все это может быть не столь простой задачей.
Win32 API функции могут существовать в двух вариантах, ориентированных на две кодировки - ANSI, когда символы строк кодируются одним байтом, и Unicode, предполагающей двухбайтную кодировку. По этой причине в DLL возможны три варианта для имен функций:
Имя. В этом случае кодировка не играет значения.ИмяA. Окончание A означает, что функция использует ANSI кодировку.ИмяW. Окончание W (от Wide) свидетельствует об Unicode кодировке.Чтобы выбрать функцию в нужной кодировке, нужно правильно задать Alias имя функции. Заметьте, что в VBA вызываются функции Win32 в ANSI кодировке, поэтому следует выбирать имена с окончанием A в тех случаях, когда при вызове функции используются строки и кодировка имеет значение.
Мы уже говорили об одной из основных проблем, возникающих при вызове Win32 API функций, - необходимо обеспечить корректное задание оператора Declare, описание типов данных и констант, для чего нужно уметь корректно транслировать описание этих объектов из языка C в язык VBA. Чтобы облегчить решение этой задачи, можно воспользоваться специальным инструментальным средством, называемым API Viewer. Это средство доступно в поставке Office 2000 Developer и в Microsoft Visual Basic. Этот обозреватель позволяет:
Declare.Declare.Взгляните на начальный этап работы с обозревателем, где выбирается текстовый файл для просмотра. Обратите внимание, что обозреватель позволяет работать с текстовыми файлами, содержащими информацию о различных интерфейсах, в частности Win32 и
(рис 6.1) Выбор файла Win32API для просмотра в API ViewerНа следующем рисунке можно увидеть результаты нашей дальнейшей работы с API Viewer. Вначале из верхнего выпадающего списка API Types мы выбрали элемент Declares, в окне Available Items из списка поочередно выбрали интересующие нас функции Win32 API, - соответствующая этим функциям корректная форма записи оператора Declare появилась в окне Selected Items. На следующем шаге из верхнего списка был выбран элемент Types, из списка типов был выбран тип, в нашем примере MenuItemInfo, и корректное для VBA его определение появилось в окне Selected Items. Аналогично, можно получить корректное определение констант. Обычная технология Copy - Paste позволяет перенести информацию непосредственно в программу VBA. Взгляните, как выглядят окна обозревателя на этом этапе работы:
(рис 6.2) Получение корректной формы записи оператора Declare в окне API ViewerХотя, несомненно, обозреватель полезен, у него есть один недостаток. Было бы полезнее, если бы выбрав имя функции из списка, вся необходимая для вызова этой функции информация, - оператор
Для того чтобы получить справку по функции Win32 API, следует воспользоваться режимом Search. В окне поиска следует набрать имя нужной функции, предварительно задав требуемую область поиска. При задании области поиска необходимо, по крайней мере, включить флажки Platform SDK Documentation и Others SDK Documentation. При расширении этой области можно получить не только описание функций, но и статьи с примерами их использования. Заметьте, на этом сервере Вы всегда найдете много полезной информации.
Итак, чтобы корректно вызывать Win32 API функции на VBA, следует разбираться, чем отличаются вызовы функций в языках C и Visual Basic (VBA). Даже, если используется обозреватель API Viewer, который автоматически транслирует типы языка C в типы языка VBA, знание этих различий необходимо во многих ситуациях.
Нижеследующая таблица описывает соответствие между некоторыми, простыми типами данных двух языков:
| C/C++ тип данных | Венгерская нотация | Описание | Тип языка VBA |
|---|---|---|---|
BOOL |
b | 8-и битное булево значение. Значение 0 эквивалентно False, ненулевое значение - True |
Boolean или Long |
BYTE |
ch | 8-и битное целое без знака | Byte |
HANDLE |
h | 32 -х битное целое без знака, задающее описатель Windows -объектов | Long |
int |
n | 2-х байтное целое со знаком | Integer |
UINT |
u | 2-х байтное целое без знака | Long |
DWORD |
dw | 4-х байтное целое без знака | Long |
long |
l | 4-х байтное целое со знаком | Long |
LP |
lp | 32-х битный указатель на C/C++ структуры, строки, функции или другие данные в памяти | Long |
LPZSTR |
lpsz | 32-х битный указатель на C строку, завершаемую нулем | Long |
При преобразовании данных между типами UINT и DWORD и типом Long могут возникнуть проблемы, если заданы некорректные значения этих данных. Понятно, что если параметр функции API объявлен как UINT, то возникнет ошибка при попытке передать через тип Long отрицательное значение или длинное целое, превосходящее значение, допустимое для типа UINT. Конечно, нужно быть или осторожным или ввести собственные типы данных, где все необходимые проверки будут выполняться.
В языке C можно определять записи - совокупность данных разного типа. Такие записи в языке C называются структурами. Обычно, вначале определяется соответствующий структурный тип, а затем имя этого типа используется при объявлении конкретных Type…End Type, называемая типом, определенным пользователем. Переменные такого типа передаются вызываемой функции обычным способом: X As T, где T - имя пользовательского типа. Позже мы приведем пример вызова функции Win32 API, которой передается структура в качестве аргумента.
В языке C особенно при работе с объектами Windows, широко используется тип Handle - задающий описатели объектов. Когда создаются объекты ядра операционной системы и такие основные объекты, как окна, - все они снабжаются описателем, имеющим тип Handle. Описатель представляет длинное целое и однозначно идентифицирует объект. Всегда, когда в функцию Win32 API необходимо передать такой объект, то реально передается его описатель. При трансляции описателей в тип языка VBA ему ставится в соответствие тип Long. При этом желательно понимать, что речь все-таки идет об описателях, создаваемых операционной системой, в момент создания соответствующего объекта. Соответствующие примеры будут даны чуть позже.
В языке С формально нет процедур, есть только функции. В тех случаях, когда по существу речь идет о процедурах, вычисляющих не один скалярный результат, а имеющих несколько выходных параметров, как правило, используется функция, результат которой говорит об успешности выполнения процедуры. Если результат функции имеет значение True, то все выходные параметры благополучно вычислены, в противном случае следует проанализировать причину неуспеха. Типично для языка C то, что вызов функций является условием оператора If, - оператор If задает упаковку вызова функции, позволяя не только вызвать функцию, но и проверить корректность завершения ее работы. При трансляции заголовков таких функций в язык VBA, они естественно транслируются в функции VBA и для работы с ними можно сохранить стиль языка C.
Однако в языке C используются и функции, не возвращающие результата. Результат таких функций задается описателем Void, по существу, они являются процедурами. И при трансляции их в VBA их и следует задавать в виде процедур.
В языке C++ основным способом передачи параметров является передача их по значению, в VBA - по ссылке. Конечно, в обоих языках применяются оба способа. Тем не менее, нужно понимать, что описатель ByVal очень часто будет встречаться при вызове Win32 API функций, значительно чаще, чем при вызове обычных VBA функций. Если используется API Viewer, то этот описатель автоматически будет появляться для тех параметров, где необходима подобная форма вызова. Следует обратить внимание на одно обстоятельство. Несмотря на то, что такой описатель может появляться и для строковых аргументов, строки всегда передаются в функции Win32 API по ссылке. Последнее обстоятельство также связано со спецификой работы со строками в функциях языка C, на которых следует остановиться подробнее.
Как мы уже говорили, строки передаются по ссылке, даже если у параметра указан описатель ByVal. Не возникает никаких проблем при передаче строки в функцию Win32 API. Передаваемый аргумент может быть произвольным строковым выражением, в том числе переменной типа String или строковой константой. Сложнее дело обстоит, если функция должна вернуть строку, в качестве результата. Прежде всего, напомним, что в функциях Win32 API тип возвращаемого значения никогда не является строкой, - это всегда целочисленное значение, чаще всего булево значение, указывающее на то, удачно ли завершилось выполнение функции. Поэтому, если нужно получить строку в качестве результата, то функции передается два параметра - строка и ее длина, описанные чаще всего следующим образом:
ByVal lpResultStr As String, ByVal LenResultString As Long
Поскольку результат будет формироваться непосредственно в области памяти, отведенной строке lpResultStr, то перед вызовом эта строка должна удовлетворять следующим условиям:
LenResultString должен указывать максимально допустимое число символов результирующей строки.С учетом этих требований перед вызовом функций, возвращающих строковые значения, как правило, передаваемая функции строка инициализируется и набивается подходящим количеством нулевых символов. Напомним, нулевой символ задается константой vbNullChar. Обычно, это делается следующим образом:
Const MaxSize = 255 As Long lpResultString = String$(MaxSize, vbNullChar) LenResultString = Len(lpResultString)
После того, как функция благополучно завершит работу, приходится выделять из строки возвращенное значение. Если строка до вызова состояла сплошь из нулевых символов, то результат после вызова представляет ее префикс, заканчивающийся первым нулевым значением. В этом случае результат легко выделяется и это причина того, почему целесообразно строку инициализировать нулевыми символами.
Иногда, параметр функции API может принимать значения различных типов. В этом случае в операторе Declare нельзя указать один конкретный тип этого параметра. Для решения подобной проблемы и был введен специальный тип Any. Если параметр имеет этот тип, то в период трансляции не проверяется правильность соответствия типов и аргумент может принимать значения любых типов. Конечно, нужно иметь в виду, что функция может принимать значения хоть и нескольких, но совершенно определенных типов. Поэтому при работе с типом Any на программиста возлагается вся ответственность за корректную передачу значения функции API. Альтернативой применения типа Any является задание для одной функции нескольких операторов Declare, - по одному на каждый возможный тип параметра. Тогда в каждой конкретной ситуации будет вызываться соответствующая функция с нужным типом параметра.
Разговор об особенностях вызова Win32 API функций еще не закончен. Тем не менее, пора прервать общее изложение и обратиться к примерам. Многое из того, что было сказано, целесообразно проиллюстрировать примерами работы с конкретными функциями Win 32 API. Каждый из наших примеров будет посвящен работе с некоторой группой функций.
Как мы уже говорили, окна - это один из основных объектов операционной системы. Функции для работы с ними находятся, в основном, в библиотеке User32. Из большого множества функций мы отобрали несколько функций, позволяющих продемонстрировать, как можно получать описатели окон, как, зная описатель, можно получать характеристики окон и как можно изменять характеристики окон. Начнем с приведения программного текста, а уж потом подробно прокомментируем его. Заметим, что для получения корректного описания операторов Declare, используемых типов данных и констант мы использовали API Viewer и описание функций, которое можно найти на уже упоминавшемся сервере Microsoft.
В проекте нашего тестового документа был создан модуль "Окна" и в разделе его объявлений помещен следующий текст:
Option Explicit 'Константы Public Const SW_HIDE = 0 Public Const SW_SHOWNORMAL = 1 Public Const SW_SHOWMINIMIZED = 2 Public Const SW_SHOWMAXIMIZED = 3 'Типы Public Type RECT Left As Long Top As Long Right As Long Bottom As Long End Type 'Функции Public Declare Function GetActiveWindow Lib "user32" () As Long Public Declare Function GetWindowRect Lib "user32" (ByVal hwnd As Long, _ lpRect As RECT) 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 FindWindow Lib "user32" Alias "FindWindowA" _ (ByVal lpClassName As String, ByVal lpWindowName As String) As Long Public Declare Function ShowWindow Lib "user32" (ByVal hwnd As Long, _ ByVal nCmdShow As Long) As Long Public Declare Function SetWindowText Lib "user32" Alias "SetWindowTextA" _ (ByVal hwnd As Long, ByVal lpString As String) As Long
Дадим краткую характеристику используемых функций:
GetActiveWindow возвращает описатель активного окна.GetWindowRect получает в качестве входного параметра описатель окна hwnd и возвращает значения полей структуры Rect, переданной функции в качестве второго параметра lpRect. Заметьте, тип Rect должен быть предварительно определен. Обратите внимание, первый параметр передается по значению, а второй по ссылке. Передача по ссылке позволяет функции заполнить значениями поля переданной ей структуры. Значения этих полей задают координаты прямоугольника, определяющего положение окна на экране дисплея. Возвращаемый функцией результат будет равен нулю, если выполнение функции закончится неуспехом, например, при некорректном задании описателя.GetWindowText по описателю окна возвращает его заголовок. Поскольку функция должна вернуть строку, то, как мы говорили ранее, ей передаются два параметра - lpString и cch , задающие строку и число символов, доступных для заполнения. Обратите внимание, здесь используется псевдоним с окончанием A, указывающим на использование кодировки ANSI. Опять-таки, результат, возвращаемый функцией, зависит от успеха операции.FindWindow возвращает описатель окна. Функция ищет окно и возвращает в случае успеха описатель этого окна. Окно ищется либо по имени класса, заданного параметром lpClassName, либо по заголовку, заданному параметром lpWindowName. При вызове может быть задан только один из этих параметров, второй может быть указателем с неопределенным значением.. Поскольку параметр может быть либо указателем, имеющим тип Long, либо строкой, то в предыдущих версиях по этой причине для обоих параметров следовало указывать тип Any. Теперь указывается тип String, поскольку введена специальная константа vbNullString, формально имеющая тип String, но позволяющая передать указатель со значением Null.ShowWindow позволяет сделать видимым или невидимым окно, заданное описателем, может также минимизировать, максимизировать или нормализовать его размеры. Соответствующее действие определяется вторым параметром, значения которого задаются соответствующей константой. В разделе объявлений модуля мы определили возможные значения этих констант, которые будут использованы в процессе вызова этой функции.SetWindowText позволяет установить новый заголовок окна. Является функцией, парной к функции GetWindowText, но, поскольку здесь строка заголовка передается функции, то функция устроена проще, имеет два, а не три параметра и передавать ей можно константу, что и будет продемонстрировано в нашем примере.Приведем теперь процедуру, в которой поочередно запускаются описанные здесь функции (В примерах используются документы: DocOne6, DocTwo6
Public Sub WorkWithWindows()
Dim Res As Long 'Результат выполнения функции
Dim HandleAW As Long 'Описатель активного окна
Dim RectAW As RECT 'Структура, задающая прямоугольник окна
Dim TextAW As String 'Заголовок активного окна
Dim LenTextAW As Long 'Длина строки
Dim HandleW As Long 'Описатель окна
Dim TextW As String 'Заголовок окна
'Получить описатель активного окна
HandleAW = GetActiveWindow
Debug.Print HandleAW
'Получить прямоугольник, задающий положение активного окна
Res = GetWindowRect(HandleAW, RectAW)
Debug.Print Res
If Res > 0 Then 'OK
Debug.Print "Размеры окна: Left = ", RectAW.Left, " Top = ", _
RectAW.Top, " Right = ", RectAW.Right, " Bottom = ", RectAW.Bottom
Else:
MsgBox ("Не удалось получить размеры активного окна")
End If
'Получить заголовок окна
'Предварительная набивка результирующей строки нулевыми символами
TextAW = VBA.String$(255, vbNullChar)
LenTextAW = VBA.Len(TextAW)
Res = GetWindowText(HandleAW, TextAW, LenTextAW)
Debug.Print Res
If Res > 0 Then 'OK
TextAW = VBA.Left(TextAW, VBA.InStr(1, TextAW, vbNullChar) - 1)
Debug.Print TextAW
Else:
MsgBox ("Не удалось получить заголовок активного окна")
End If
'Поиск окна документа по его заголовку
'Возвращается описатель окна
TextW = "DocOne6 - Microsoft Word"
HandleW = FindWindow(vbNullString, TextW)
If HandleW > 0 Then 'OK
Debug.Print HandleW
Else:
MsgBox ("Не удалось найти окно с указанным заголовком" _
vbCrLf TextW)
End If
'Минимизация и нормализация окна документа
Res = ShowWindow(HandleW, SW_SHOWMINIMIZED)
If Res > 0 Then Debug.Print "Окно минимизировано"
Res = ShowWindow(HandleW, SW_SHOWNORMAL)
If Res > 0 Then Debug.Print "Окно в нормальном состоянии"
'Изменение заголовка окна
TextW = "Document1 - Microsoft Word"
HandleW = FindWindow(vbNullString, TextW)
If HandleW > 0 Then 'OK
Debug.Print HandleW
Else:
MsgBox ("Не удалось найти окно с указанным заголовком" _
vbCrLf TextW)
End If
Res = SetWindowText(HandleW, "DocTwo6 - Microsoft Word")
End Sub
Приведем сразу же и результаты отладочной печати. Заметим, что все функции выполнились успешно, поэтому не появлялись сообщения в диалоговых окнах, предусмотренные на случай неуспеха.
655706 1 Размеры окна: Left = 8 Top = 73 Right = 769 Bottom = 580 58 Microsoft Visual Basic - DocOne6 [running] - [Окна (Code)] 6684884 Окно минимизировано Окно в нормальном состоянии 191103490
Дадим комментарии к работе этой процедуры:
GetActiveWindow был получен описатель активного окна. Заметим, что это было окно кода выполняемой процедуры. Значение этого описателя равно 655706.GetWindowRect, был получен прямоугольник, определяющий положение окна на экране. Функция успешно завершила работу и вернула результат, равный 1 (истина). Как и в остальных случаях, по окончании выполнения функции мы проверили результат на успешность завершения.58, который вернула функция GetWindowText, задает число символов результирующей строки. Обратите внимание, перед вызовом функции передаваемая ей строка была инициализирована нулевыми символами. После успешного завершения из строки был выделен результат, задающий заголовок. Его печать показывает, что активным в момент запуска функции было выполняемое окно кода. Для выделения заголовка из строки использовался нулевой символ, как признак окончания заголовка. Для решения этой задачи можно было использовать и число возвращаемых символов - значение, возвращаемое функцией.FindWindow нашла такое окно и вернула его описатель, равный 6684884. Обратите внимание, первый параметр был задан константой vbNullString.ShowWindow с различными значениями констант.FindWindow для нахождения описателя вновь открытого документа. Затем при вызове функции SetWindowText был изменен заголовок этого окна. Заметьте, новое значение заголовка передавалось функции в виде обычной строковой константы.Наш следующий пример показывает, как получить, используя вызов Win32 API функций, следующие характеристики окружения:
Функции, используемые в этом примере, принадлежат библиотеке Kernel32. Заметьте, что в отличие от большинства случаев в данном примере будут вызываться не только функции, но и процедуры, хранящиеся в библиотеке Kernel32. Начнем с описания констант, типов и операторов Declare, предшествующих вызову API функций:
Option Explicit 'Константы Public Const PROCESSOR_INTEL_386 = 386 Public Const PROCESSOR_INTEL_486 = 486 Public Const PROCESSOR_INTEL_PENTIUM = 586 Public Const PROCESSOR_MIPS_R4000 = 4000 Public Const PROCESSOR_ALPHA_21064 = 21064 'Типы Type SYSTEM_INFO dwOemID As Long dwPageSize As Long lpMinimumApplicationAddress As Long lpMaximumApplicationAddress As Long dwActiveProcessorMask As Long dwNumberOrfProcessors As Long dwProcessorType As Long dwAllocationGranularity As Long dwReserved As Long End Type Type OSVERSIONINFO dwOSVersionInfoSize As Long dwMajorVersion As Long dwMinorVersion As Long dwBuildNumber As Long dwPlatformId As Long szCSDVersion As String * 128 End Type Type MEMORYSTATUS dwLength As Long dwMemoryLoad As Long dwTotalPhys As Long dwAvailPhys As Long dwTotalPageFile As Long dwAvailPageFile As Long dwTotalVirtual As Long dwAvailVirtual As Long End Type 'Операторы Declare Declare Function GetVersionEx Lib "kernel32" Alias "GetVersionExA" _ (LpVersionInformation As OSVERSIONINFO) As Long Declare Sub GlobalMemoryStatus Lib "kernel32" (lpBuffer As _ MEMORYSTATUS) Declare Sub GetSystemInfo Lib "kernel32" (lpSystemInfo As _ SYSTEM_INFO)
Для сбора информации об окружении - используемом процессоре, операционной системе, имеющейся памяти и других характеристиках компьютера используем три следующие функции (процедуры) Win32 API:
GetVersionEx имеет в качестве единственного параметра, передаваемого по ссылке, структуру типа OSVERSIONINFO. В результате выполнения функции будут заполнены поля этой структуры, содержащие всю необходимую информацию об используемой версии операционной системы. Конечно, необходимо в раздел объявлений включить и объявление типа OSVERSIONINFO. Возвращаемый функцией результат позволяет проанализировать успешность ее выполнения.GlobalMemoryStatus имеет единственный, передаваемый по ссылке параметр типа MEMORYSTATUS. В результате выполнения процедуры поля структуры заполняются собранной информацией об объеме физической и виртуальной памяти, общей и доступной для использования в текущий момент, размере слова и некоторых других характеристиках памяти.GetSystemInfo имеет единственный, передаваемый по ссылке параметр типа SYSTEM_INFO. В результате выполнения процедуры поля структуры заполняются собранной системной информацией о типе процессора, числе процессоров, используемом размере страницы и других характеристиках компьютера. Нужно ли говорить, что для корректного вызова функции требуется объявление типа SYSTEM_INFO и констант, необходимых для анализа значений поля dwProcessorType, определяющего тип центрального процессора.Приведем теперь процедуру, в которой вызываются указанные функции API, обрабатывается полученная информация, конечные результаты выводятся в окно отладки:
Public Sub WorkWithStatus()
Dim res As Long 'Результат выполнения функции
Dim msg As String ' Формируемое сообщение
Dim verinfo As OSVERSIONINFO 'Информация об ОС и ее версиях
Dim sysinfo As SYSTEM_INFO 'Системная информация
Dim memstatus As MEMORYSTATUS 'Информация о статусе памяти
verinfo.dwOSVersionInfoSize = Len(verinfo)
res = GetVersionEx(verinfo)
If res > 0 Then
Select Case verinfo.dwPlatformId
Case 0
msg = "Windows 32s "
Case 1
msg = "Windows 95/98 "
Case 2
msg = "Windows NT "
End Select
msg = msg verinfo.dwMajorVersion "." verinfo.dwMinorVersion
msg = msg " (Build " verinfo.dwBuildNumber ")" vbCrLf
Debug.Print msg
Else
MsgBox ("Не могу получить версию операционной системы")
End If
' определение типа процессора
GetSystemInfo sysinfo
msg = "Процессор: "
Select Case sysinfo.dwProcessorType
Case PROCESSOR_INTEL_386
msg = msg "Intel 386" vbCrLf
Case PROCESSOR_INTEL_486
msg = msg "Intel 486" vbCrLf
Case PROCESSOR_INTEL_PENTIUM
msg = msg "Intel Pentium" vbCrLf
Case PROCESSOR_MIPS_R4000
msg = msg "MIPS R4000" vbCrLf
Case PROCESSOR_ALPHA_21064
msg = msg "DEC Alpha 21064" vbCrLf
Case Else
msg = msg "(unknown)" vbCrLf
End Select
Debug.Print msg
msg = "Число процессоров: " sysinfo.dwNumberOrfProcessors vbCrLf
Debug.Print msg
msg = "Размер страницы: " sysinfo.dwPageSize vbCrLf
Debug.Print msg
msg = "Минимальный адрес приложения: " sysinfo.lpMinimumApplicationAddress vbCrLf
Debug.Print msg
msg = "Максимальный адрес приложения: " sysinfo.lpMaximumApplicationAddress vbCrLf
Debug.Print msg
' Получение характеристик памяти
GlobalMemoryStatus memstatus
msg = "Физическая память. Всего: " _
VBA.Format$(memstatus.dwTotalPhys \ 1024, "###,###,###") "K" vbCrLf
Debug.Print msg
msg = "Физическая память. Доступно: " _
VBA.Format$(memstatus.dwAvailPhys \ 1024, "###,###,###") "K" vbCrLf
Debug.Print msg
msg = "Виртуальная память. Всего: " _
VBA.Format$(memstatus.dwTotalVirtual \ 1024, "###,###,###") "K" vbCrLf
Debug.Print msg
msg = "Виртуальная память. Доступно: " _
VBA.Format$(memstatus.dwAvailVirtual \ 1024, "###,###,###") "K" vbCrLf
Debug.Print msg
msg = "Длина слова: " memstatus.dwLength vbCrLf
Debug.Print msg
msg = "Загрузка памяти: " memstatus.dwMemoryLoad vbCrLf
Debug.Print msg
End Sub
Программа, по-видимому, не нуждается в особых комментариях, и мы ограничимся приведением результатов отладки, которые показывают мое рабочее окружение:
Windows NT 4.0 (Build 1381) Процессор: Intel Pentium Число процессоров: 1 Размер страницы: 4096 Минимальный адрес приложения: 65536 Максимальный адрес приложения: 2147418111 Физическая память. Всего: 32 180K Физическая память. Доступно: 4 772K Виртуальная память. Всего: 2 097 024K Виртуальная память. Доступно: 1 982 872K Длина слова: 32 Загрузка памяти: 0
Уже говорилось, что функции API, работающие со строками, вызываются в ANSI кодировке. Сейчас мы попытаемся объяснить причину этого факта, а, с другой стороны, покажем, как можно вызывать функции Win32 API, использующие Unicode кодировку. Заметим, что это может быть важным не столько для функций Win32 API, сколько для других внешних функций, которые могут существовать в кодировке Unicode и не иметь ANSI варианта. Начнем с объяснения ситуации, - почему в VBA внешние функции вызываются в кодировке ANSI. Следует понимать, что строки VBA хранятся в Unicode кодировке и передача строк при вызове внутренних функций внутри VBA происходит в кодировке Unicode. Однако VB и VBA предполагают, что внешний мир устроен по-другому и до сих пор использует кодировку ANSI. Поэтому всякий раз, когда вызываются внешние функции, при вызове происходит преобразование и строковая информация передается и возвращается в кодировке ANSI. По этой причине нельзя вызвать функцию в кодировке Unicode простым изменением псевдонима, задав у него окончание W. Покажем сейчас, как можно "обмануть" VBA, заставив его не выполнять указанных преобразований, что и позволит вызывать функции, корректно работающие в Unicode кодировке. Покажем также, что, как и всякий обман, не всегда все заканчивается благополучно. Тем не менее, с предлагаемым приемом полезно познакомиться. Решение задачи основывается на следующем:
Declare необходимо тип String изменить на тип Any, что обеспечит отсутствие проверок и преобразований.Declare для строкового параметра указан спецификатор ByVal, то его необходимо удалить или изменить на ByRef, явно указав передачу параметра по ссылке.Остальные детали рассмотрим после приведения соответствующей программы. В качестве примера мы воспользуемся уже рассмотренными функциями:
FindWindow, которая позволяет найти окно по его заголовку, вернув описатель окна в качестве результата,GetWindowText, SetWindowText, позволяющие получить и установить новый заголовок окна.Заметим сразу же, что нам удалось успешно вызвать и корректно работать с двумя последними функциями в Unicode кодировке. Однако этот прием не работает при вызове функции FindWindowW. Несмотря на все попытки, переданная для поиска строка заголовка не приводила к успешному завершению поиска. Но обо всем по порядку. Приведем вначале раздел объявлений модуля с именем Unicode, созданного для работы с этим примером:
Option Explicit 'Объявление вызываемых функций в Unicode кодировке 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 ArCapt() As Byte 'Объявление динамического массива
Все пояснения уже сделаны и поэтому приведем процедуру этого модуля, вызывающую функции API:
Public Sub WorkWithUniFunc()
Dim res As Long
Dim Capt As String 'Заголовок
'Динамический массив байтов для передачи строки заголовка
Dim HandleW As Long 'Описатель окна
'Поиск окна по заголовку
Capt = "Document1 - Microsoft Word"
HandleW = FindWindowA(vbNullString, Capt)
If HandleW > 0 Then 'OK
Debug.Print HandleW
Else: MsgBox ("FindWindowA не может найти окно с заголовком" vbCrLf Capt)
End If
'Попытки использовать для поиска Unicode функцию
'FindWindowW не увенчались успехом
' ReDim ArCapt(0 To 2 * VBA.Len(Capt)) As Byte
' ArCapt = Capt vbNullChar
' Debug.Print ArCapt
' HandleW = FindWindowW(0, ArCapt(0))
' If HandleW > 0 Then 'OK
' Debug.Print HandleW
' Else: MsgBox ("Не могу вызвать UniCode FindWindowW")
' End If
'Получить заголовок окна
ArCapt = VBA.String$(128, vbNullChar)
res = GetWindowText(HandleW, ArCapt(0), 128)
If res > 0 Then 'OK
Debug.Print ArCapt
Else: MsgBox ("не получен заголовок окна")
End If
'Изменить заголовок окна
Capt = "NewDoc"
ArCapt = Capt vbNullChar
res = SetWindowText(HandleW, ArCapt(0))
'Повторно получить заголовок окна
ArCapt = VBA.String$(128, vbNullChar)
res = GetWindowText(HandleW, ArCapt(0), 128)
If res > 0 Then 'OK
Debug.Print ArCapt
Else: MsgBox ("не получен заголовок окна")
End If
End Sub
Приведем результаты отладочной печати:
327894 Document1 - Microsoft Word Document1 - Microsoft Word NewDoc
Дадим краткие комментарии к тексту процедуры:
FindWindowA, работающей в кодировке ANSI. Она успешно находит окно, заголовок которого задан переменной Capt. Функция возвращает его описатель.FindWindowW, работающую в кодировке Unicode. В тексте нашел отражение один из вариантов решения. К сожалению, поиск во всех случаях заканчивался неуспехом, хотя, как показал побайтный анализ в окне Watch и как показывает ArCapt содержит Unicode копию строки заголовкаGetWindowTextW. Функция корректно работала, используя ссылку на переданный ей массив байтов ArCapt.SetWindowTextW.GetWindowTextW. Неудача в вызове функции FindWindowW может быть вызвана разными причинами. Вероятнее всего, при выполнении поиска и проведения операций сравнения строк, применяемый способ не корректен, или, по крайней мере, требует дополнительных уточнений, возможно связанных с длиной строки. С другой стороны, сам прием является некоторой уловкой. Существует более честный способ, хотя, возможно, и более трудоемкий. Для решения задачи можно создать библиотеку типов TypeLib, содержащую описание функций Win32 API в Unicode кодировке, включить ссылку на эту библиотеку и вызывать функции без всяких уловок.
Как мы уже говорили ранее, не бывает программ без ошибок. Если ошибка возникает при выполнении кода процедур и функций 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
Приведем результаты печати , появившиеся в окне отладки при выполнении этой процедуры:
DocOne6 - Microsoft Word Не корректно задано имя при вызове Unicode FindWindowW функции! Microsoft Visual Basic - DocOne6 [running] - [Unicode (Code)] Не корректно задано имя при вызове Unicode FindWindowW функции!
Прокомментируем теперь работу программы и полученные результаты:
FindWindowW возникла ошибка периода выполнения, функция вернула нулевой результат. Ошибка была обработана, и как показывает константа ERROR_INVALID_NAME, причиной является ошибка в задании имени (передаваемый формат в виде массива байтов не годится для цели поиска и сравнения строк), о чем свидетельствует отладочная информация.GetWindowTextW, возвращающую строку в виде массива байтов. Тут же этот массив используется для поиска окна по заголовку. Однако ничего не помогает и снова при поиске окна возникает ошибка. Она обрабатывается, о чем выдается соответствующее сообщение.Мы уже говорили о функциях обратного вызова, называемых функциями. Для "многослойного" способа построения программных систем, характерного для программирования, функции внешнего слоя могут вызывать функции ядра без особых проблем. Однако паритета между ядром и внешним слоем нет. Вызов функций внешнего слоя из ядра затруднен. Чтобы как-то решить эту проблему и вводятся функций, но во всех случаях заголовок вызываемой функции жестко фиксирован и известен ядру. Этот механизм функций применяется и для обеспечения двусторонней связи между функциями VBA и функциями Win32 API, которым в процессе их работы требуется обратный вызов функций VBA.
Заметьте, в предыдущих версиях VBA не было возможности явным образом работать с функциями Win32 API, требующими вызова функций. Теперь такая возможность появилась, благодаря включению в язык возможности передачи указателя функции в качестве параметра процедур и функций. Явное введение в язык конструкции AddressOf, возвращающей указатель на функцию, дало возможность при вызове функции Win32 API передать ей в качестве аргумента имя функции. Попробуем разобраться в деталях того, как вызываются функции Win32 API, требующие функции для своей работы, как пишутся такие функции на VBA, как передается информация между функциями, - как это все, в конечном итоге, согласуется между собой. Начнем, прежде всего, с ответа на вопрос, а как узнать, что функция Win32 API требует для своей работы вызова функции. Подсказку можно получить от обозревателя, если проанализировать оператор Declare, созданный API Viewer. Когда имя параметра начинается префиксом lp, а заканчивается окончанием Func, это означает, что соответствующий аргумент является ссылкой на имя функции. К сожалению, обозреватель не содержит необходимой информации о том, каким должен быть заголовок ByVal могут дорого стоить. Пожалуй, одна из наиболее сложных задач при работе с функцией состоит в том, чтобы найти ее описание, а затем, используя документацию, ориентированную на язык C/C++, корректно описать на VBA заголовок этой функции.
Еще одна, важная для понимания задача состоит в организации правильного обмена информацией между процедурой VBA, вызываемой ею функцией Win32 API и вызываемой ею функцией. Прежде всего, следует понимать, что программисту никогда не приходится вызывать самому функцию. Ее всегда вызывает соответствующая функция Win32 API. Она же передает ей текущие значения аргументов, необходимые для работы функция производит изменения в мире объектов VBA программы и, следовательно, она должна быть каким-то образом связана с этим миром. Иногда это делается за счет того, что в функции Win32 API предусмотрен специальный параметр, который вызывающая ее программа передает ей, а она, в свою очередь, передает его функцию через глобальные переменные. Именно этот способ мы использовали в наших примерах. Прежде, чем перейти к примерам, давайте подведем итоги и еще раз сформулируем основные этапы организации работы при вызове функций Win32 API, требующих функций. Итак, необходимо:
Callback функции.Callback функцию.Callback функциями.Callback функции и другие необходимые аргументы.Функцией (процедурой) высших порядков в программировании называют функцию (процедуру), один из формальных параметров которой имеет тип функции или процедуры. Введение в язык функций высших порядков существенно повышает выразительную силу языка программирования. Классическим примером процедуры высшего порядка является процедура вычисления интеграла, одним из параметров которой выступает подынтегральная функция. Типичным примером функций высших порядков являются функции Win32 API, требующие вызова функций. Существует несколько способов введения функций высших порядков в язык программирования, среди которых лучшим, видимо, является способ, основанный на введении функционального типа. В этом случае можно объявлять переменные типа Func или Proc, а затем уже передавать такие переменные, как аргументы при вызове функции высшего порядка. Другой классический способ основан на работе с указателями. Такой
AddressOf имя
Параметр имя может быть именем процедуры, функции или процедуры - свойства ( Property ). В качестве результата возвращается ссылка на объект с указанным именем.
К сожалению, введя долгожданную конструкцию AddressOf, разработчики остановились на пол пути. С ее помощью можно передать функции высшего порядка в качестве аргумента имя функции. Однако, по-прежнему, нельзя описать на VB или VBA функцию высшего порядка. Такие функции должны быть внешними, как функции Win32 API или функции собственноручно разработанной DLL на языке C/C++. Можно было бы предъявить и еще одно "законное" требование к этой конструкции. Было бы весьма полезно, если бы она позволяла получать ссылку на объект любого произвольного типа, не ограничиваясь только функциональным типом. В этом случае работа с указателями в языке VBA стала бы полноценной.
В настоящее время на конструкцию AddressOf накладывается целый ряд ограничений:
AddressOf, должны быть в том же проекте, в котором помещено объявление и вызов функции высшего порядка. Вызываемые процедуры и функции должны быть расположены в стандартном модуле, они не могут находиться в модуле класса или в модуле формы.Группу функций Win32 API, требующих вызова функций, составляют так называемые функции перечисления, - Enum функции. Эти функции позволяют перечислить в определенном порядке все объекты операционной системы заданной группы. Вызывая на каждом шаге перечисления EnumWindows, EnumPrinters, EnumFontFamilies, EnumFonts, EnumPorts, EnumResourseNames и многие другие. В качестве примера, рассмотрим работу с одной из этих функций.
Окна являются одним из основных объектов операционной системы. Обычно, программист и не подозревает, сколь много таких объектов существует в процессе работы его программы. Функция EnumWindows позволяет перечислить все такие объекты. В нашем примере мы в результате работы с окнами сформируем три коллекции, - описателей окон, имен классов окон, заголовков окон. Задачей функции.
Начнем с описания функции EnumWindows в том виде, в каком оно представлено в документации Platform SDK:
BOOL EnumWindows( WNDENUMPROC lpEnumFunc, // pointer to callback function LPARAM lParam // application-defined value );
Функция EnumWindows перечисляет все окна верхнего уровня, передавая текущий описатель окна функции, определенной в приложении. Функция не перечисляет дочерние окна - Функция выполняется, пока перечисление не будет закончено или функция не вернет значение False. Ее параметры:
lpEnumFunc - указатель на определенную в приложении Callback функцию.lParam - определенное приложением значение, передаваемое в Callback функцию.Функция возвращает значение 0 в случае неуспеха и ненулевое значение при благоприятном исходе.
Описание функции EnumWindowsProc, полученное из той же документации, имеет вид:
BOOL CALLBACK EnumWindowsProc( HWND hwnd, // handle to parent window LPARAM lParam // application-defined value );
Функция EnumWindowsProc является функцией, определенной в приложении, используемой при вызове функций Win32 API EnumWindows или EnumDesktopWindows. Она получает при вызове описатель окна верхнего уровня. Тип WNDENUMPROC определяет указатель на эту функцию. Имя EnumWindowsProc является держателем места (
Ее параметры:
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 не помощник в деле преобразования описания функций, то эту работу необходимо проделать самостоятельно. В результате, описание имеет вид:
Public Function EnumWindowsProc(ByVal HandleW As Long, _ ByVal lParam As Long) As Long
Заметим, формальная трансляция не вызывает затруднений, - описатель следует опустить, имя можно дать произвольное, а трансляция типов в данном случае достаточно проста. Вместе с тем, с описанием типа параметра 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 есть в разделе объявлений другого модуля проекта, но, заметьте, в этом есть необходимость, поскольку в других модулях эта функция использовалась с различными псевдонимами. Раздел объявлений модуля содержит объявление трех глобальных переменных - трех коллекций, с которыми будет работать функция EnumWindowsProc, формируя на каждом шаге своего вызова очередной элемент каждой из коллекций. Коллекции будут содержать соответственно описатели окон, заголовки окон и имена классов. Заметьте, что все окна имеют имя класса, но не все окна имеют заголовок, так что число элементов в коллекциях будет различным в процессе работы. Приведем теперь текст процедуры 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
Напомним, эта процедура вызывается автоматически в процессе работы процедуры 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
Несколько комментариев к этой процедуре:
AddressOf " в момент вызова Win32 API функции EnumWindows. В результате ее выполнения создается ссылка на Callback функцию (адрес расположения функции в памяти). Заметьте, на имя передаваемой функции не накладывается ограничений, поэтому в разных вызовах могут быть разные имена, что и позволяет иметь при необходимости несколько Сallback функций.0 в качестве значения второго параметра. Это своего рода заглушка, поскольку, как говорилось ранее, передача информации производится через глобальные переменные. В следующем примере мы продемонстрируем возможность передачи информации и через этот параметр.Давайте познакомимся с начальными элементами коллекций, содержащих описатели, заголовки и имена классов объектов - окон, существующих в момент запуска процедуры 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
Наш следующий пример является упрощенным вариантом предыдущего примера. Его целью является демонстрация возможности передать информацию в процедуру 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
Дадим несколько комментариев:
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
Кроме функций перечисления, требующих в процессе своей работы вызов функций, другим известным примером является функция SetTimer, создающая таймер. Во многих приложениях возникает необходимость синхронизировать его работу в соответствии с регулярно поступающими сообщениями от таймера. Общая схема такова: таймер посылает сообщения приложению с заданным интервалом, в ответ приложение выполняет определенную работу, вызывая ту или иную функцию ( функцию). Класс таких диспетчерских приложений, регулярно обрабатывающих вновь поступившие заявки, весьма велик. При работе в приложении Access для этих целей введен специальный элемент управления - Timer. В приложениях Word или Excel такого элемента нет, но всегда можно воспользоваться соответствующими функциями Win32 API, чтобы создать один или несколько собственных таймеров и организовать работу приложения, реагирующего на их сообщения. Заметьте, несмотря на то, что физический таймер один, логических таймеров, посылающих приложению свои сообщения, может быть несколько.
Эта функция создает таймер, посылающий сообщения с заданным интервалом. Ее описание, которое можно найти на Platform SDK, имеет вид:
UINT SetTimer( HWND hWnd, // handle to window for timer messages UINT nIDEvent, // timer identifier UINT uElapse, // time-out value TIMERPROC lpTimerFunc // pointer to timer procedure );
Ее параметры:
hwnd - Описатель окна, которому будут посылаться сообщения таймера. В VBA программах таймер не связывается с окном и значение этого параметр задается как NULL.nIDEvent - Задает uElapse - Задает интервал, с которым таймер будет посылать свои сообщения. Интервал задается в миллисекундах, так что значение 1000 соответствует одной секунде.lpTimerFunc - Указатель на Callback функцию, которая будет вызываться всякий раз, когда обрабатывается сообщение WM_Timer, поступающее от таймера.Если функция успешно завершает свою работу и создает таймер, то в качестве результата она возвращает уникальный идентификатор этого таймера, идентифицирующий его. Этот идентификатор запоминается и используется для уничтожения таймера при вызове Win32 API функции KillTimer. В случае неуспеха возвращается значение 0.
Заметьте, функция SetTimer только создает таймер. В отличие от функций перечисления вызов функции не происходит в ее теле. Вызов осуществляется более сложным путем. WM_Timer автоматически вызывается
Оператор Declare, задающий VBA описание этой функции имеет соответственно вид:
Public Declare Function SetTimer Lib "user32" (ByVal hwnd As Long, _ ByVal nIDEvent As Long, ByVal uElapse As Long, ByVal lpTimerFunc As Long) As Long
Функция TimerProc является функцией, определенной приложением и вызываемой при обработке сообщений, поступающих от таймера. Ее определение имеет вид:
VOID CALLBACK TimerProc( HWND hwnd, // handle of window for timer messages UINT uMsg, // WM_TIMER message UINT idEvent, // timer identifier DWORD dwTime // current system time );
Ее параметры:
hwnd - Описатель окна.uMsg - Указывает WM_Timer сообщение.idEvent - dwTime - Задает текущее системное время, возвращаемое функцией GetTickCount.Заметьте, имя TimerProc является лишь держателем места. В конкретной ситуации необходимо будет определить одну или несколько функций с подходящими именами. Поскольку вызов каждой из этих функций производится автоматически, то нет необходимости заботиться о корректной передаче аргументов в момент вызова. Необходимо лишь позаботиться о корректной трансляции приведенного определения, взятого из справочной системы Platform SDK, к виду, воспринимаемому в программах на VBA. Вот как выглядит возможное определение:
Public Sub TimerProc(ByVal HandleW As Long, ByVal msg As Long, _ ByVal idEvent As Long, ByVal TimeSys As Long)
Обратите внимание, мы транслировали функцию в процедуру, поскольку функция TimerProc не возвращает значения. Все типы данных преобразованы в тип Long, в том числе UINT и DWORD. В данной ситуации нет причин для беспокойства о возможной некорректности передаваемых значений, поскольку их передачу обеспечивает сама система.
Поскольку одновременно могут существовать несколько таймеров, также как и при необходимости заменить один таймер другим, - по разным причинам возникает необходимость удаления уже не нужных таймеров. Для этого и используется функция Win32 API KillTimer. Вот ее стандартное описание:
BOOL KillTimer( HWND hWnd, // handle to window that installed timer UINT nIDEvent, // timer identifier );
Ее параметры:
hwnd - Описатель окна, ассоциированного с таймером, совпадающий по значению с соответствующим параметром функции SetTimer. Напомним, в VBA программах таймер не связывается с окном и значение этого параметр задается как NULL.nIDEvent - Задает NULL, его значение задается идентификатором, возвращенным в качестве результата по окончании работы функции SetTimer.Если функция успешно завершает свою работу и удаляет таймер, то в качестве результата она возвращает ненулевое значение. В случае неуспеха возвращается значение 0.
В свое время в книге по языку Visual C++ , демонстрируя работу с таймером и соответствующими функциями Win32 API, мы разработали проект " Жизнь ". В этом проекте моделируется известная компьютерная игра, где можно задать начальную конфигурацию "жизни". Затем эта конфигурация начинает жить, изменяя свое состояние по заданным правилам. Изменение состояния происходит в качестве ответной реакции на сообщения таймера. Другим подобным примером, по существу вариацией на эту же тему, является создание экранных заставок. Сейчас мы решили обойтись более простым примером, демонстрирующим суть проблемы, но не имеющим эффектной формы. В нашем тестовом примере есть две командные кнопки Start и Finish. В ответ на нажатие первой кнопки создается таймер, соответствующая ему функция ведет подсчет числа ее вызов и уведомляет об этом, печатая значение счетчика в окне отладки. При нажатии кнопки Finish таймер удаляется. Кнопки можно нажимать многократно. Все процедуры обработки помещены в модуль Таймер. Вот его текст:
'Функции работы с таймером
Public Declare Function SetTimer Lib "user32" (ByVal hwnd As Long, _
ByVal nIDEvent As Long, ByVal uElapse As Long, ByVal lpTimerFunc As Long) As Long
Public Declare Function KillTimer Lib "user32" (ByVal hwnd As Long, ByVal nIDEvent As Long) As Long
'Глобальная информация
Public Counter As Long 'Счетчик числа вызовов Callback функции
Public IdEv As Long
Public Sub HowManyProc(ByVal HandleW As Long, ByVal msg As Long, _
ByVal idEvent As Long, ByVal TimeSys As Long)
'Функция обратного вызова. Вызывается при обработке сообщения WM_Timer,
'посылаемого таймером, созданным процедурой SetTimer
Counter = Counter + 1
Debug.Print "Hi", Counter
End Sub
Public Sub Start()
'Создает таймер, вызывая Win32 Api функцию SetTimer
Counter = 0
IdEv = SetTimer(0, 0, 10000, AddressOf HowManyProc)
If IdEv = 0 Then
MsgBox ("Не удалось создать таймер!")
Else
Debug.Print "Создан Таймер: Идентификатор = ", IdEv
End If
End Sub
Public Sub Finish()
'Удаляет таймер
If IdEv > 0 Then
Call KillTimer(0, IdEv)
Debug.Print "Удален Таймер: Идентификатор = ", IdEv
IdEv = 0
End If
End Sub
Комментариев, приведенных в тексте, полагаем достаточно для понимания всех деталей. Приведем еще результаты печати, периодически появляющиеся в окне отладки. Следует только сказать, что дважды были поочередно нажаты кнопки Start и Finish:
Создан Таймер: Идентификатор =32578 Hi 1 Hi 2 Hi 3 Удален Таймер: Идентификатор =32578 Создан Таймер: Идентификатор =32573 Hi 1 Hi 2 Hi 3 Hi 4 Удален Таймер: Идентификатор =32573
Заметьте, в нашей реализации кнопки нужно нажимать поочередно, поскольку хранится только последнее значение Start, то будет создано несколько таймеров, но при последующих нескольких нажатиях кнопки Finish будет удален только один, последний
В предыдущих лекциях мы много говорили о классах и, в частности, рассматривали возможность применения класса в качестве упаковки тех или иных стандартных средств. Такой прием позволяет инкапсулировать в классе стандартные средства, - функции API, элементы управления, - скрыть многие детали работы с ними, а, зачастую, и предоставить конечному пользователю дополнительный сервис. Этот полезный прием целесообразно применять всякий раз, когда предполагается работа с функциями Win32 API. Все эти вопросы объявления функций, трансляции из одной формы описания в другую, описание многих параметров, не используемых по существу задачи, следует скрыть в описании класса, организовав интерфейс класса, более разумный с позиций конечного пользователя.
Каждый из наших примеров работы с функциями Win32 API достоин лучшей упаковки и представления его в виде класса. Мы ограничимся одним последним примером и представим в виде класса работу с таймером. Пожалуй, этот пример наиболее интересен, поскольку он представляет особый случай из-за наличия функции, которая, конечно же, не может быть стандартизована и должна быть задана конечным пользователем, в интересах которого и строится класс.
Поскольку мы уже хорошо знакомы с тем, как строятся классы и как работать с функциями Win32 API, то нам осталось рассказать, как объединить эти две вещи в единое целое. Никаких особых деталей здесь нет. Общая стратегия такова:
Применим эту общую схему для создания класса ВашТаймер. Начнем с проектирования его интерфейса. Естественно, целью класса является предоставление пользователю возможности создавать таймер, посылающий сообщения с заданным интервалом, и удалять его, когда необходимость в нем исчезнет. Пользователь не должен ничего знать о функциях Win32 API, об операторах Declare, преобразовании типов. Кажется естественным с этих позиций в интерфейс класса включить два метода: " СоздатьТаймер " и " УдалитьТаймер " и свойство " ИнтервалТаймера ", доступное для чтения и записи. Методы не имеют параметров, что облегчает работу с ними. Чтобы
Рассматриваемый нами случай упаковки функций Win32 API особый, поскольку одна из этих функций требует вызова функции. Заметьте, ВашТаймер. Чтобы не возникала соблазна поместить в класс ВашТаймер:
Option Explicit
'Класс ВашТаймер служит упаковкой функций WIN32 API работы с таймером
'Интерфейс класса будут составлять две функции:
'СоздатьТаймер, УдалитьТаймер и свойство ИнтервалТаймера
'При работе с классом необходимо описать Callback функцию по следующему образцу:
'Public Sub TimerProc(ByVal HandleW As Long, ByVal msg As Long, _
' ByVal idEvent As Long, ByVal TimeSys As Long)
' 'Функция обратного вызова. Вызывается при обработке сообщения WM_Timer,
' 'посылаемого таймером, созданным процедурой SetTimer
'
' 'Поместите здесь свой код!
'
'End Sub
'Функции Win32 API для работы с таймером
Private Declare Function SetTimer Lib "user32" (ByVal hwnd As Long, _
ByVal nIDEvent As Long, ByVal uElapse As Long, ByVal lpTimerFunc As Long) As Long
Private Declare Function KillTimer Lib "user32" (ByVal hwnd As Long, ByVal nIDEvent As Long) As Long
'Свойства: Интервал - хранит значение интервала посылки сообщений
Private Интервал As Long
'Идентификатор таймера
Private IdEv As Long
Public Sub СоздатьТаймер()
'Создает таймер, вызывая Win32 Api функцию SetTimer
IdEv = SetTimer(0, 0, Интервал, AddressOf TimerProc)
If IdEv = 0 Then
MsgBox ("Не удалось создать таймер!")
Else
Debug.Print "Создан Таймер: Идентификатор = ", IdEv
End If
End Sub
Public Sub УдалитьТаймер()
'Удаляет таймер
If IdEv > 0 Then
Call KillTimer(0, IdEv)
Debug.Print "Удален Таймер: Идентификатор = ", IdEv
IdEv = 0
End If
End Sub
Public Property Get ИнтервалТаймера() As Long
ИнтервалТаймера = Интервал
End Property
Public Property Let ИнтервалТаймера(ByVal NewValue As Long)
Интервал = NewValue
End Property
Private Sub Class_Initialize()
Интервал = 1000
End Sub
Private Sub Class_Terminate()
УдалитьТаймер
End Sub
Некоторые комментарии к этому тексту:
СоздатьТаймер и УдалитьТаймер, а также процедуры - свойства Property Get и Property Let ИнтервалТаймера, позволяющие взаимодействовать с закрытым свойством Интервал.Declare, описывающие функции Win32 API SetTimer и KillTimer, уже упомянутое свойство Интервал и свойство IdEv, хранящее Class_Initialize и Class_Terminate. В конструкторе инициализируется свойство Таймер, значение которого устанавливается по умолчанию, равным одной секунде. В TimerProc. Процедура с таким именем и уже упоминавшимися требованиями к ее заголовку должна быть описана в стандартном модуле класса, созданным конечным пользователем.Рассмотрим, как работать с классом ВашТаймер. Мы не стали изобретать ничего нового, - в тестовый документ добавили две кнопки Start1 и Finish1, которые работают также как и их тезки Start и Finish, но вызывают для этого методы и свойства объекта MyTimer класса ВашТаймер. В модуле, где объявлен соответствующий объект, находится и процедура обратного вызова TimerProc. Вот соответствующий текст этого модуля:
Option Explicit 'Модуль Таймер1 'Глобальная информация Public Counter As Long 'Счетчик числа вызовов Callback функции Public MyTimer As New ВашТаймер Public Sub Start1() MyTimer.ИнтервалТаймера = 5000 MyTimer.СоздатьТаймер End Sub Public Sub Finish1() MyTimer.УдалитьТаймер End Sub Public Sub TimerProc(ByVal HandleW As Long, ByVal msg As Long, _ ByVal idEvent As Long, ByVal TimeSys As Long) 'Функция обратного вызова. Вызывается при обработке сообщения WM_Timer, 'посылаемого таймером, созданным процедурой SetTimer Counter = Counter + 1 Debug.Print "Hi", Counter End Sub
Комментируя этот текст, следует заметить, что введение обертывающего класса облегчает работу с таймером. Единственной проблемой остается достаточно сложное и возможно непонятное конечному пользователю описание заголовка функции TimerProc. Чтобы облегчить ее решение, можно, как это сделано в нашем примере, заготовку этой функции включить в описание класса в качестве комментария.
В заключение приведем результаты эксперимента с нажатием кнопок Start1 и Finish1:
Создан Таймер: Идентификатор =31711 Hi 1 Hi 2 Hi 3 Удален Таймер: Идентификатор =31711 Создан Таймер: Идентификатор =31704 Hi 4 Hi 5 Hi 6 Hi 7 Удален Таймер: Идентификатор =31704
На этом мы закончим рассмотрение темы работы с функциями Win32 API.
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.