# 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"` 产生相同的结果.

  如果使用指定的名称无法找到函数, 则自动附加 "W"(Unicode) 前缀. 例如, `"MessageBox"` 等同于 `"MessageBoxW"`.

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

  此参数也可以仅是一个表示需调用函数的内存地址的整数. 如 COM 和 CallbackCreate 提供的地址.

  如果该参数是一个对象, 则使用该对象的 `Ptr` 属性的值. 如果不存在这样的属性, 则抛出 PropertyError.

Type1, Arg1

  类型: 字符串

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

Cdecl ReturnType

  类型: 字符串

  通常省略单词 Cdecl, 因为大多数函数使用标准调用约定而不是 "C" 调用约定(像 wsprintf 这样接受可变数目参数的函数在这点上是个例外). 注意, 不支持大多数面向对象的 C++ 函数使用的 thiscall 约定.

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

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

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

## 返回值

类型: 字符串或整数

DllCall 返回 Function 返回的实际值. 如果 Function 属于不返回值的类型, 则结果为指定返回类型的未定义值(默认为整数).

## 参数和返回值的类型

类型 | 描述
--- | ---
Str | 一个字符串, 如 `"Blue"` 或 `MyVar`, 或 VarRef(如 `&MyVar`). 如果被调用的函数修改了字符串, 并且参数是裸变量或 VarRef, 那么它的内容将被更新. 例如, 下面的调用将把 MyVar 的内容转换为大写字母: `DllCall("CharUpper", "Str", MyVar)`. 如果函数的设计存储长度大于参数的输入值(或者参数仅用于输出), 建议创建一个缓冲, 使用 Ptr 类型传递, 然后使用 StrGet 在函数返回后检索字符串, 如 wsprintf 示例所示. 否则, 请在调用函数之前确保变量足够大. 这可以通过调用 `VarSetStrCapacity(MyVar, 123)` 来实现, 其中 123 是 MyVar 肯定能够容纳的 16 位单位(通俗地讲就是字符) 的数量. 如果变量返回时不是以空终止符结尾的, 则会显示一条错误消息, 并且由于内存可能由于缓冲溢出而损坏导致程序退出. 这通常表明变量的容量不足. Str 参数不能是计算结果为数字(例如 `i+1`) 这样的表达式. 如果如此, 则不会调用函数并抛出 TypeError. 很少使用的 Str* 参数类型传递包含字符串地址的临时变量的地址. 如果函数将新地址写入临时变量, 则将新字符串复制到脚本变量, if a VarRef was passed. 它可以用在期望 "TCHAR **" 或 "LPTSTR *" 类型参数的函数中. 但是, 如果函数分配了内存并希望调用者释放它(例如通过调用 CoTaskMemFree), 则必须改用 `Ptr*` 参数类型. 注意: 在将字符串传递给函数时, 请注意函数所期望的字符串的 type(类型).
WStr | 由于 AutoHotkey 原生使用 UTF-16, 因此 WStr(宽字符串) 等同于 Str.
AStr | AStr 导致输入值自动转换为 ANSI. 由于用于此转换的临时内存仅足够容纳转换后的输入字符串, 因此函数写入其的任何值都将被丢弃. 要接收 ANSI 字符串作为输出参数, 请遵循以下示例:
    buf := Buffer(length)  ; 分配临时缓冲.
    DllCall("Function", "ptr", buf)  ; 传递缓冲给函数.
    str := StrGet(buf, "cp0")  ; 从缓冲接收 ANSI 字符串.
还支持很少使用的 AStr* 参数类型, 其行为与 Str* 类型类似, 不同之处在于, 所有新字符串都将从 ANSI 转换为原生格式, UTF-16. 有关等效的 Win32 类型和其他详细信息, 请参阅二进制兼容性.
Int64 | 64 位整数, 其范围是 -9223372036854775808(-0x8000000000000000) 到 9223372036854775807(0x7FFFFFFFFFFFFFFF).
Int | 32 位整数(最常用的整数类型), 其范围是 -2147483648(-0x80000000) 到 2147483647(0x7FFFFFFF). Int 有时也被称为 "Long". 在函数期望 BOOL 类型的参数中也应该使用 Int(BOOL 值应该是 1 或 0). 无符号的 Int(UInt) 也经常使用, 例如用作 DWORD.
Short | 16 位整数, 其范围是 -32768(-0x8000) 到 32767(0x7FFF). 无符号 Short(UShort) 能用于期望 BYTE 的函数中.
Char | 8 位整数, 其范围是 -128(-0x80) 到 127(0x7F). 无符号 character(UChar) 能用于期望 WORD 的函数中.
Float | 32 位浮点数, 具有 6 位精度.
Double | 64 位浮点数, 具有 15 位精度.
Ptr | 指针大小的整数, 等同于 Int 或 Int64, 具体取决于运行脚本的 exe 是 32 位还是 64 位. Ptr 应该用于指向数组或结构(如 RECT* 或 LPPOINT) 以及几乎所有句柄(如 HWND, HBRUSH 或 HBITMAP) 的指针. 如果参数是指向简单数值的指针( 如 LPDWORD 或 int*), 通常应该使用 * 或 P 后缀代替 "Ptr". 如果将对象传递给 Ptr 参数, 则使用该对象的 `Ptr` 属性的值. 如果不存在此类属性, 则抛出 PropertyError. 通常, 该对象是 Buffer 对象. 如果将对象传递给 Ptr* 参数, 则在调用之前将检索对象的 `Ptr` 属性值, 并将包含该值的临时变量的地址传递给函数. 函数返回后, 新值将分配回该对象的 `Ptr` 属性. Ptr 也可以和 * 或 P 后缀一起使用; 此时它应该用在通过 LPVOID* 或类似类型输出指针的函数中. UPtr 也是有效的, 但仅适用于 32 位版本中的无符号整数, 因为 AutoHotkey 不支持无符号的 64 位整数. 注意: 要传递 NULL 句柄或指针, 请传递整数 0.
* 或 P (后缀) | 在上述任何类型上附加一个星号(星号前可选的空格), 以表示传递参数的地址而不是值本身(必须将被调用的函数设计为接受该参数). 由于此类参数的值可能会被函数修改, 每当传递一个 VarRef 作为参数时, 变量的内容将在函数返回后被更新. 例如, 以下调用将通过地址将 MyVar 的内容传递给 MyFunction, 但还将更新 MyVar 以反映 MyFunction 对它所做的任何更改: `DllCall("MyDll\MyFunction", "Int*", &MyVar)`. 通常, 星号可用于参数类型或返回类型以 "LP" 开头的函数. 最常见的示例是 LPDWORD, 它是指向 DWORD 的指针. 由于 DWORD 是 32 位无符号整数, 因此请使用 "UInt*" 或 "UIntP" 表示 LPDWORD. 星号不能用于字符串类型(如 LPTSTR), 指向结构的指针(如 LPRECT) 或数组; 对于这些, 应使用 "Str" 或 "Ptr", 具体取决于您传递的是字符串, 地址或 Buffer. 注意: "Char*" 与 "Str" 不同, 因为 "Char*" 传递一个 8 位数字的地址, 而 "Str" 传递一系列字符的地址, 该字符可以是 16 位(Unicode) 或 8 位("AStr"), 具体取决于 AutoHotkey 的版本. 同样, "UInt*" 传递的是 32 位数字的地址, 因此如果函数期望值的数组或大于 32 位的结构, 则不应使用 "UInt*". 由于 AutoHotkey 中的变量没有固定类型, 因此传递给函数的地址指向临时内存, 而不是调用者的变量.
U (前缀) | 在上述任一整数类型加上字母 U 前缀, 以将其解释为无符号整数(UInt64, UInt, UShort 和 UChar). 严格来说, 这仅对返回值和星号变量才是必需的, 因为按值传递的参数是无符号的还是有符号的(Int64 除外) 都无关紧要. 如果为无符号参数指定了负整数, 则该整数将被转换到无符号域中. 例如, 当 -1 作为 UInt 发送时, 它将被转换成 0xFFFFFFFF. 不支持函数产生的 无符号 64 位整数. 因此, 要使用大于或等于 0x8000000000000000 的数字, 请省略 U 前缀并将从函数接收的任何负值解释为大整数. 例如, 如果将函数设计为产生 UInt64, 则将函数产生的 -1(Int64 类型), 实际上将产生 0xFFFFFFFFFFFFFFFF.
HRESULT | 32 位整数. 这通常与 COM 函数一起使用, 并且仅作为返回类型有效, 没有任何前缀或后缀. 错误值(由 FAILED macro 定义) 永远不会返回; 而是抛出一个 OSError. 因此, 成功时, 返回值范围是 0 到 2147483647. HRESULT 是 ComCall 的默认返回类型.

## 错误

DllCall 会在下列任何一种情况下抛出 Error:

- OSError: 使用 HRESULT 返回类型, 函数返回一个错误值(由 FAILED macro 定义). `Exception.Extra` 包含十六进制错误码.
- TypeError: [DllFile\]Function 参数是一个浮点数. 需要字符串或正整数.
- ValueError: 返回的类型或指定的参数的类型无效.
- TypeError: 参数传递了一个意外类型的值. 例如, 将计算结果为数字的表达式传递给字符串(str) 参数, 将非数字字符串传递给数值参数, 或者将对象传递给 非 Ptr 类型的参数.
- Error: 无法访问或加载指定的 DllFile. 如果没有明确指明 DllFile 的路径, 则该文件必须存在于系统的 PATH 或 A_WorkingDir 中. 如果用户没有访问文件的权限, 或者 AutoHotkey 是 32 位而 DLL 是 64 位(反之亦然), 也都可能发生此错误.
- Error: 在 DLL 中找不到指定的函数.
- Error: 该函数已被调用, 但由于致命异常而中止. `Exception.Extra` 包含异常代码. 例如, 0xC0000005 表示 "访问冲突". 在这种情况下, 线程将中止(如果没有使用 try 的话), 但是仍将更新所有星号变量. 致命异常的一个例子是取消引用无效指针(如 NULL(0)). 由于 Cdecl 函数永远不会产生下一段中描述的错误, 因此当传递给它的参数太少时, 它可能会生成异常.
- Error: 该函数已被调用, 但传递了太多或太少的参数. `Exception.Extra` 包含参数列表不正确的字节数. 如果为正, 则传递了太多的参数(或参数太大), 或者调用需要 CDecl. 如果为负, 则传递的参数太少. 应该纠正这种情况, 以确保函数的可靠运行. 出现此错误也可能表示抛出了异常. 请注意, 由于使用 x64 调用约定, 因此 64 位版本永远不会引发此错误.

## 原生异常和 A_LastError

尽管有内置的异常处理机制, 仍可能因使用 DllCall 使脚本崩溃. 当函数不直接生成异常但产生不适当的数据时, 如无效的指针或未终止的字符串, 可能会发生这种情况. 如果脚本向其传递了不合适的值, 如无效的指针或容量不足的 "str", 则可能不是函数的错误. 当脚本指定了不适当的参数类型或返回类型时, 如声明函数产生的普通整数是星号变量或 str, 脚本也可能崩溃.

内置变量 A_LastError 包含操作系统的 GetLastError() 函数的结果.

## 性能

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

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

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

    ; 在下面的示例中, 如果 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)

如果 DllCall 的首个参数是原义的字符串如 `"MulDiv"` 并且包含了函数的 DLL 在脚本开始前已正常装载了, 或者已成功通过 #DllLoad 加载, 那么此字符串会自动被解析为函数地址. 这种内置的优化比上面显示的示例更有效.

最后, 当将字符串变量传递给不会改变字符串长度的函数时, 通过将变量按地址传递(例如 `StrPtr(MyVar)`), 而不是以 "str" 的形式传递, 可以提高性能(特别是当字符串很长时). 后面的例子把字符串转换成大写: `DllCall("CharUpper", "Ptr", StrPtr(MyVar), "Ptr")`.

## 结构和数组

结构是内存中连续存储的 成员(空间) 的集合. 大多数成员倾向于是整数.

接受结构(或内存块数组) 地址的函数可以通过某种方式分配内存并将内存地址传递给函数来调用. Buffer 对象被推荐用于此目的. 一般使用以下步骤:

1) 调用 `MyStruct := Buffer(123, 0)` 来分配一个缓冲来保存结构体的数据. 将 `123` 替换为至少与结构大小一样大的数字, 以字节为单位. 指定零作为最后一个参数是可选的; 它将所有成员初始化为二进制零, 这通常用于避免在下一步中频繁调用 NumPut.

2) 如果目标函数使用结构中的初始值, 请调用 `NumPut("UInt", 123, MyStruct, 4)` 初始化任何不为零的成员. 将 `123` 替换为要放入目标成员的整数(或指定 `StrPtr(Var)` 来存储字符串的地址). 将 `4` 替换为目标成员的偏移量(有关 "offset" 的说明, 请参阅步骤 #4). 将 `"UInt"` 替换为适当的类型, 如果成员是指针或句柄, 则如 `"Ptr"`.

3) 调用目标函数, 将 MyStruct 作为 Ptr 参数传递. 例如, `DllCall("MyDll\MyFunc", "Ptr", MyStruct)`. 该函数将检查和/或更改一些成员. DllCall 会自动使用缓冲的地址, 通常使用 `MyStruct.Ptr` 来检索.

4) 使用 `MyInteger := NumGet(MyStruct, 4, "UInt")` 从结构中检索任何所需的整数. 将 `4` 替换为结构中目标构件的偏移量. 第一个成员的偏移量始终为 0. 第二个成员的偏移量为 0 加第一个成员的大小(通常为 4). 第二个成员之后的成员是前一个成员的偏移量加上前一个成员的大小. 大多数成员 -- 如 DWORD, Int 和其他类型的 32 位整数 -- 的大小为 4 个字节. 将 `"UInt"` 替换为适当的类型, 如果成员是指针或句柄, 则将其省略.

有关实际用法, 请参阅结构示例.

## 已知限制

当一个变量的字符串地址(例如 `StrPtr(MyVar)`) 被传递给一个函数, 并且该函数改变了变量内容的长度时, 那么后面使用这个变量可能出现错误. 要解决此问题, 有下面几种方法: 1) 将 MyVar 作为 "Str" 参数传递, 而不是 Ptr/地址; 2) 在调用 DllCall 后, 调用 `VarSetStrCapacity(MyVar, -1)` 来更新变量的内部存储长度.

由函数保存到变量中的任何二进制零都可以作为终止符, 防止大多数内置函数访问或更改零右边的所有数据. 但是, 这样的数据可以使用 StrPtr 检索字符串的地址, 并将其传递给其他函数, 例如 NumPut, NumGet, StrGet, StrPut, 以及 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 中一般可以通过 ComObject, ComObjGet 或 ComObjActive 以及内置的对象语法进行访问.

不支持 IDispatch 的 COM 对象可以通过从对象接口的虚拟函数表中获取函数的地址用于 DllCall 中. 有关详情, 请参阅下面的示例. 但是, 通常最好使用 ComCall 来简化此过程.

## .NET 框架

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

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

## 相关

二进制兼容性, 缓冲对象, ComCall, PostMessage, OnMessage, CallbackCreate, Run, VarSetStrCapacity, 函数, SysGet, #DllLoad, Windows API Index

## 示例

调用 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 True
    if not DllCall("IsWindowVisible", "Ptr", WinExist("Untitled - Notepad"))  ; WinExist 返回 HWND.
        MsgBox "The window is not visible."

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

    ZeroPaddedNumber := Buffer(20)  ; 确保缓冲足够大以便容纳新的字符串.
    DllCall("wsprintf", "Ptr", ZeroPaddedNumber, "Str", "%010d", "Int", 432, "Cdecl")  ; 需要 Cdecl 调用约定.
    MsgBox StrGet(ZeroPaddedNumber)

    ; 此外, 使用 Format 函数配合 0 标志可以达到同样的效果:
    MsgBox Format("{:010}", 432)

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

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

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

    F1::
    F1 up::
    {
        static SPI_GETMOUSESPEED := 0x70
        static SPI_SETMOUSESPEED := 0x71
        static OrigMouseSpeed := 0

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

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

监视活动窗口并显示其焦点控件中的垂直滚动条的位置(实时更新).

    SetTimer WatchScrollBar, 100

    WatchScrollBar()
    {
        try FocusedHwnd := ControlGetFocus("A")
        if !FocusedHwnd  ; 没有焦点控件.
            return
        ; 在工具提示中显示垂直或水平滚动条的位置:
        ToolTip(DllCall("GetScrollPos", "Ptr", FocusedHwnd, "Int", 1))  ;  最后一个参数 1 表示 SB_VERT, 而 0 表示 SB_HORZ.
    }

写入一些文本到文件, 然后从文件读取回内存. 在同时读取或写入多个文件时, 使用这种方法可以改善性能. 此外, 使用 FileOpen 可以实现同样的目的.

    FileName := FileSelect("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 !hFile
    {
        MsgBox "Can't open '" FileName "' for writing."
        return
    }
    TestString := "This is a test string.`r`n"  ; 通过这种方式写入内容到文件时, 要使用 `r`n 而不是 `n 来开始新行.
    StrSize := StrLen(TestString) * 2
    DllCall("WriteFile", "Ptr", hFile, "Str", TestString, "UInt", StrSize, "UIntP", &BytesActuallyWritten := 0, "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 !hFile
    {
        MsgBox "Can't open '" FileName "' for reading."
        return
    }
    ; 为要读取的字符串分配一块内存:
    Buf := Buffer(StrSize)
    DllCall("ReadFile", "Ptr", hFile, "Ptr", Buf, "UInt", Buf.Size, "UIntP", &BytesActuallyRead := 0, "Ptr", 0)
    DllCall("CloseHandle", "Ptr", hFile)  ; 关闭文件.
    MsgBox "The following string was read from the file: " StrGet(Buf)

当您按下 `Win`+`C` 时, 隐藏鼠标光标. 稍后若要显示光标, 请再次按下热键.

    OnExit (*) => SystemCursor("Show")  ; 确保到脚本退出时鼠标光标是显示的.

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

    SystemCursor(cmd)  ; cmd = "Show|Hide|Toggle|Reload"
    {
        static visible := true, c := Map()
        static sys_cursors := [32512, 32513, 32514, 32515, 32516, 32642
                             , 32643, 32644, 32645, 32646, 32648, 32649, 32650]
        if (cmd = "Reload" or !c.Count)  ; 在请求或首次调用时进行重载.
        {
            for i, id in sys_cursors
            {
                h_cursor  := DllCall("LoadCursor", "Ptr", 0, "Ptr", id)
                h_default := DllCall("CopyImage", "Ptr", h_cursor, "UInt", 2
                    , "Int", 0, "Int", 0, "UInt", 0)
                h_blank   := DllCall("CreateCursor", "Ptr", 0, "Int", 0, "Int", 0
                    , "Int", 32, "Int", 32
                    , "Ptr", Buffer(32*4, 0xFF)
                    , "Ptr", Buffer(32*4, 0))
                c[id] := {default: h_default, blank: h_blank}
            }
        }
        switch cmd
        {
          case "Show": visible := true
          case "Hide": visible := false
          case "Toggle": visible := !visible
          default: return
        }
        for id, handles in c
        {
            h_cursor := DllCall("CopyImage"
                , "Ptr", visible ? handles.default : handles.blank
                , "UInt", 2, "Int", 0, "Int", 0, "UInt", 0)
            DllCall("SetSystemCursor", "Ptr", h_cursor, "UInt", id)
        }
    }

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

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

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

    Rect := Buffer(16)  ; 设置容量为 4 个 4 字节整型.
    NumPut( "Int", 0                  ; 左
          , "Int", 0                  ; 上
          , "Int", A_ScreenWidth//2   ; 右
          , "Int", A_ScreenHeight//2  ; 下
          , Rect)
    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 := DateDiff(A_Now, A_NowUTC, "Seconds")  ; 取整后秒数会更精确.
        UTC_Delta := Round(-UTC_Delta/60)  ; 取整到最近的分钟数以确保精度.
        YYYYMMDDHHMISS := DateAdd(YYYYMMDDHHMISS, UTC_Delta, "Minutes")  ; 对本地时间应用偏移来转换到 UTC.

        SystemTime := Buffer(16)  ; 此结构由 8 个 UShort 组成(即 8*2=16).

        NumPut( "UShort", SubStr(YYYYMMDDHHMISS, 1, 4)  ; YYYY(年)
              , "UShort", SubStr(YYYYMMDDHHMISS, 5, 2)  ; MM(月, 01-12)
              , "UShort", 0                             ; Unused(周几)
              , "UShort", SubStr(YYYYMMDDHHMISS, 7, 2)  ; DD(日)
              , "UShort", SubStr(YYYYMMDDHHMISS, 9, 2)  ; HH(小时, 00-24)
              , "UShort", SubStr(YYYYMMDDHHMISS, 11, 2) ; MI(分钟)
              , "UShort", SubStr(YYYYMMDDHHMISS, 13, 2) ; SS(秒)
              , "UShort", 0                             ; Unused(毫秒)
              , SystemTime)

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

更多结构的例子:

- 请参阅 WinLIRC 客户端脚本演示来学习如何使用 DllCall 来创建到 TCP/IP 服务器的连接并从那里接收数据.
- 操作系统提供了标准的对话框让用户选取字体和/或颜色或图标. 这些对话框使用了结构, 并可以在这里找到演示的例子: GitHub.

从任务栏移除活动窗口 3 秒. 可以对比一下等同效果的 ComCall 示例.

    /*
      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 := ComObject(CLSID_TaskbarList, IID_ITaskbarList)

    activeHwnd := WinExist("A")

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

    ; 非包装接口指针必须手动释放.
    ObjRelease(tbl.ptr)

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

dftg