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

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

Проект перевода документации AutoHotkey: перечень переведённых статей и статей в работе.

Оригинал статьи в AutoHotkey.chm: "DllCall()", в содержании - "Misc. Commands" - "DllCall".

Ник переводчика: Gourmet.

Настоящее имя переводчика: Валентина Гаврикова, г.Москва.

DllCall() [v1.0.33+]

Вызывает функцию в DLL, например, стандартную функцию API Windows.

Result := DllCall("[DllFile\]Function" [, Type1, Arg1, Type2, Arg2, "Cdecl ReturnType"])

Параметры

Result

Фактическое значение, возвращенное функцией. Если функция не возвращает значения, результат - неопределенное целое число. Если функция вызвала ошибку, возвращаемое значение - пустая строка.

[DllFile\]Function

Имя файла DLL, после которого идет обратный слеш и название (имя) функции. Например: "MyDLL\MyFunction" (расширение файла ".dll" является необязательным). Если абсолютный путь не определен, DllFile разыскивается в PATH системы или в текущем рабочем каталоге сценария (см. описание встроенной переменной A_WorkingDir).

DllFile может быть опущен при вызове функции, которая находится в User32.dll, Kernel32.dll или ComCtl32.dll (или Gdi32.dll для версий, начиная с 1.0.36.07). Например, "User32\IsWindowVisible" приведет к тому же самому результату, что и "IsWindowVisible". Для таких стандартных DLL символ "A" (суффикс, который появляется у некоторых функций API), также может быть опущен. Например, "MessageBox" - то же самое, что и "MessageBoxA".

Производительность может быть значительно улучшена при использовании повторных запросов к заранее загруженным DLL (с помощью "Kernel32.dll\LoadLibrary", подробнее об этом см. ниже).

Type1, Arg1

Каждая из этих пар представляет отдельный параметр, который будет передан функции. Количество пар не ограничено. Type - см. таблицу типов ниже. Arg - значение, которое будет передано функции.

Cdecl ReturnType

Слово "Cdecl" обычно опускается, потому что большинство функций использует стандартное соглашение о вызовах, а не "C-соглашение" о вызовах (функции вроде wsprintf, которые принимают изменяющееся количество параметров - одно из исключений). Если вы опускаете "Cdecl", но вызов приводит к тому, что встроенная переменная ErrorLevel принимает значение "An", где n - полный размер параметров, которые вы передали, это может означать, что слово "Cdecl" все же требуется (подробнее о значениях ErrorLevel см. ниже).

Если слово "Cdecl" используется, то оно должно находиться перед указанием типа возвращаемого значения (ReturnType). Отделите слова друг от друга символом пробела или табуляции. Например: "Cdecl Str".

ReturnType: если функция возвращает 32-разрядное целое число (Int), BOOL, или вообще ничего, ReturnType может быть опущен. Иначе, определите тип, пользуясь таблицей типов ниже. Суффикс "звездочка" (*) также поддерживается.

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

ТипОписание
Str

Строка типа "Blue" или MyVar. Если вызванная функция изменяет строку, и параметр - явная переменная, ее содержание будет модифицировано. Например, следующий вызов преобразовал бы содержание MyVar к верхнему регистру: DllCall ("CharUpper", "str", MyVar).

Однако, если функция разработана так, что работает со строкой большей, чем текущая "вместимость" переменной, перед вызовом функции необходимо убедиться, что переменная является достаточно "большой". Это может быть достигнуто с помощью вызова VarSetCapacity (MyVar, 123), где 123 - количество байт, которое должна быть способна вместить переменная MyVar.

Параметр типа str не должен быть выражением, которое возвращает число. Если это так, функция не будет вызвана, а встроенная переменная ErrorLevel будет установлена на -2.

Вообще, тип str должен использоваться с функциями, которые ожидают LPSTR, LPCSTR, LPTSTR и подобные типы.

Звездочка "str * " поддерживается, но очень редко используется. Это может использоваться с функциями, которые ожидают что-то подобное "char ** " или "LPSTR * ".

Int64

64-разрядное целое число.

Int

32-разрядное целое число, которое является самым обычным целочисленным типом (иногда называемым "LONG"). Этот тип должен также использоваться для любого параметра типа BOOL, ожидаемого функцией (значение типа BOOL должно быть 1 или 0).

Также весьма часто используется беззнаковый Int (UInt), например, для DWORD, COLORREF и различных дескрипторов типа HWND, HBRUSH и HBITMAP.

Short

16-разрядное целое число. Беззнаковый Short (UShort) может использоваться с функциями, которые ожидают WORD.

Char

8-разрядное целое число. Беззнаковый Char (UChar) может использоваться с функциями, которые ожидают BYTE.

Float

32-разрядное число с плавающей запятой, которое обеспечивает 6 цифр точности.

Double

64-разрядное число с плавающей запятой, которое обеспечивает 15 цифр точности.

* или P (суффикс)

Добавьте в конец звездочку (с дополнительным предыдущим пробелом) к любому из вышеупомянутых типов, чтобы передать адрес параметра, а не само значение (вызываемая функция должна специально требовать этого). Так как значение такого параметра может быть изменено функцией, всякий раз, когда явную переменную передают как параметр, содержание такой переменной будет обновлено. Например, следующий вызов передает текущее содержание переменной MyVar функции MyFunc и также обновляет переменную MyVar, чтобы отразить любые изменения, сделанные функцией MyFunc: DllCall ("MyDll\MyFunc", " int * ", MyVar).

Вообще, звездочка используется всегда, когда функция имеет тип параметра или тип возвращаемого значения, который начинается с "LP" (кроме строки типа LPSTR, для которой нужно использовать "str"). Самый обычный пример - LPDWORD, который является указателем на DWORD. Так как DWORD - 32-разрядное целое число без знака, используйте "UInt * ", чтобы представить LPDWORD.

Обратите внимание, что "char * " - не то же самое, что "str", потому что "char * " передает адрес 8-битного числа, а "str" передает адрес последовательности символов.

U (префикс)

Прибавьте символ U к любому из целочисленных типов выше, чтобы интерпретировать его как целое число без знака (UInt64, UInt, UShort и UChar). Строго говоря, это необходимо только для возвращаемых значений и типов со звездочкой, потому что не имеет значения, содержит ли параметр знак, если он передан по значению (исключая Int64).

32-разрядное целое число без знака (UInt) соответствует любому DWORD, HWND или подобному параметру, ожидаемому функцией. Кроме того, значение HWND (дескриптор окна) - то же самое, что и уникальный идентификатор окна (см. описание функции WinGet).

Если в качестве беззнакового параметра определено отрицательное целое число, целое число будет преобразовано в беззнаковое. Например, если -1 посылается как UInt, она превратится в 0xFFFFFFFF. Однако, эта методика не поддерживается для 64-разрядного целого (UInt64).

64-разрядные целые числа без знака в полной мере не поддерживаются. Поэтому, чтобы работать с числами, большими или равными 0x8000000000000000, опустите префикс U и интерпретируйте любые отрицательные значения, полученные от функции, как большие целые числа. Например, функция, которая выдает -1 как Int64, в действительности возвращает 0xFFFFFFFFFFFFFFFF, если она должна выдавать UInt64.

ErrorLevel

Встроенная переменная ErrorLevel устанавливается на одно из следующих значений, чтобы указать, потерпел ли вызов неудачу или выполнен успешно.

0: успешно.

-1 (минус единица): параметр [DllFile\]Function - выражение, которое возвращает число. Требуется строка.

-2: тип возвращаемого значения или один из указанных типов параметров недопустимы. Такая ошибка может также возникнуть, если в качестве параметра типа str передано выражение, возвращающее число.

-3: невозможно обратиться к указанному DllFile. Если для DllFile не был определен абсолютный путь, файл должен существовать в PATH системы или в текущем рабочем каталоге сценария (переменная A_WorkingDir). Эта ошибка также может произойти, если у пользователя недостаточно прав для получения доступа к файлу.

-4: указанная функция не найдена в DLL.

N (любое положительное число): функция вызвана, но произошла фатальная ошибка (исключение) N (например, 0xC0000005 означает "нарушение прав доступа"). В таких случаях функция возвращает пустое значение (пустая строка), но любые переменные со звездочками будут обновлены. Пример фатального исключения: разыменование недопустимого указателя, такого, как NULL. Так как функция Cdecl никогда не вызывает ошибку, которая описана абзацем ниже, возможно возникновение исключения, когда передано слишком мало параметров.

An (символ А, сопровождаемый целым числом n): функция вызвана, но передано слишком много или слишком мало параметров. "n" - количество байтов, на которое список параметров был превышен или занижен. Если n положительное, передано слишком много параметров (или параметры слишком большие) или запрос требует CDecl. Если n отрицательное, передано слишком мало параметров. Эта ситуация должна быть исправлена, чтобы гарантировать надежную работу функции. Присутствие этой ошибки также может означать, что произошло исключение, когда функция вернула пустое значение.

Производительность

При многократных вызовах DLL производительность может быть значительно улучшена с помощью ее явной загрузки (это не является необходимостью для стандартных DLL типа User32, т.к. они загружены всегда). Этот прием позволяет избежать внутреннего вызова LoadLibrary и FreeLibrary при каждом обращении к DllCall. Например:

;избегает загрузки библиотеки в цикле
hModule := DllCall("LoadLibrary", "str", "MyFunctions.dll")
Loop, C:\My Documents\*.*, , 1
	result := DllCall("MyFunctions\BackupFile", "str", A_LoopFileFullPath)
DllCall("FreeLibrary", "UInt", hModule)
;лучше всего выгружать DLL после ее использования (или перед завершением сценария)

Структуры и Массивы [v1.0.36.07 +]

Структура - совокупность членов (полей), совместно хранимых в памяти. Большинство членов, как правило, является целыми числами.

Функции, которые принимают указатель на структуру (или блок данных в памяти), можно вызвать, сохранив необработанные двоичные данные структуры в обычной переменной. Обычно предпринимаются следующие шаги:

  1. Вызовите VarSetCapacity (MyStruct, 123, 0), чтобы гарантировать, что целевая переменная является достаточно "большой", чтобы вместить данные структуры. Замените 123 числом, не меньшим, чем размер структуры. Если вы укажете нуль в последнем необязательном параметре, это инициализирует все члены двоичным нулем, что обычно используется для избегания вызова InsertInteger() на следующем шаге.
  2. Если целевая функция использует первоначальные значения в структуре, вызовите InsertInteger (123, MyStruct, 4), чтобы инициализировать любые члены, которые должны быть ненулевыми. Замените 123 целым числом, которое будет присвоено нужному члену (или напишите &Var, чтобы указать адрес переменной). Замените 4 смещением нужного члена (см. шаг 4 для описания "смещения").
  3. Вызовите целевую функцию, передавая MyStruct как параметр типа "str". Функция прочитает и/или изменит некоторые члены структуры.
  4. Используйте MyInteger: = ExtractInteger (MyStruct, 4), чтобы отыскать любые желаемые целые числа в структуре. Замените 4 смещением нужного члена в структуре. Первый член имеет всегда смещение 0. Второй член - смещение 0 плюс размер первого члена (обычно 4). Члены дальше второго - смещение предыдущего члена плюс размер предыдущего члена. Большинство членов - типа DWORD, Int и других 32-разрядных целых чисел - имеет размер 4 байта.

Далее приведены функции, о которых упомянуто выше. Они могут быть вставлены в любой сценарий или включены через #Include. См. также примеры практического использования структур ниже.

ExtractInteger(ByRef pSource, pOffset = 0, pIsSigned = false, pSize = 4)
;pSource - строка (буфер), область памяти которого содержит
;необработанное/двоичное целое число по смещению pOffset.
;Вызывающая программа должна передать значение "истина" в pIsSigned,
;чтобы результат интерпретировался как число со знаком, а не беззнаковое.
;pSize - размер целого числа, содержащегося в pSource,
;в байтах (например, 4 байта для DWORD или Int).
;pSource должен передаваться по ссылке (ByRef), чтобы избежать искажения
;в течение процесса копирования от формального к фактическому
;(так как pSource может содержать достоверные данные
;после его первого двоичного нуля).
{
	Loop %pSize% ; Формирует целое число, складывая его байты.
		result += *(&pSource + pOffset + A_Index-1) << 8*(A_Index-1)
	if (!pIsSigned OR pSize > 4 OR result < 0x80000000)
		;Знаковый или беззнаковый - не имеет значения в этих случаях.
		return result
	;Иначе, конвертируем значение (теперь известно, что 32-разрядное)
	;к его знаковой копии:
	return -(0xFFFFFFFF - result + 1)
}

InsertInteger(pInteger, ByRef pDest, pOffset = 0, pSize = 4)
;Вызывающая программа должна гарантировать, что pDest имеет достаточную вместимость.
;Чтобы сохранить любое существующее содержимое в pDest, изменяем только
;число байтов pSize, начинающихся со смещения pOffset.
{
;копируем каждый байт целого числа в структуру как необработанные двоичные данные.
Loop %pSize%
DllCall("RtlFillMemory", UInt, &pDest + pOffset + A_Index-1, UInt, 1, UChar
	, pInteger >> 8*(A_Index-1) & 0xFF)
}

Замечания

Несмотря на встроенную обработку исключительных ситуаций, все же возможно аварийное завершение сценария из-за вызова DllCall. Это может случиться, если функция непосредственно не генерирует исключение, но приводит к какому-то несоответствию, типа плохого указателя или незавершенной строки. Это может и не являться ошибкой функции, если сценарий передал неподходящее значение типа плохого указателя или "str" с недостаточной вместимостью. Сценарий может также аварийно завершиться, если определен несоответствующий тип параметра или тип возвращаемого значения, например, если не выполнено требование, что обычное целое число, которое генерирует функция, должно являться типом со звездочкой или "str".

В версиях, начиная с 1.0.33.01, при определении типа параметра или типа возвращаемого значения, который не содержит пробела или звездочки, кавычки вокруг этого типа могут быть опущены. Например, str может использоваться вместо "str" и CDecl вместо "CDecl". В версиях, начиная с 1.0.34, символ P может использоваться вместо звездочки, чтобы позволить опустить кавычки. Например: UIntP.

Функция, которая возвращает адрес одной из строк, которую ей передавали, может возвратить идентичную строку в адресе памяти, отличном от ожидаемого. Например, вызов CharLower(CharUpper(MyVar)) на языке программирования конвертировал бы содержимое MyVar в нижний регистр. Но когда то же самое делается с помощью DllCall(), MyVar может оказаться в верхнем регистре после следующего вызова, потому что CharLower будет оперировать с временной строкой, содержание которой идентично MyVar:

MyVar = ABC
result := DllCall("CharLower", str, DllCall("CharUpper", str, MyVar, str), str)

Чтобы обойти это, измените два подчеркнутых выше "str" на UInt. Это приведет к тому, что значение, возвращаемое CharUpper, будет интерпретировано как чистый адрес, который будет передан CharLower как целое число.

Примеры

Вызов функции API Windows "MessageBox" с сообщением, какую кнопку нажал пользователь.

WhichButton := DllCall("MessageBox", "int", "0", "str", "Нажмите Да или Нет", "str"
	, "Заголовок окна", "int", 4)
MsgBox Вы нажали кнопку #%WhichButton%.

Вызов функции API "IsWindowVisible", чтобы узнать, видимо ли окно Notepad.

DetectHiddenWindows On
;WinExist() возвращает HWND.
if (not DllCall("IsWindowVisible", "UInt", WinExist("Безымянный - Блокнот")))
	MsgBox Окно невидимо.

Вызов API wsprintf(), чтобы дополнить число 432 лидирующими нулями до 10 символов.

;гарантируем, что переменная является достаточно вместительной, чтобы принять новую строку:
VarSetCapacity(ZeroPaddedNumber, 20)
;требует соглашения о вызовах Cdecl:
DllCall("wsprintf", "str", ZeroPaddedNumber, "str", "%010d", "int", 432, "Cdecl")
MsgBox %ZeroPaddedNumber%

QueryPerformanceCounter() может использоваться, если вы нуждаетесь в большей точности, чем может дать встроенная переменная A_TickCount.

if DllCall("QueryPerformanceCounter", "Int64 *", Counter)
	MsgBox Текущее значение счетчика: %Counter%
else
	MsgBox Система не поддерживает счетчик.

Если передан HWND окна, а также текст или ClassNN одного из его элементов управления, следующая функция возвращает HWND этого элемента управления.

GetChildHWND(ParentHWND, ChildClassNN)
{
WinGetPos, ParentX, ParentY,,, ahk_id %ParentHWND%
if ParentX =
	return ; родительское окно не найдено (возможно, требуется DetectHiddenWindows)
ControlGetPos, ChildX, ChildY,,, %ChildClassNN%, ahk_id %ParentHWND%
if ChildX =
	return ; дочернее окно не найдено, возвращаем пустое значение
;Конвертируем дочерние координаты, которые вычисляются относительно
;верхнего левого угла родителя, в абсолютные/экранные координаты
;для использования с WindowFromPoint().
;Далее ПРЕДНАМЕРЕННО передаем функции слишком много параметров,
;потому что каждый параметр является 32-разрядным, что позволяет функции
;автоматически комбинировать их в один параметр в 64 бита (а именно в структуру POINT):
return DllCall("WindowFromPoint", "int", ChildX + ParentX, "int", ChildY + ParentY)
}

Следующий пример требует описанную выше функцию GetChildHWND(). Этот пример наблюдает за активным окном и отображает вертикальную позицию полосы прокрутки его активного в настоящий момент элемента управления.

#Persistent
SetTimer, WatchScrollBar, 100
return

WatchScrollBar:
ActiveWindow := WinExist("A")
if not ActiveWindow ; нет активного окна
	return
ControlGetFocus, FocusedControl, ahk_id %ActiveWindow%
if not FocusedControl ; нет активного элемента управления
	return
;Отобразим позицию вертикальной или горизонтальной полосы прокрутки в подсказке:
ChildHWND := GetChildHWND(ActiveWindow, FocusedControl)
;последний параметр: 1 - для SB_VERT, 0 - для SB_HORZ
ToolTip % DllCall("GetScrollPos", "UInt", ChildHWND, "Int", 1)
return

Запись некоторого текста в файл и чтение его назад в память (требует v1.0.34 +). Этот метод может использоваться для увеличения производительности в тех случаях, когда много файлов читается или записывается одновременно.

FileSelectFile, FileName, S16,, Создание нового файла:
if FileName =
	return
GENERIC_WRITE = 0x40000000 ;открытие файла на запись, а не на чтение
CREATE_ALWAYS = 2 ;создавать новый файл (перезапись любого существующего файла)
hFile := DllCall("CreateFile", str, FileName, Uint, GENERIC_WRITE, Uint, 0
	, UInt, 0, UInt, CREATE_ALWAYS, Uint, 0, UInt, 0)
if not hFile
{
	MsgBox Невозможно открыть "%FileName%" на запись.
	return
}
TestString = Это пробная строка.
DllCall("WriteFile", UInt, hFile, str, TestString, UInt, StrLen(TestString)
	, UIntP, BytesActuallyWritten, UInt, 0)
DllCall("CloseHandle", UInt, hFile) ;закрываем файл

;Теперь, когда файл был записан, читаем его содержимое обратно в память.
GENERIC_READ = 0x80000000 ;открытие файла на чтение, а не на запись
;этот режим подразумевает, что файл, который будет открыт, должен уже существовать
OPEN_EXISTING = 3
FILE_SHARE_READ = 0x1 ;могут ли другие процессы открыть файл, в то время как мы его открыли
FILE_SHARE_WRITE = 0x2
hFile := DllCall("CreateFile", str, FileName, UInt, GENERIC_READ, UInt
	, FILE_SHARE_READ|FILE_SHARE_WRITE, UInt, 0, UInt, OPEN_EXISTING, Uint, 0, UInt, 0)
if not hFile
{
	MsgBox Невозможно открыть "%FileName%" на чтение.
	return
}
;Очищаем переменную, чтобы проверить результат, но гарантируем,
;что она сохраняет достаточную вместимость:
BytesToRead := VarSetCapacity(TestString, StrLen(TestString))
DllCall("ReadFile", UInt, hFile, str, TestString, UInt, BytesToRead
	, UIntP, BytesActuallyRead, UInt, 0)
DllCall("CloseHandle", UInt, hFile) ;закрываем файл
MsgBox Следующая строка была прочитана из файла: "%TestString%"

Пример использования структуры: передаем адрес структуры RECT функции GetWindowRect(), которая присваивает членам структуры позиции left, top, right и bottom границ окна (относительно экрана).

Run Notepad
;это также устанавливает "последнее найденное окно" для использования WinExist() ниже
WinWait Безымянный - Блокнот
;RECT - структура, состоящая из четырех 32-разрядных целых чисел (то есть 4*4=16)
VarSetCapacity(Rect, 16)
DllCall("GetWindowRect", UInt, WinExist(), Str, Rect) ;WinExist() возвращает HWND
MsgBox % "Left " . ExtractInteger(Rect, 0, true) . " Top " . ExtractInteger(Rect, 4, true)
	  . " Right " . ExtractInteger(Rect, 8, true) . " Bottom " 
	  . ExtractInteger(Rect, 12, true)

Пример использования структуры: передаем функции FillRect() адрес структуры RECT, которая указывает область экрана, которая временно закрашивается красным.

;Устанавливаем вместимость на четыре 4-байтовых целых числа и инициализируем их нулями:
VarSetCapacity(Rect, 16, 0)
InsertInteger(A_ScreenWidth//2, Rect, 8) ;третье целое число в структуре - "rect.right"
InsertInteger(A_ScreenHeight//2, Rect, 12) ;четвертое целое число в структуре - "rect.bottom"
;передаем нуль, чтобы получить контекст устройства рабочего стола
hDC := DllCall("GetDC", UInt, 0)
;создаем красную кисть (0x0000FF в формате BGR)
hBrush := DllCall("CreateSolidBrush", UInt, 0x0000FF)
;заполним указанный прямоугольник, используя кисть выше
DllCall("FillRect", UInt, hDC, Str, Rect, UInt, hBrush)
DllCall("ReleaseDC", UInt, 0, UInt, hDC) ;очистка
DllCall("DeleteObject", UInt, hBrush) ;очистка

Пример использования структуры: изменяем системную дату и время. Проявите осмотрительность: если дата изменяется на будущую, это может заставить преждевременно выполняться плановые работы!

SetSystemTime("20051008142211") ;передаем timestamp (местный, не UTC).

SetSystemTime(YYYYMMDDHHMISS)
;Устанавливаем системные часы на указанную дату и время.
;Вызывающая программа должна гарантировать, что входящий параметр - допустимый date-time stamp
;(местное время, не UTC). Возвращаем не нуль при успехе или нуль иначе.
{
	;Конвертируем параметр из местного времени в UTC для использования с SetSystemTime().
	UTC_Delta -= %A_NowUTC%, Seconds ;секунды вернее округлить
	UTC_Delta := Round(-UTC_Delta/60) ;для верности округляем до ближайшей минуты
	YYYYMMDDHHMISS += %UTC_Delta%, Minutes ;применяем смещение, чтобы преобразовать в UTC

	VarSetCapacity(SystemTime, 16, 0) ;структура состоит из 8 UShorts (то есть 8*2=16)

	StringLeft, Int, YYYYMMDDHHMISS, 4    ;YYYY (год)
	InsertInteger(Int, SystemTime, 0, 2)
	StringMid, Int, YYYYMMDDHHMISS, 5, 2  ;MM (месяц года, 1-12)
	InsertInteger(Int, SystemTime, 2, 2)
	StringMid, Int, YYYYMMDDHHMISS, 7, 2  ;DD (день месяца)
	InsertInteger(Int, SystemTime, 6, 2)
	StringMid, Int, YYYYMMDDHHMISS, 9, 2  ;HH (часы в 24-часовом формате)
	InsertInteger(Int, SystemTime, 8, 2)
	StringMid, Int, YYYYMMDDHHMISS, 11, 2 ;MI (минуты)
	InsertInteger(Int, SystemTime, 10, 2)
	StringMid, Int, YYYYMMDDHHMISS, 13, 2 ;SS (секунды)
	InsertInteger(Int, SystemTime, 12, 2)

	return DllCall("SetSystemTime", str, SystemTime)
}

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

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