Перейти на главную страничку сайта (список статей, файлы для скачивания)

ФОРУМ (здесь можно обсудить эту статью, а также любые проблемы программирования на различных макроязыках и в скриптовых средах)

Библиотека dynwrapx.dll и вызов функций Win 32 API

Автор библиотеки и данного описания: Юрий Попов.

Ник автора на форуме: YMP.

Условия распространения библиотеки: freeware.

Содержание

Введение

DynamicWrapperX - это ActiveX компонент (СОМ-сервер), написанный мной по мотивам DynamicWrapper как попытка более полной реализации идеи. Он предоставляет возможность из скриптов на JScript и VBScript вызывать функции, экспортируемые dll-библиотеками, в частности функции Windows API. Этот компонент не является модификацией оригинального кода DynamicWrapper, он написан с нуля на языке ассемблера GoAsm. Тестировался под Windows XP SP1 и Windows 98 SE.

Нововведения заключаются на данный момент в следующем:

---------------------------------------------------------------------------

P.S. Хотя я и постарался протестировать компонент в различных ситуациях, идеальную его работу гарантировать не могу. Буду благодарен за сообщения на форуме о глюках и багах, буде такие обнаружатся.

Наверх

Регистрация в системе

Зарегистрировать компонент можно двумя способами:
regsvr32.exe <путь-к-компоненту>\dynwrapx.dll - для всех пользователей.
regsvr32.exe /i <путь-к-компоненту>\dynwrapx.dll - для текущего пользователя.

Если dynwrapx.dll лежит в папке System32, System, Windows, в текущей, или в одной из тех, что перечислены в переменной окружения Path, путь можно не указывать. При регистрации для текущего пользователя объект будет доступен только этому пользователю.

Разрегистрировать компонент можно так:
regsvr32.exe /u <путь-к-компоненту>\dynwrapx.dll - для всех пользователей.
regsvr32.exe /u /i <путь-к-компоненту>\dynwrapx.dll - для текущего пользователя.

В Windows 98, возможно, придётся указать полный путь к regsvr32.exe (папка System). Кроме того, регистрация для текущего пользователя здесь лишена смысла, т.к. компонент не будет работать.

Наверх

Встроенные методы объекта

[1]  Register( DllName, FuncName [, i=ParamTypes] [, r=RetValType] ) - регистрация функции из DLL в качестве метода объекта.
[2]  RegisterCallback( FuncRef [, i=ParamTypes] [, r=RetValType] ) - регистрация скриптовой функции как callback-функции.
[3]  NumGet( Address [, Offset] [, Type] ) - чтение числа из памяти по заданному адресу.
[4]  NumPut( Var, Address [, Offset] [, Type] ) - запись числа в память по заданному адресу.
[5]  StrPtr( Var [, Type] ) - получение указателя на строку в виде числовой переменной.
[6]  StrGet( Address [, Type] ) - считывание строки по заданному адресу (указателю).
[7]  Space( Count [, Char] ) - создание строки заданной длины.

Наверх

Метод Register

Этот метод регистрирует функцию, находящуюся в указанной dll-библиотеке, в качестве метода объекта. После этого она может вызываться так же, как и встроенные методы, по своему имени через точку. Синтаксис метода Register в целом остался тот же, что и у оригинального DynamicWrapper. Флаговый параметр (f=...) упразднён, так что если он есть, то просто игнорируется. Префикс i= по-прежнему обозначает параметры функции, r= - возвращаемое значение.

JScript

DX = new ActiveXObject("DynamicWrapperX");                  // Создание объекта.
DX.Register("user32.dll", "MessageBoxW", "i=hwwu", "r=l");  // Регистрация функции из dll.
res = DX.MessageBoxW(0, "Hello, world!", "Test", 4);        // Вызов.

VBScript

Set DX = CreateObject("DynamicWrapperX")                    ' Создание объекта.
DX.Register "user32.dll", "MessageBoxW", "i=hwwu", "r=l"    ' Регистрация функции из dll.
res = DX.MessageBoxW(0, "Hello, world!", "Test", 4)         ' Вызов.

Имя библиотеки, указанное без пути к ней, означает поиск по имени - сначала в памяти процесса среди уже загруженных библиотек (в порядке их загрузки), затем на диске. В Windows XP порядок поиска на диске будет таким:

  1. Каталог приложения (в данном случае приложение - это либо wscript.exe, либо cscript.exe)
  2. Текущий каталог
  3. System32
  4. System
  5. Windows
  6. Каталоги, перечисленные в переменной окружения Path

Если библиотека находится в файле с расширением dll, расширение указывать не обязательно, т.е. выше можно было бы написать просто "user32". Если библиотека находится в файле без расширения, в конце имени нужно поставить точку, например: "mylib."

Имя функции может иметь варианты. Например, функция, описанная в документации как MessageBox, реально присутствует в user32.dll в двух воплощениях: MessageBoxW (для строк в Юникоде) и MessageBoxA (для строк в традиционной 8-битной кодировке ANSI). Следуя традиции, я сохранил логику поиска функций прежней: например, если указано имя MessageBox и такой функции в user32.dll не будет найдено, то поиск автоматически повторяется для варианта MessageBoxA. Имена юникодовских функций нужно указывать точно, с буквой W на конце.

Список параметров можно опускать, только если функция таковых не имеет, при этом не нужно оставлять на этом месте запятую.

Возвращаемое значение, если оно не требуется, можно опускать независимо от того, возвращает что-то функция или нет.

JScript

DX = new ActiveXObject("DynamicWrapperX");
DX.Register("kernel32", "GetCommandLine", "r=s");      // У этой функции нет параметров.
CmdLine = DX.GetCommandLine();                         // Команда, запустившая данный процесс.
WScript.Echo(CmdLine);

VBScript

Set DX = CreateObject("DynamicWrapperX")
DX.Register "kernel32", "Beep", "i=uu"      ' Функция Beep возвращает значение, но оно не нужно.
DX.Beep 800, 1000                           ' Звук через динамик системного блока.

Типы входных параметров и возвращаемых значений

l - знаковое целое 32 бита - LONG, INT, LPARAM, LRESULT и т.п., диапазон значений: -2147483648 ... 2147483647;
u - беззнаковое целое 32 бита - ULONG, UINT, DWORD, WPARAM, ... , диапазон значений: 0 ... 4294967295;
h - хэндл, дескриптор - HANDLE, HWND, HMODULE, HINSTANCE, HICON, ... , диапазон значений: -2147483648 ... 4294967295;
p - указатель, для чисел то же, что 'u', но также можно использовать для передачи объекта (IDispatch *) и строки;
n - знаковое целое 16 бит - SHORT, диапазон значений: -32768 ... 32767;
t - беззнаковое целое 16 бит - USHORT, WORD, WCHAR, OLECHAR, ... , диапазон значений: 0 ... 65535;
c - знаковое целое 8 бит - CHAR, диапазон значений: -128 ... 127;
b - беззнаковое целое 8 бит - UCHAR, BYTE, ... , диапазон значений: 0 ... 255;
f - дробное число одинарной точности (32 бита) - FLOAT;
d - дробное число двойной точности (64 бита) - DOUBLE;
w - строка в Юникоде - BSTR, LPWSTR, LPOLESTR, OLECHAR *, WCHAR *, ...;
s - строка в кодировке ANSI/Windows по умолчанию - LPSTR, LPCSTR, CHAR *, ...;
z - строка в кодировке OEM/DOS по умолчанию - LPSTR, LPCSTR, CHAR *, ...

Наверх

Выходные параметры

L - указатель на число (его адрес в памяти) - LONG *, LPLONG и т.п.;
H - то же - HANDLE *, PHANDLE, LPHANDLE, ...;
U - то же - ULONG *, LPDWORD, ...;
P - то же;
N - то же - SHORT *;
T - то же - USHORT *, LPWORD, WCHAR *, OLECHAR *, ...;
C - то же - CHAR *, ...;
B - то же - UCHAR *, LPBYTE, ...;
F - то же - FLOAT *, PFLOAT;
D - то же - DOUBLE *, PDOUBLE;
W - выходная строка в Юникоде;
S - выходная строка в ANSI;
Z - выходная строка в OEM.

Особенность JScript: чтобы использовать число в качестве выходного, его нужно объявить через оператор new. Например, так:
a = new Number(0);

Со строками (как входными, так и выходными) этого делать не нужно, и даже противопоказано, т.к. объект String будет содержать не обычную строку, а "versioned stream". Что это такое, я пока не разбирался.

Наверх

О строках

В JScript и VBScript используются строки типа BSTR. Это строка в Юникоде, т.е. код каждого символа занимает 2 байта. За последним символом расположен ограничитель - 2 нулевых байта. Кроме того, перед первым символом находится 4-байтное число, содержащее длину строки в байтах (без учёта нулевых на конце). Указатель, содержащийся в скриптовых строковых переменных, является адресом первого символа такой строки (т.е. байты, содержащие длину строки, как бы остаются за кадром).

Передать строку можно тремя различными способами:

1) Входная строка: w, s, z. Для типов s и z строка копируется с конвертацией в соответствующую кодировку, и API-функция получает указатель на эту копию. После завершения работы функции память, запрошенная под копию строки, освобождается, т.е. эта копия перестаёт существовать. В случае w функции передаётся указатель на оригинал строки.

2) Выходная строка: W, S, Z. Передаётся указатель на оригинал строки, но S и Z предварительно конвертируются (на месте). После завершения функции строки S и Z конвертируются обратно в Юникод, у них измеряется длина, а у строки W только измеряется длина. Длина (в байтах) записывается перед строкой. Без этой последней операции потом в скрипте будут глюки, например, при конкатенации этой строки с другими.

3) Указатель: p. Это самый простой способ. Передаётся указатель на оригинал строки, без конвертации. После завершения функции нет ни конвертации, ни корректировки длины. Так что если функция туда что-то запишет, оно там будет в нетронутом виде. Здесь строка фактически является просто буфером в памяти для хранения любых данных.

Может показаться, что это то же самое, что w, но это не так. Разница в том, что параметр, объявленный как p, принимает не только строковые переменные, но и числовые.

Возвращая строку как p, получаем числовую переменную, содержащую указатель на строку, которую вернула функция. Возвращая строку как w, s или z, получаем строковую переменную, содержащую указатель на копию этой строки. Строки s и z копируются с конвертацией в Юникод.

Вызовы API, принимающие строковые аргументы, как правило, представлены в двух вариантах - например, MessageBoxA и MessageBoxW. Использование в скриптах юникодовских вариантов, с окончанием W, представляется более логичным, т.к. при этом нет дополнительных перекодировок строк туда и обратно.

Что касается Windows 98, то там оба движка также используют строки в Юникоде, но в библиотеках API не все функции, экспортируемые с окончанием W, реально работают. Многие из них являются просто болванками, которые ничего не делают, а только возвращают 0. Например, MessageBoxW работает, а lstrcmpiW - нет, хотя и присутствует в библиотеке kernel32.

Выходная строка, как и прочие выходные параметры, предназначена для того, чтобы функция API в неё что-нибудь записала, так что её длина должна быть соответствующей.

Наверх

Метод RegisterCallback

Этот метод нужен для получения указателя на скриптовую функцию, пригодного для передачи какой-либо функции API. Которая потом, используя этот указатель, могла бы данную скриптовую функцию вызывать. Примером API-функции, которая этого требует, может служить EnumWindows. Она перебирает существующие окна и хэндл каждого окна передаёт callback-функции в качестве параметра. После этого она ждёт, что вернёт callback-функция. Если 1, то перебор идёт дальше, если 0 - прекращается.

В JScript и VBScript функция является объектом и ссылка на неё для таких целей не годится. Поэтому ссылка на скриптовую функцию сначала передаётся методу RegisterCallback, а API-функция получает возвращённый им указатель на одну из вспомогательных процедур внутри dynwrapx.dll, вызовы которой будут транслироваться в вызовы скриптовой функции, а возвращаемые значения - в обратном направлении. Всего таких процедур 16, так что максимальное число callback-функций в скрипте также 16.

В JScript ссылка на функцию - это она сама, только без скобок, а в VBScript сначала нужно использовать GetRef. Кроме ссылки на функцию, задаются также типы её параметров и возвращаемого значения - аналогично методу Register (но используются только маленькие буквы).

JScript

DX = new ActiveXObject("DynamicWrapperX");

DX.Register("user32", "EnumWindows",    "i=pl");
DX.Register("user32", "GetWindowTextW", "i=hWl");          // Вариант Unicode.
// DX.Register("user32", "GetWindowText", "i=hSl");        // Вариант ANSI.

pCbkFunc = DX.RegisterCallback(CbkEnumWin, "i=hl", "r=l"); // Регистрация CbkEnumWin
                                                           // как функции обратного
                                                           // вызова и получение
                                                           // указателя.
n=0, m=0, WinList="";

Title = DX.Space(256);              // Буфер под заголовок окна (выходная строка).

DX.EnumWindows(pCbkFunc, 0);        // Вызов EnumWindows с передачей ей указателя на
                                    // callback-функцию.

WScript.Echo("Всего окон: " + m + "\nС заголовками: " + n + "\n\n" + WinList);


// ............... Собственно callback-функция ....................

function CbkEnumWin(hwnd, lparam)
{
  DX.GetWindowTextW(hwnd, Title, 256);
  // DX.GetWindowText(hwnd, Title, 256);  // Вариант ANSI.
  if(Title.length > 0) {  // Если длина заголовка больше 0, заносим в список.
    WinList += hwnd + "\t" + Title + "\n";
    ++n;
  }
  ++m;
  return 1;              // Если вернуть 0, вызовы прекратятся.
}

VBScript

Set DX = CreateObject("DynamicWrapperX")

DX.Register "user32", "EnumWindows",    "i=pl"
DX.Register "user32", "GetWindowTextW", "i=hWl"     ' Вариант Unicode.
' DX.Register "user32", "GetWindowText", "i=hSl"    ' Вариант ANSI.

Set Ref = GetRef("CbkEnumWin")  ' Получение ссылки на функцию.

pCbkFunc = DX.RegisterCallback(Ref, "i=hl", "r=l")  ' Регистрация CbkEnumWin
                                                    ' как функции обратного вызова
                                                    ' и получение указателя.
n = 0 : m = 0 : WinList = ""
Title = Space(256)              ' Буфер под заголовок окна (выходная строка).

DX.EnumWindows pCbkFunc, 0      ' Вызов EnumWindows с передачей ей указателя на
                                ' callback-функцию.

WScript.Echo "Всего окон: " & m & vbCrLf & "С заголовками: " & n & _
              vbCrLf & vbCrLf & WinList


' ................ Собственно callback-функция .......................

Function CbkEnumWin(hwnd, lparam)
  DX.GetWindowTextW hwnd, Title, 256
  ' DX.GetWindowText hwnd, Title, 256   ' Вариант ANSI.
  If Len(Title) > 0 Then  ' Если длина заголовка больше 0, заносим в список.
    WinList = WinList & hwnd & vbTab & Title & vbCrLf
    n = n+1
  End If
  m = m+1
  CbkEnumWin = 1          ' Если вернуть 0, вызовы прекратятся.
End Function

Наверх

Дополнительные методы

NumGet( Address [, Offset] [, Type] ) - чтение числа из памяти. Address - базовый адрес. Offset - смещение от него (в байтах), положительное или отрицательное: его можно применять в циклах для считывания/записи последовательно расположенных чисел; по умолчанию 0. Type - тип считываемых данных: те же буквенные обозначения, что и для метода Register, но используются только маленькие буквы; по умолчанию - l. Считанные данные помещаются в возвращаемое методом значение.

NumPut( Var, Address [, Offset] [, Type] ) - запись числа в память. Var - либо литеральное числовое значение, либо переменная, содержащая значение, которое нужно записать. Остальное - как в NumGet. Возвращаемое методом значение содержит адрес сразу за последним записанным байтом.

В обоих методах Address может быть как числом, так и строкой, в последнем случае в качестве базового адреса используется указатель на эту строку. Это позволяет использовать строки в качестве буфера для данных, размещать там массивы, структуры и т.п.

StrPtr( Var [, Type] ) - возвращает указатель на строку (на оригинал). Var - строковая переменная или константа. Type - необязательный параметр. Возможные значения: w (по умолчанию), s, z. При значениях s и z строка предварительно конвертируется (на месте).

StrGet( Address [, Type] ) - чтение строки с указанного адреса. Возвращает копию строки. Address может быть как указателем в виде числа, так и строковой переменной. Type - необязательный параметр. Возможные значения: w (по умолчанию), s, z. Значения s и z нужны для чтения строки в ANSI- или OEM-кодировке, при этом она конвертируется в Юникод.

Space( Count [, Char] ) - создание строки (BSTR) заданной длины. Возвращает строковую переменную. Count - число символов (двухбайтных). Char - необязательный параметр: символ, которым будет заполнена строка. По умолчанию строка заполняется пробелами - так же, как это делает функция Space в VBScript. Чтобы заполнить строку двоичными нулями, нужно задать Char как пустую строку ("").

JScript

DX = new ActiveXObject("DynamicWrapperX");
str = "Hello, world! Это я.";

// Чтение из памяти. Читаются коды символов строки.

codes = "";

for(i=0; i < str.length; ++i)
  codes += DX.NumGet(str, i*2, "t") + " "; // i умножается на 2, т.к. смещение должно
                                           // быть в байтах, а тип "t" - двухбайтный.
WScript.Echo("Коды символов:\n" + codes);

// Чтение и запись. Строка записывается в обратном порядке символов.

len = str.length;
buf = DX.Space(len);                   // Буфер для записи.

for(i=0, j=len-1; i < len; ++i, --j) { // По смещению len-1 находится последний символ.
  code = DX.NumGet(str, i*2, "t");     // Читаем слева направо (смещение растёт).
  DX.NumPut(code, buf, j*2, "t");      // Пишем справа налево (смещение уменьшается).
}
WScript.Echo("Строка наоборот:\n" + buf);

// Прочее.

ptr = DX.StrPtr(str);                  // Получение указателя на строку в виде числа,
                                       // строка остаётся в Юникоде.
WScript.Echo("Адрес строки: " + ptr);

ptr = DX.StrPtr(str, "z");             // Получение указателя на ту же строку,
                                       // предварительно конвертированную в OEM/DOS.
str1 = DX.StrGet(ptr, "z");            // Чтение этой строки по указателю,
                                       // с обратной конвертацией.
WScript.Echo("Восстановленная строка:\n" + str1);

VBScript

Set DX = CreateObject("DynamicWrapperX")
str = "Hello, world! Это я."

' Чтение из памяти. Читаются коды символов строки.

strlen = Len(str)
codes = ""

For i=0 To strlen-1
  codes = codes & DX.NumGet(str, i*2, "t") & " "  ' i умножается на 2, т.к. смещение должно
Next                                              ' быть в байтах, а тип "t" - двухбайтный.

WScript.Echo "Коды символов:" & vbCrLf & codes

' Чтение и запись. Строка записывается в обратном порядке символов.

buf = Space(strlen)                 ' Буфер для записи.
j = strlen-1                        ' По смещению strlen-1 находится последний символ.

For i=0 To strlen-1
  code = DX.NumGet(str, i*2, "t")   ' Читаем слева направо (смещение растёт).
  DX.NumPut code, buf, j*2, "t"     ' Пишем справа налево (смещение уменьшается).
  j = j-1
Next

WScript.Echo "Строка наоборот:" & vbCrLf & buf

' Прочее.

ptr = DX.StrPtr(str)                 ' Получение указателя на строку в виде числа,
                                     ' строка остаётся в Юникоде.
WScript.Echo "Адрес строки: " & ptr

ptr = DX.StrPtr(str, "z")            ' Получение указателя на ту же строку,
                                     ' предварительно конвертированную в OEM/DOS.
str1 = DX.StrGet(ptr, "z")           ' Чтение этой строки по указателю, с обратной конвертацией.

WScript.Echo "Восстановленная строка:" & vbCrLf & str1

Наверх

Скачать

Вы можете скачать библиотеку здесь (версия 1.0.0.0 от 05.09.2008 г., архив 14 191 байт). В архиве находится сама библиотека и html-файл справки (статья, которую вы сейчас читаете).

Наверх

Перейти на главную страничку сайта (список статей, файлы для скачивания)

© 2007 http://www.script-coding.com При любом использовании материалов сайта обязательна ссылка на него как на источник информации, а также сохранение целостности и авторства материалов.