DllCall 时,最容易让人卡住的不是调用一个普通函数,而是参数里出现 RECT*POINT*SYSTEMTIME*WINDOWPLACEMENT* 这类东西。文档里说它是一个结构体指针,新手第一眼看过去,基本就会开始怀疑人生。

我一般把它拆成一句话理解:结构体就是一块连续内存,里面按固定顺序放了几个字段;传结构体,就是先准备这块内存,再把它的地址交给 Windows API。

这篇不追求把 C 语言结构体讲完整,只讲 AHK v1 里最常用、最不容易出错的写法。

先记住四步

遇到结构体参数时,我通常按这个顺序处理:

  1. 查清结构体有哪些字段,每个字段是什么类型。
  2. 算出结构体总大小和每个字段的偏移。
  3. VarSetCapacity 分配一块足够大的内存。
  4. 需要预填的字段用 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。很多结构体参数在微软文档里会写成 LPRECTRECT*PPOINT 这类形式,本质都是“指向某个结构体的指针”。

在 v1 里,指针和句柄优先用 Ptr 类型。这样脚本在 32 位和 64 位 AHK 下更稳,不需要自己把所有指针都改成 UIntInt64

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;如果写的是 HWNDHANDLEvoid*LPCTSTR 这类指针或句柄,就优先按 Ptr 思考。

偏移怎么计算

偏移就是字段从结构体开头往后数多少字节。最简单的结构体可以直接累加字段大小:

  • IntUIntDWORD 通常是 4 字节。
  • ShortUShortWORD 通常是 2 字节。
  • CharUCharBYTE 通常是 1 字节。
  • PtrHWNDHANDLE 的大小是 A_PtrSize

真正容易出错的是“对齐”。有些结构体为了让字段按 CPU 习惯排列,中间会自动填充空字节。简单的 RECT、POINT、SYSTEMTIME 这类结构不太容易踩坑;一旦结构里混有指针、短整数、数组、嵌套结构,就要特别谨慎。

我建议先从微软文档、AHK 帮助文档、成熟示例里确认大小。如果不确定,不要凭感觉硬写偏移。

32 位和 64 位最容易错在 Ptr

很多老代码喜欢把句柄、地址写成 UInt。在 32 位 AHK 里这样经常能跑,但到了 64 位,指针变成 8 字节,UInt 仍然只有 4 字节,就可能截断地址。

所以凡是看到这些类型,我会优先想到 Ptr

  • HWNDHICONHBITMAPHANDLE
  • LPVOIDvoid*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 能操作的内存。只要按“分配内存、写入字段、传地址、读回字段”这条路线走,大多数结构体示例都能拆开看懂。

相关教程

参考来源

声明:站内资源为整理优化好的代码上传分享与学习研究,如果是开源代码基本都会标明出处,方便大家扩展学习路径。请不要恶意搬运,破坏站长辛苦整理维护的劳动成果。本站为爱好者分享站点,所有内容不作为商业行为。如若本站上传内容侵犯了原著者的合法权益,请联系我们进行删除下架。