Основы офисного программирования и язык VBA

         

Вызов функций Win32 API, работающих в Unicode кодировке


Уже говорилось, что функции API, работающие со строками, вызываются в ANSI кодировке. Сейчас мы попытаемся объяснить причину этого факта, а, с другой стороны, покажем, как можно вызывать функции Win32 API, использующие Unicode кодировку. Заметим, что это может быть важным не столько для функций Win32 API, сколько для других внешних функций, которые могут существовать в кодировке Unicode и не иметь ANSI варианта. Начнем с объяснения ситуации, - почему в VBA внешние функции вызываются в кодировке ANSI. Следует понимать, что строки VBA хранятся в Unicode кодировке и передача строк при вызове внутренних функций внутри VBA происходит в кодировке Unicode. Однако VB и VBA предполагают, что внешний мир устроен по-другому и до сих пор использует кодировку ANSI. Поэтому всякий раз, когда вызываются внешние функции, при вызове происходит преобразование и строковая информация передается и возвращается в кодировке ANSI. По этой причине нельзя вызвать функцию в кодировке Unicode простым изменением псевдонима, задав у него окончание W. Покажем сейчас, как можно "обмануть" VBA, заставив его не выполнять указанных преобразований, что и позволит вызывать функции, корректно работающие в Unicode кодировке. Покажем также, что, как и всякий обман, не всегда все заканчивается благополучно. Тем не менее, с предлагаемым приемом полезно познакомиться. Решение задачи основывается на следующем:

  1. При вызове функции вместо строки используется массив байтов, хранящий копию строки. Напомним, что внутри VBA строка хранится в Unicode кодировке, поэтому и массив байтов будет хранить строку в этой кодировке.
  2. В операторе Declare необходимо тип String изменить на тип Any, что обеспечит отсутствие проверок и преобразований.
  3. Если в операторе 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:

Пример 6.5.

(html, txt)

Приведем результаты отладочной печати:

327894 Document1 - Microsoft Word

Document1 - Microsoft Word NewDoc

Дадим краткие комментарии к тексту процедуры:

  • Работа процедуры начинается с вызова функции API FindWindowA, работающей в кодировке ANSI. Она успешно находит окно, заголовок которого задан переменной Capt. Функция возвращает его описатель.
  • На следующем шаге мы пытались решить эту же задачу, используя функцию API FindWindowW, работающую в кодировке Unicode. В тексте нашел отражение один из вариантов решения.


    К сожалению, поиск во всех случаях заканчивался неуспехом, хотя, как показал побайтный анализ в окне Watch и как показывает отладочная печать, массив байтов ArCapt содержит Unicode копию строки заголовка
  • Полученный описатель окна использовался при вызове Unicode варианта функции GetWindowTextW. Функция корректно работала, используя ссылку на переданный ей массив байтов ArCapt.
  • Затем, используя эту же технику, заголовок окна был изменен при вызове Unicode варианта функции SetWindowTextW.
  • Для контроля повторно была вызвана функция GetWindowTextW. Отладочная печать подтвердила корректность работы.


Неудача в вызове функции FindWindowW может быть вызвана разными причинами. Вероятнее всего, при выполнении поиска и проведения операций сравнения строк, применяемый способ не корректен, или, по крайней мере, требует дополнительных уточнений, возможно связанных с длиной строки. С другой стороны, сам прием является некоторой уловкой. Существует более честный способ, хотя, возможно, и более трудоемкий. Для решения задачи можно создать библиотеку типов TypeLib, содержащую описание функций Win32 API в Unicode кодировке, включить ссылку на эту библиотеку и вызывать функции без всяких уловок.


Содержание раздела