DllCall()

调用 DLL 文件中的函数, 例如标准的 Windows API 函数.

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

参数

[DllFile\]Function

DLL 或 EXE 文件名, 后面跟着一个反斜杠和函数名. 例如: "MyDLL\MyFunction" (省略时文件扩展名默认为 ".dll"). 如果未指定绝对路径, 则假定 DllFile 在系统的 PATH 指定的路径或 A_WorkingDir 中. 注意, DllCall() 需要一个带反斜杠(\) 的路径. 不支持正斜杠(/).

调用 User32.dll, Kernel32.dll, ComCtl32.dll 或 Gdi32.dll 中的函数时可以省略 DllFile. 例如, 使用 "User32\IsWindowVisible""IsWindowVisible" 得到相同的结果.

如果使用指定的名称无法找到函数, 则会根据当前运行脚本的 AutoHotkey 版本自动附加 A(ANSI) 或 W(Unicode) 后缀. 例如, "MessageBox" 在 ANSI 版本中等同于 "MessageBoxA" 而在 Unicode 版本中等同于 "MessageBoxW".

重复 调用 DLL 通过预先加载此 DLL 文件可以显著改善执行效率.

[1.0.46.08+]: 此参数也可以仅是一个表示需调用函数的内存地址的整数. 如 COMRegisterCallback() 提供的地址.

Type1, Arg1

这样的每对数据表示需传递给函数的单个参数. 参数的个数没有限制. 关于 Type, 请参阅下面的类型表, 关于 Arg, 指定传递给函数的值.

Cdecl ReturnType

通常省略单词 Cdecl 因为大多数函数使用标准调用约定而不是 "C" 调用约定(像 wsprintf 这样接受可变数目参数的函数在这点上是个例外). 如果您省略了 Cdecl 但调用时产生 ErrorLevel An(这里 n 是您传递的参数的总大小), 此时可能需要加上 Cdecl. 注意大多数面向对象的 C++ 函数使用的 thiscall 约定是不受支持的.

如果使用了, 那么单词 Cdecl 应该在返回值类型前列出(如果有), 在单词间使用空格或 tab 分隔. 例如: "Cdecl Str".

[AHK_L 53+]: 因为在 64 位代码中不存在 "C" 调用约定, 所以在 AutoHotkey 的 64 位版本中可以使用 Cdecl 但没有效果.

ReturnType: 如果函数返回 32 位的有符号整型(Int), BOOL 或没有返回值, 则 ReturnType 可以省略. 否则, 需要指定下面类型表中参数类型的其中一个. 还支持星号后缀.

返回值

DllCall 返回由被调用 Function 实际返回的值. 如果 Function 没有返回值, 那么结果为未定义整数. 如果 Function 在被调用时发生了错误, 那么返回值为空(一个空字符串).

参数和返回值类型

类型 描述
Str

例如 "Blue" 或 MyVar 这样的字符串型, 如果被调用函数修改了字符串且此参数是个裸变量, 那么其内容将被更新. 例如, 后面的调用将把 MyVar 的内容转换成大写: DllCall("CharUpper", "Str", MyVar).

然而, 如果函数需要保存超过变量当前容量的字符串, 则在调用前必须确保此变量的容量足够大. 这可以通过调用 VarSetCapacity(MyVar, 123) 来实现, 其中 123 是为 MyVar 保留的容量.

Str 参数不能是计算结果为数字(例如 i+1) 的表达式. 如果这样, 则不会调用函数并设置 ErrorLevel 的值为 -2.

支持星号变量 "Str*" 但极少使用. 它可以用在期望如 "TCHAR **" 或 "LPTSTR *" 类型参数的函数中.

注意: 传递字符串给函数时, 应该注意函数期望什么 类型 的字符串.

AStr
WStr
[AHK_L 42+]: ANSI 或 Unicode( 字符) 字符串型. 请参阅脚本兼容性了解等同的 Win32 类型和其他细节.
Int64 64 位整型, 其范围为 -9223372036854775808(-0x8000000000000000) 到 9223372036854775807(0x7FFFFFFFFFFFFFFF).
Int

32 位整型(最常用的整数类型), 其范围为 -2147483648(-0x80000000) 到 2147483647(0x7FFFFFFF). Int 有时也写成 "Long".

在函数期望 BOOL 类型的参数中也应该使用 Int(BOOL 值应该为 1 或 0).

无符号整型(UInt) 也是频繁使用的类型, 例如用于 DWORD.

Short 16 位整型, 其范围为 -32768(-0x8000) 到 32767(0x7FFF). 无符号短整型(UShort) 可用于函数参数的 WORD 类型.
Char 8 位整型, 其范围为 -128(-0x80) 到 127(0x7F). 无符号字符型(UChar) 可用于函数参数的 BYTE 类型.
Float 32 位浮点型, 具有 6 位精确度.
Double 64 位浮点型, 具有 15 位精确度.
Ptr

[AHK_L 42+]: 指针大小的整型, 根据当前运行的脚本是 32 位还是 64 位分别相当于 Int 或 Int64. Ptr 应该用于数组或结构中的指针(例如 RECT* 或 LPPOINT) 和几乎所有句柄(例如 HWND, HBRUSH 或 HBITMAP). 如果参数是指向简单的数值的指针(例如 LPDWORD 或 int*), 通常应该使用 * 或 P 后缀代替 "Ptr".

Ptr 也可以和 * 或 P 后缀一起使用; 此时它应该用在通过 LPVOID* 或类似类型输出指针的函数中.

UPtr 也是有效的, 但仅适用于 32 位版本中的无符号整型, 因为 AutoHotkey 不支持无符号的 64 位整型.

如果需要和旧版本的 AutoHotkey 兼容, 请使用如下所示的变量类型:

Ptr := A_PtrSize ? "Ptr" : "UInt" ; 若 A_PtrSize 没有定义, 则使用 UInt.
DllCall("DeleteFile", Ptr, &filename) ; 省略 Ptr 两边的引号.

注意: 要传递 NULL 句柄或指针, 请传递整数 0.

* 或 P
(后缀)

在上述任一类型后附加星号(星号前可以含有空格) 表示传递参数的地址而不是参数的值(被调用函数必须接受地址). 由于这时函数可能修改参数的值, 所以每当不受保护的变量作为参数传递时, 变量的内容都会被更新. 例如, 后面的调用将通过地址传递 MyVar 的内容给 MyFunction, 但也会更新 MyVar 来反映 MyFunction 对它作出的改变: DllCall("MyDll\MyFunction", "Int*", MyVar).

一般说来, 星号可用于函数中以 "LP" 开始的参数类型或返回值类型. 最常见的例子是 LPDWORD, 这是指向 DWORD 的指针. 因为 DWORD 是无符号 32 位整型, 所以用 "UInt*" 或 "UIntP" 来表示 LPDWORD. 星号不应该用于字符串类型(例如 LPTSTR), 指向结构的指针(如 LPRECT) 或数组; 对于这些, 根据您需要传递的是变量还是其地址应该使用 "Str" 或 "Ptr".

注意: "Char*" 与 "Str" 类型是不同的, 因为 "Char*" 传递 8 位数的地址, 而 "Str" 传递一系列字符的地址, 根据 AutoHotkey 版本的不同它可能是 8 位(ANSI) 或 16 位(Unicode) 的, 同样地, "UInt*" 会传递 32 位数的地址, 所以不应该使用在函数期望大于 32 位的结构或值的数组的时候.

由于 AutoHotkey 中的变量没有固定的类型, 所以传递给函数的地址指向临时内存而不是变量自身, 不需要对这样的变量调用 VarSetCapacity(), 因为在函数返回后 DllCall() 会正确的更新变量.

U
(前缀)

在上述任一整型前加上字符 U 表示无符号整型(UInt64, UInt, UShort 和 UChar). 严格说来, 这仅对于返回值或星号变量才是必要的, 因为它不影响值传递的参数是无符号的还是有符号的(Int64 除外).

如果在无符号参数中指定负整数, 那么它会被转换到无符号范围中. 例如, 把 -1 作为 UInt 类型传递, 则它会被转换成 0xFFFFFFFF.

不支持由函数产生的 无符号 64 位整型. 因此, 要让大于或等于 0x8000000000000000 的数字有效, 请省略 U 前缀并且把任何从函数接收到的负值转换成很大的正数. 例如, 如果设计返回值为 UInt64 类型的函数在返回 Int64 类型的值 -1 时, 实际的返回值是 0xFFFFFFFFFFFFFFFF.

对于 UInt64 类型的参数, 大的无符号值可以作为字符串传递. 对于没有 * 或 P 后缀的所有较小的整数类型, 或者当将纯 64 位整数传递给函数时, 由于使用二进制补码来表示有符号整数, 有符号与无符号没有区别. 但是, 对于要求无符号的值使用 U 前缀可以提高清晰度.

不推荐: 当指定不包含空格或星号的参数类型或返回值类型时, 包含的引号可以省略. 例如, 可以使用 Str 代替 "Str"CDecl 代替 "CDecl". 不推荐在新脚本中使用.

错误处理

[v1.1.04+]: 此函数失败时会抛出异常. 想了解更多信息, 请参阅运行时错误.

ErrorLevel 会被设置为下列值的其中一个来表示调用成功或失败.

0: 成功.

-1(负 1): [DllFile\]Function 参数是浮点数. 要求参数为字符串或正整数.

-2: 返回值类型或某个指定的参数类型无效. 此错误的原因也可能是传递计算结果为数字的表达式给字符串(str) 参数.

-3: 无法访问或加载指定的 DllFile, 如果没有明确指明 DllFile 的路径, 则文件必须在系统 PATH 变量的路径或 A_WorkingDir 中. 此错误的原因可能是用户缺少访问此文件的权限, 或者 AutoHotkey 为 32 位而 DLL 是 64 位, 反之亦然.

-4: 在 DLL 中没有找到指定的函数.

N(任意正数): 函数被调用后异常终止了, 致命异常号为 N(例如, 0xC0000005 表示 "访问违例"). 此时函数返回空值(空字符串), 但仍会更新所有的星号变量. 致命异常的一个例子是间接引用了无效的指针, 如 NULL. 因为 Cdecl 函数决不会产生下一段描述中的 "An" 错误, 所以当传递给它的参数太少时可能会产生异常.

An(字母 A 后面跟着整数 n): 调用了函数, 但传递给它的参数太多或太少了. "n" 是错误的参数列表超过正确的参数列表的字节数. 如果 n 为正数, 表示传递的参数过多(或参数太大), 或函数调用要求 CDecl 的返回值类型. 如果 n 为负数, 表示传递的参数过少. 应该改正这种情况来让函数可靠运行. 此错误的出现也可能表示出现了异常, 此时函数返回空字符串. 请注意由于 x64 调用约定, 64 位版本不会设置 ErrorLevel 为 An.

异常和 A_LastError

尽管有内置的异常处理机制, 但 DllCall 仍有可能让脚本崩溃. 当函数不直接生成异常而输出一些不适当的数据(例如无效的指针或没有结束符的字符串) 时, 这种情况可能会发生. 这可能不是函数的问题, 而是脚本传递给它不适合的值, 例如无效的指针或内存空间不足的 "str", 当脚本指定不适当的参数类型或返回值类型(例如要求由函数生成的整数是星号变量str) 时, 它也可能崩溃.

内置变量 A_LastError 包含了操作系统 GetLastError() 函数最近调用返回的结果, 在调用函数后会立即执行此函数(对效率的影响未知). A_LastError 是一个介于 0 和 4294967295 之间的数字(总是为十进制而非十六进制). 像 ErrorLevel 一样, A_LastError 是与每个线程关联的设置; 即由其他线程产生的中断不会改变它. 不过, Run/RunWait 也会设置 A_LastError 的值.

性能

当需要重复调用一个 DLL 时, 预先明确装载它可以显著提高执行效率(对于标准的 DLL 文件如 User32 可以不需要, 因为它是常驻的). 这样的实现避免了 DllCall 内部每次调用 LoadLibrary 和 FreeLibrary 的需要. 例如:

hModule := DllCall("LoadLibrary", "Str", "MyFunctions.dll", "Ptr")  ; 避免了在循环中每次都需要使用 DllCall() 装载库.
Loop, C:\My Documents\*.*, , 1
    result := DllCall("MyFunctions\BackupFile", "Str", A_LoopFileFullPath)
DllCall("FreeLibrary", "Ptr", hModule)  ; 为了释放内存, 在使用 DLL 后需要进行卸载.

[v1.0.46.08+]: 通过预先查找函数的地址甚至可以获得更快的性能. 例如:

; 在下面的地址中, 如果 DLL 还没有装载, 则请使用 LoadLibrary 代替 GetModuleHandle.
MulDivProc := DllCall("GetProcAddress", "Ptr", DllCall("GetModuleHandle", "Str", "kernel32", "Ptr"), "AStr", "MulDiv", "Ptr")
Loop 500
    DllCall(MulDivProc, "Int", 3, "Int", 4, "Int", 3)

[AHK_L 31+]: 如果 DllCall 的首个参数是原义的字符串如 "MulDiv" 并且包含了函数的 DLL 在脚本开始前已正常装载了, 那么此字符串会自动被解析为函数地址. 这种内置的最优化比上述演示的例子执行地更高效.

同时, 在脚本的任意位置添加 #NoEnv 这行可以改善在使用了没有加引号的参数类型(例如 Int 与 "Int") 时 DllCall 的性能.

最后, 当传递字符串变量给不会改变它长度的函数时, 传递变量的地址(例如 &MyVar) 而不是作为 "str" 类型可以提升性能(尤其是当字符串很长的时候). 后面的例子把字符串转换成大写: DllCall("CharUpper", "Ptr", &MyVar, "Ptr").

结构和数组

结构是内存中连续的 成员(空间) 的集合. 大多数成员变量类型是整型.

调用接受结构地址(或内存块数组) 的函数可以把结构中的原始二进制数据保存到普通变量中. 一般步骤如下:

1) 调用 VarSetCapacity(MyStruct, 123, 0) 来确保目标变量足够大以便保存结构数据. 把 123 替换为至少和结构一样大小的数字. 最后一个参数零是可选的; 它会初始化所有成员变量为二进制零, 这样通常可以避免在下一步中频繁调用 NumPut().

2) 如果目标函数需要初始化结构, 则调用 NumPut(123, MyStruct, 4, "UInt") 来初始化任意成员为非零数据. 把 123 替换为需要向目标成员存入的整数(或指定 &Var 来存入变量的地址). 把 4 替换为目标成员的偏移(关于 "偏移" 的说明请参阅步骤 #4). 把 "UInt" 替换为合适的类型, 或当成员是指针或句柄时省略.

3) 调用目标函数, 把 MyStruct地址作为 UInt(或 Ptr 在 [AHK_L 42+] 时) 的参数传递过去. 例如, DllCall("MyDll\MyFunc", "Ptr", &MyStruct). 函数将检查和/或改变其中的某些成员变量.

4) 使用 MyInteger := NumGet(MyStruct, 4, "UInt") 从结构中获取任何想要的成员变量. 把 4 替换为结构中目标成员的偏移. 首个成员变量的偏移总为 0. 第二个成员的偏移为 0 加上首个成员的大小(一般为 4 字节). 后面成员的偏移等于它前一个成员的偏移加上前一个成员的大小. 大多数成员(例如 DWORD, Int 和其他的 32 位整型) 大小为 4 个字节. 把 "UInt" 替换为合适的类型, 或当成员是指针或句柄时省略.

请参阅结构示例了解实际用法.

已知限制

当把变量的地址(例如 &MyVar) 传递给函数而此函数修改了变量内容的长度, 那么后面使用这个变量可能出现错误, 要解决此问题, 有下面几种方法: 1) 把 MyVar 作为 "Str" 参数而不是作为 Ptr/地址 传递; 2) [v1.0.44.03+]: 在调用 DllCall 后使用 VarSetCapacity(MyVar, -1) 来更新变量的内部存储长度.

由函数保存到变量中的任何二进制零会隐藏这个零右边的所有数据; 即这样的数据无法被大多数命令和函数访问或修改. 但是, 这样的数据可以使用地址运算符NumPut()/NumGet() 以及 DllCall 自己进行操作.

当一个字符串传递给一个返回此字符串地址的函数后, 此函数可能会出乎意料地返回一个地址不同内容相同的字符串. 例如在编程语言中调用 CharLower(CharUpper(MyVar)) 将把 MyVar 的内容转换成小写形式, 但当使用 DllCall() 进行相同操作时, 在后面的调用操作后 MyVar 将是小写的, 因为 CharLower 对一个内容与 MyVar 相同而地址不同的临时字符串进行操作:

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

要变通解决此问题, 请把上面两个带下划线的 "Str" 修改成 Ptr. 这种情况说明了 CharUpper 的返回值为纯地址并作为整型传递给 CharLower.

处理字符串时可能遇到某些限制. 更多细节请参阅脚本兼容性.

组件对象模型(COM)

在 VBScript 和其他类似语言中可访问的 COM 对象在 AutoHotkey 中一般可以通过 ComObjCreate(), ComObjGet()ComObjActive() 以及内置的对象语法进行访问.

不支持 IDispatch 的 COM 对象可以通过从对象接口的虚函数表中获取函数的地址用于 DllCall 中. 想了解更多细节, 请参阅较远后面的这个例子.

.NET 框架

.NET Framework 库由被称为公共语言运行时(CLR) 的 "虚拟机" 来执行. 在这种情况下, .NET DLL 文件的格式与常规 DLL 文件的格式不同, 并且通常不包含 DllCall 能够调用的任何函数.

但是, AutoHotkey 可以通过 COM callable wrappers 来使用 CLR. 除非该库也已注册为通用 COM 组件, 否则必须先通过 DllCall 手动初始化 CLR 本身. 有关详情, 请参阅 .NET Framework Interop.

脚本兼容性, PostMessage, OnMessage(), RegisterCallback(), Run, VarSetCapacity(), 函数, SysGet, Microsoft Docs

示例

调用 Windows API 函数 "MessageBox" 并报告用户按下了哪个按钮.

WhichButton := DllCall("MessageBox", "Int", 0, "Str", "Press Yes or No", "Str", "Title of box", "Int", 4)
MsgBox You pressed button #%WhichButton%.

将桌面墙纸更改为指定的位图(.bmp) 文件.

DllCall("SystemParametersInfo", "UInt", 0x14, "UInt", 0, "Str", A_WinDir . "\winnt.bmp", "UInt", 1)

调用 API 函数 "IsWindowVisible" 来判断记事本窗口是否可见.

DetectHiddenWindows On
if not DllCall("IsWindowVisible", "Ptr", WinExist("Untitled - Notepad"))  ; WinExist() 返回 HWND.
    MsgBox The window is not visible.

调用 API 的 wsprintf() 函数来给数字 432 加上前导零以填充到 10 个字符的长度(0000000432).

VarSetCapacity(ZeroPaddedNumber, 20)  ; 确保变量的容量足够大以便容纳新的字符串.
DllCall("wsprintf", "Str", ZeroPaddedNumber, "Str", "%010d", "Int", 432, "Cdecl")  ; 需要 Cdecl 调用约定.
MsgBox %ZeroPaddedNumber%

; [v1.1.17+]: 通过 Format 函数配合 0 标志可以达到同样的效果:
MsgBox % Format("{:010}", 432)

演示 QueryPerformanceCounter(), 它提供了比 A_TickCount 的 10 ms 更高的精确度.

DllCall("QueryPerformanceFrequency", "Int64*", freq)
DllCall("QueryPerformanceCounter", "Int64*", CounterBefore)
Sleep 1000
DllCall("QueryPerformanceCounter", "Int64*", CounterAfter)
MsgBox % "Elapsed QPC time is " . (CounterAfter - CounterBefore) / freq * 1000 " ms"

按下热键临时减慢鼠标移动速度, 这样有助于准确定位. 按住 F1 来降低鼠标速度. 释放后则恢复原来的速度.

F1::
SPI_GETMOUSESPEED := 0x70
SPI_SETMOUSESPEED := 0x71
; 获取鼠标当前的速度以便稍后恢复:
DllCall("SystemParametersInfo", "UInt", SPI_GETMOUSESPEED, "UInt", 0, "UIntP", OrigMouseSpeed, "UInt", 0)
; 现在在倒数第二个参数中设置较低的速度(范围为 1-20, 10 是默认值):
DllCall("SystemParametersInfo", "UInt", SPI_SETMOUSESPEED, "UInt", 0, "Ptr", 3, "UInt", 0)
KeyWait F1  ; 这里避免了由于键盘的重复特性导致再次执行 DllCall.
return

F1 up::DllCall("SystemParametersInfo", "UInt", SPI_SETMOUSESPEED, "UInt", 0, "Ptr", OrigMouseSpeed, "UInt", 0)  ; 恢复原来的速度.

监视活动窗口并显示其焦点控件中的垂直滚动条的位置(实时更新). 此脚本需要 [v1.0.43.06+], 因为其中使用了 ControlGet Hwnd.

#Persistent
SetTimer, WatchScrollBar, 100
return

WatchScrollBar:
ActiveWindow := WinExist("A")
if not ActiveWindow  ; 没有活动窗口.
    return
ControlGetFocus, FocusedControl, ahk_id %ActiveWindow%
if not FocusedControl  ; 没有焦点控件.
    return
; 在工具提示中显示垂直或水平滚动条的位置:
ControlGet, ChildHWND, Hwnd,, %FocusedControl%, ahk_id %ActiveWindow%
ToolTip % DllCall("GetScrollPos", "Ptr", ChildHWND, "Int", 1)  ;  最后一个参数 1 表示 SB_VERT, 而 0 表示 SB_HORZ.
return

写入一些文本到文件, 然后从文件读取回内存(需要 [v1.0.34+]). 在同时读取或写入多个文件时, 使用这种方法可以改善性能. [AHK_L 42+]: 使用 FileOpen() 可以实现同样的目的.

FileSelectFile, FileName, S16,, Create a new file:
if (FileName := "")
    return
GENERIC_WRITE := 0x40000000  ; 以写入而不是读取的方式打开文件.
CREATE_ALWAYS := 2  ; 创建新文件(覆盖任何现有的文件).
hFile := DllCall("CreateFile", "Str", FileName, "UInt", GENERIC_WRITE, "UInt", 0, "Ptr", 0, "UInt", CREATE_ALWAYS, "UInt", 0, "Ptr", 0, "Ptr")
if not hFile
{
    MsgBox Can't open "%FileName%" for writing.
    return
}
TestString := "This is a test string.`r`n"  ; 通过这种方式写入内容到文件时, 要使用 `r`n 而不是 `n 来开始新行.
StrLen := StrLen(TestString) * (A_IsUnicode ? 2 : 1)
DllCall("WriteFile", "Ptr", hFile, "Str", TestString, "UInt", StrLen, "UIntP", BytesActuallyWritten, "Ptr", 0)
DllCall("CloseHandle", "Ptr", 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, "Ptr", 0, "UInt", OPEN_EXISTING, "UInt", 0, "Ptr", 0)
if not hFile
{
    MsgBox Can't open "%FileName%" for reading.
    return
}
; 清空变量以便进行测试, 但需要确保它含有足够的容量:
BytesToRead := VarSetCapacity(TestString, StrLen)
DllCall("ReadFile", "Ptr", hFile, "Str", TestString, "UInt", BytesToRead, "UIntP", BytesActuallyRead, "Ptr", 0)
DllCall("CloseHandle", "Ptr", hFile)  ; 关闭文件.
MsgBox The following string was read from the file: %TestString%

当您按下 Win+C 时, 隐藏鼠标光标. 再次按下 Win+C 显示光标. 此脚本来自 www.autohotkey.com/forum/topic6107.html

OnExit, ShowCursor  ; 确保到脚本退出时鼠标光标是显示的.
return

ShowCursor:
SystemCursor("On")
ExitApp

#c::SystemCursor("Toggle")  ; Win+C 热键用来切换鼠标光标的显示和隐藏.

SystemCursor(OnOff=1)   ; 初始化 = "I", "Init"; 隐藏 = 0, "Off"; 切换 = -1,"T", "Toggle"; 显示 = 其他
{
    static AndMask, XorMask, $, h_cursor
        ,c0,c1,c2,c3,c4,c5,c6,c7,c8,c9,c10,c11,c12,c13 ; 系统指针
        , b1,b2,b3,b4,b5,b6,b7,b8,b9,b10,b11,b12,b13   ; 空白指针
        , h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11,h12,h13   ; 默认指针的句柄
    if (OnOff = "Init" or OnOff = "I" or $ = "")       ; 在请求或首次调用时进行初始化
    {
        $ := "h"                                          ; 活动的默认指针
        VarSetCapacity( h_cursor,4444, 1 )
        VarSetCapacity( AndMask, 32*4, 0xFF )
        VarSetCapacity( XorMask, 32*4, 0 )
        system_cursors := "32512,32513,32514,32515,32516,32642,32643,32644,32645,32646,32648,32649,32650"
        StringSplit c, system_cursors, `,
        Loop %c0%
        {
            h_cursor   := DllCall( "LoadCursor", "Ptr",0, "Ptr",c%A_Index% )
            h%A_Index% := DllCall( "CopyImage", "Ptr",h_cursor, "UInt",2, "Int",0, "Int",0, "UInt",0 )
            b%A_Index% := DllCall( "CreateCursor", "Ptr",0, "Int",0, "Int",0
                , "Int",32, "Int",32, "Ptr",&AndMask, "Ptr",&XorMask )
        }
    }
    if (OnOff = 0 or OnOff = "Off" or $ = "h" and (OnOff < 0 or OnOff = "Toggle" or OnOff = "T"))
        $ := "b"  ; 使用空白指针
    else
        $ := "h"  ; 使用保存的指针

    Loop %c0%
    {
        h_cursor := DllCall( "CopyImage", "Ptr",%$%%A_Index%, "UInt",2, "Int",0, "Int",0, "UInt",0 )
        DllCall( "SetSystemCursor", "Ptr",h_cursor, "UInt",c%A_Index% )
    }
}

结构的例子. 把 RECT 结构的地址传递给 GetWindowRect(), 它会把窗口的左, 上, 右和下边的位置(相对于屏幕) 存入结构的成员中.

Run Notepad
WinWait Untitled - Notepad  ; 这里同时设置了 "上次找到的窗口" 以用于下面的 WinExist().
VarSetCapacity(Rect, 16)  ; RECT 结构由四个 32 位整数组成(即 4*4=16).
DllCall("GetWindowRect", "Ptr", WinExist(), "Ptr", &Rect)  ; WinExist() 返回 HWND.
MsgBox % "Left " . NumGet(Rect, 0, "Int") . " Top " . NumGet(Rect, 4, "Int")
    . " Right " . NumGet(Rect, 8, "Int") . " Bottom " . NumGet(Rect, 12, "Int")

结构的例子. 把 RECT 结构的地址传递给 FillRect(), 这个结构表示需要临时描绘为红色的屏幕区域.

VarSetCapacity(Rect, 16, 0)  ; 设置容量为 4 个的 4 字节整型并把它们都初始化为零.
NumPut(A_ScreenWidth//2, Rect, 8, "Int")  ; 结构中的第三个整数是 "rect.right".
NumPut(A_ScreenHeight//2, Rect, 12, "Int") ; 结构中的第四个整数是 "rect.bottom".
hDC := DllCall("GetDC", "Ptr", 0, "Ptr")  ; 传递零来获取桌面的设备上下文.
hBrush := DllCall("CreateSolidBrush", "UInt", 0x0000FF, "Ptr")  ; 创建红色画刷(0x0000FF 是 BGR 格式).
DllCall("FillRect", "Ptr", hDC, "Ptr", &Rect, "Ptr", hBrush)  ;  使用上面的画刷填充指定的矩形.
DllCall("ReleaseDC", "Ptr", 0, "Ptr", hDC)  ; 清理.
DllCall("DeleteObject", "Ptr", hBrush)  ; 清理.

结构的例子. 将系统的时钟更改为指定的日期和时间. 请注意改变时间为将来的日期可能会导致计划任务提早运行!

SetSystemTime("20051008142211")  ; 传递时间戳(本地的, 非 UTC).

SetSystemTime(YYYYMMDDHHMISS)
; 设置系统时钟为指定的日期和时间.
; 调用者必须确保传入的参数是有效的日期时间戳
; (本地时间, 非 UTC). 成功时返回非零值, 否则返回零.
{
    ; 把参数从本地时间转换为 UTC 以便用于 SetSystemTime().
    UTC_Delta -= A_NowUTC, Seconds  ; 取整后秒数会更精确.
    UTC_Delta := Round(-UTC_Delta/60)  ; 取整到最近的分钟数以确保精度.
    YYYYMMDDHHMISS += UTC_Delta, Minutes  ; 对本地时间应用偏移来转换到 UTC.

    VarSetCapacity(SystemTime, 16, 0)  ; 此结构由 8 个 UShort 组成(即 8*2=16).

    Int := SubStr(YYYYMMDDHHMISS, 1, 4)    ; YYYY(年份)
    NumPut(Int, SystemTime, 0, "UShort")
    Int := SubStr(YYYYMMDDHHMISS, 5, 2)  ; MM(年中的月数, 1-12)
    NumPut(Int, SystemTime, 2, "UShort")
    Int := SubStr(YYYYMMDDHHMISS, 7, 2)  ; DD(月中的天数)
    NumPut(Int, SystemTime, 6, "UShort")
    Int := SubStr(YYYYMMDDHHMISS, 9, 2)  ; HH(24 小时制的小时数)
    NumPut(Int, SystemTime, 8, "UShort")
    Int := SubStr(YYYYMMDDHHMISS, 11, 2) ; MI(分钟数)
    NumPut(Int, SystemTime, 10, "UShort")
    Int := SubStr(YYYYMMDDHHMISS, 13, 2) ; SS(秒数)
    NumPut(Int, SystemTime, 12, "UShort")

    return DllCall("SetSystemTime", "Ptr", &SystemTime)
}

更多结构的例子:

#13: 使用 COM 从任务栏暂时地移除活动窗口.

/*
  ITaskbarList 的 VTable 中的方法:
    IUnknown:
      0 QueryInterface  -- 使用 ComObjQuery() 代替
      1 AddRef          -- 使用 ObjAddRef() 代替
      2 Release         -- 使用 ObjRelease() 代替
    ITaskbarList:
      3 HrInit
      4 AddTab
      5 DeleteTab
      6 ActivateTab
      7 SetActiveAlt
*/
IID_ITaskbarList  := "{56FDF342-FD6D-11d0-958A-006097C9A090}"
CLSID_TaskbarList := "{56FDF344-FD6D-11d0-958A-006097C9A090}"

; 创建 TaskbarList 对象并把它的地址保存到 tbl.
tbl := ComObjCreate(CLSID_TaskbarList, IID_ITaskbarList)

activeHwnd := WinExist("A")

DllCall(vtable(tbl,3), "ptr", tbl)                     ; tbl.HrInit()
DllCall(vtable(tbl,5), "ptr", tbl, "ptr", activeHwnd)  ; tbl.DeleteTab(activeHwnd)
Sleep 3000
DllCall(vtable(tbl,4), "ptr", tbl, "ptr", activeHwnd)  ; tbl.AddTab(activeHwnd)

; 非 dispatch 对象总是需要手动释放.
ObjRelease(tbl)

vtable(ptr, n) {
    ; NumGet(ptr+0) 返回对象的虚函数表
    ; (简称为 vtable) 的地址. 表达式的其余部分从
    ; vtable 中获取第 n 个函数的地址.
    return NumGet(NumGet(ptr+0), n*A_PtrSize)
}