写 DllCall 时,最容易让人卡住的不是调用一个普通函数,而是参数里出现 RECT*、POINT*、SYSTEMTIME*、WINDOWPLACEMENT* 这类东西。文档里说它是一个结构体指针,新手第一眼看过去,基本就会开始怀疑人生。
我一般把它拆成一句话理解:结构体就是一块连续内存,里面按固定顺序放了几个字段;传结构体,就是先准备这块内存,再把它的地址交给 Windows API。
这篇不追求把 C 语言结构体讲完整,只讲 AHK v1 里最常用、最不容易出错的写法。
先记住四步
遇到结构体参数时,我通常按这个顺序处理:
- 查清结构体有哪些字段,每个字段是什么类型。
- 算出结构体总大小和每个字段的偏移。
- 用
VarSetCapacity分配一块足够大的内存。 - 需要预填的字段用
NumPut写进去,调用后再用NumGet读回来。
这四步看起来麻烦,但掌握以后,很多高级 API 都能慢慢啃下来。
最经典的 RECT 示例
GetWindowRect 会把窗口的屏幕坐标写入一个 RECT 结构。RECT 很适合入门,因为它只有四个整数:left、top、right、bottom。
#Requires AutoHotkey v1.1
WinGet, hwnd, ID, A
VarSetCapacity(rect, 16, 0)
DllCall("GetWindowRect", "Ptr", hwnd, "Ptr", &rect)
left := NumGet(rect, 0, "Int")
top := NumGet(rect, 4, "Int")
right := NumGet(rect, 8, "Int")
bottom := NumGet(rect, 12, "Int")
MsgBox, % "left=" left "`ntop=" top "`nright=" right "`nbottom=" bottom
这里的关键点不是 GetWindowRect 本身,而是 rect 这块内存的布局:
left从偏移 0 开始,占 4 字节。top从偏移 4 开始,占 4 字节。right从偏移 8 开始,占 4 字节。bottom从偏移 12 开始,占 4 字节。
所以总大小是 16。API 写完以后,AHK 再按相同偏移把数字取出来。
为什么要传 &rect
DllCall 里真正传给 API 的不是 rect 这个变量名,而是变量内部缓冲区的地址,也就是 &rect。很多结构体参数在微软文档里会写成 LPRECT、RECT*、PPOINT 这类形式,本质都是“指向某个结构体的指针”。
在 v1 里,指针和句柄优先用 Ptr 类型。这样脚本在 32 位和 64 位 AHK 下更稳,不需要自己把所有指针都改成 UInt 或 Int64。
DllCall("GetWindowRect", "Ptr", hwnd, "Ptr", &rect)
第一个 Ptr 是窗口句柄,第二个 Ptr 是结构体地址。
需要先写入字段的情况
有些 API 不只是把结果写回结构体,还要求你先把某些字段填好。比如有些结构体第一个字段是 cbSize,意思是告诉 API:“我传来的结构体有多大”。这类字段如果不填,调用可能直接失败。
下面用一个简化思路演示,不绑定某个复杂 API:
size := 16
VarSetCapacity(myStruct, size, 0)
NumPut(size, myStruct, 0, "UInt")
NumPut(123, myStruct, 4, "Int")
; DllCall("SomeApi", "Ptr", &myStruct)
如果微软文档写的是 DWORD cbSize,通常用 UInt;如果写的是 int,通常用 Int;如果写的是 HWND、HANDLE、void*、LPCTSTR 这类指针或句柄,就优先按 Ptr 思考。
偏移怎么计算
偏移就是字段从结构体开头往后数多少字节。最简单的结构体可以直接累加字段大小:
Int、UInt、DWORD通常是 4 字节。Short、UShort、WORD通常是 2 字节。Char、UChar、BYTE通常是 1 字节。Ptr、HWND、HANDLE的大小是A_PtrSize。
真正容易出错的是“对齐”。有些结构体为了让字段按 CPU 习惯排列,中间会自动填充空字节。简单的 RECT、POINT、SYSTEMTIME 这类结构不太容易踩坑;一旦结构里混有指针、短整数、数组、嵌套结构,就要特别谨慎。
我建议先从微软文档、AHK 帮助文档、成熟示例里确认大小。如果不确定,不要凭感觉硬写偏移。
32 位和 64 位最容易错在 Ptr
很多老代码喜欢把句柄、地址写成 UInt。在 32 位 AHK 里这样经常能跑,但到了 64 位,指针变成 8 字节,UInt 仍然只有 4 字节,就可能截断地址。
所以凡是看到这些类型,我会优先想到 Ptr:
HWND、HICON、HBITMAP、HANDLELPVOID、void*、RECT*、POINT*- 函数返回值是地址或句柄时,也通常用
Ptr
结构体里如果有指针字段,偏移也要跟着 A_PtrSize 算,而不是固定写 4。
offset := 0 NumPut(hwnd, buffer, offset, "Ptr") offset += A_PtrSize NumPut(flags, buffer, offset, "UInt") offset += 4
什么时候用星号参数
Int*、UInt* 这类星号参数,适合“只传一个简单数值的地址”。比如 API 要返回一个 DWORD,你可以这样写:
DllCall("SomeApi", "UInt*", outValue)
但如果参数是结构体地址,比如 RECT*、POINT*,就不要写成 Int*。结构体是一整块内存,不是一个单独整数。一般应先用 VarSetCapacity 准备结构,再用 Ptr 传 &变量。
写结构体时的排查习惯
我排查结构体问题时,一般先看这几件事:
- 结构体总大小是否正确,尤其是有
cbSize字段时。 - 字段类型是否写错,把
Ptr写成UInt是常见问题。 - 偏移是否考虑了 32/64 位差异。
- 调用结果是否返回失败,必要时用
A_LastError或返回值判断。 - 字符串缓冲区是否足够,API 改了字符串长度后是否需要
VarSetCapacity(var, -1)。
结构体不是玄学。真正麻烦的地方,是 C 文档里的类型、大小、偏移、指针概念需要翻译成 AHK 能操作的内存。只要按“分配内存、写入字段、传地址、读回字段”这条路线走,大多数结构体示例都能拆开看懂。
相关教程
参考来源
- AutoHotkey 帮助文档:DllCall - Structures and Arrays
- AutoHotkey 帮助文档:NumPut、NumGet、VarSetCapacity
- AutoHotkey 论坛:jeeswg's homepage

评论(0)