# CallbackCreate

创建机器码地址, 当它被调用时会重定向到脚本中的函数.

    Address := CallbackCreate(Function , Options, ParamCount)

## 参数

Function

  类型: 函数对象

  每当调用 Address 时会自动调用此函数对象. 此函数同时会接收到传递给 Address 的参数.

  闭包或绑定函数可以用来区分多个回调函数调用相同的脚本函数.

  回调函数保留对函数对象的引用, 并在脚本调用 CallbackFree 时释放它.

Options

  类型: 字符串

  指定零个或多个下列单词或字符串. 在选项间使用空格分隔(例如 `"C Fast"`).

  Fast 或 F: 避免每次调用 Function 时都启动新的线程. 尽管这样执行的更好, 但必须避免调用 Address 的线程发生变化(例如当回调函数被传入的消息触发). 这是因为 Function 能改变在它被调用时正在运行的线程的全局设置(例如 A_LastError 和上次找到的窗口). 有关详情, 请参阅备注.

  CDecl 或 C: 让 Address 遵循 "C" 调用约定. 此选项通常省略, 因为在回调函数中使用标准调用约定更为常用. 64 位版本的 AutoHotkey 会忽略这个选项, 它使用 x64 调用约定.

  &: 将参数列表的地址(单个整数) 传递给 Function, 而不是传递给各个参数. 可以使用 NumGet 检索参数值. 当使用标准的 32 位调用约定时, ParamCount 必须以 DWORDs 指定参数列表的大小(字节数除以 4).

ParamCount

  类型: 整数

  Address 的调用者会传递给它的参数数目. 如果省略, 则它默认为 `Function.MinParams`, 这通常是 Function 定义中强制参数的数目. 在这两种情况中, 必须确保调用者准确传递此数目的参数.

## 返回值

类型: 整数

CallbackCreate 返回一个机器码地址. 此地址通常通过 DllCall 或使用 NumPut 放置在结构中传递给外部函数, 但也可以由 DllCall 直接调用. 将地址传递给 CallbackFree 将删除回调.

## 错误处理

在下列任何一种情况下, 此函数失败并抛出异常:

- Function 不是对象, 或既没有 `MinParams` 属性, 也没有 `Call` 方法.
- Function 具有 `MinParams` 属性, 该属性超过回调所提供的参数数量.
- ParamCount 为负数.
- ParamCount 省略, 并且: 1) Function 没有 `MinParams` 属性; 或 2) `&` 选项与标准 32 位调用约定一起使用.

## 回调函数的参数

分配给回调地址的函数最多可以接受 31 个参数. 允许使用可选参数, 当多个调用者调用该函数时, 这非常有用.

正确地解析参数需要了解 x86 调用约定是如何工作的. 由于 AutoHotkey 没有类型化参数, 因此假定回调的参数列表由整数组成, 可能需要进行一些重新解释.

AutoHotkey 32-bit: 所有传入参数都是无符号 32 位整数. 较小的类型被填充为 32 位, 而较大的类型被分割为一系列 32 位的参数.

如果传入的参数是一个有符号整数, 任何负数都可以通过下面的例子来表示:

    ; 方法 #1
    if (wParam > 0x7FFFFFFF)
        wParam := -(~wParam) - 1

    ; 方法 #2: 依赖于 AutoHotkey 原生使用带符号的 64 位整数这一事实.
    wParam := wParam << 32 >> 32

AutoHotkey 64-bit: 所有传入的参数都是有符号的 64 位整数. AutoHotkey 本身不支持无符号 64 位整数. 较小的类型被填充为 64 位, 而较大的类型总是通过地址传递.

AutoHotkey 32-bit/64-bit: 如果传入的参数是 8 位或 16 位(或 x64 上的 32 位), 则值的上位可能包含 "garbage" 可以使用 bitwise-and 进行过滤, 如下例所示:

    Callback(UCharParam, UShortParam, UIntParam) {
        UCharParam &= 0xFF
        UShortParam &= 0xFFFF
        UIntParam &= 0xFFFFFFFF
        ;...
    }

如果调用者希望传入参数是字符串, 那么它实际接收的是字符串的地址. 要获取这样的字符串, 请使用 StrGet:

    MyString := StrGet(MyParameter)

如果某个传入参数为结构的地址, 则可以参照 DllCall 结构中描述的步骤提取其中的各个成员.

按址接收参数: 如果使用 `&` 选项, 函数接收第一个回调参数的 地址. 例如:

    callback := CallbackCreate(TheFunc, "F&", 3)  ; 32 位中必须指定参数列表的大小.
    DllCall(callback, "float", 10.5, "int64", 42)
    TheFunc(params) {
        MsgBox NumGet(params, 0, "float") ", " NumGet(params, A_PtrSize, "int64")
    }

32 位程序中的大多数回调都使用 stdcall 调用约定, 该约定需要固定数量的参数. 在这种情况下, 必须将 ParamCount 设置为参数列表的大小, 其中 Int64 和 Double 都计为两个 32 位参数. 使用 Cdecl 或 64 位调用约定时, ParamCount 无效.

## 函数应该 Return 什么

如果函数使用不带任何参数的 Return, 或者指定空白值, 如 ""(或者根本不使用 Return), 则将 0 返回给回调的调用者. 否则, 函数应该返回一个整数, 然后将其返回给调用者. AutoHotkey 32-bit 将返回值截断为 32 位, 而 AutoHotkey 64-bit 则支持 64 位返回值. 不支持返回大于此值的结构(按值).

## Fast vs. Slow

默认/慢速模式使该函数以默认值(如 SendMode 和 DetectHiddenWindows) 重新启动. 这些默认值可以在脚本启动中更改.

与之相比, 快速模式会继承在调用函数时正在运行的线程的全局设置. 而且, 函数对全局设置的任何修改(包括上次找到的窗口) 都会在当前线程生效. 因此, 快速模式应该只在确切知道函数会被哪个线程调用时才使用.

要避免被自己(或其他任何线程) 中断, 回调可以在它的首行使用 Critical. 但是, 通过小于 0x0312 消息达到的间接调用函数时, 这并不是完全有效的(增加 Critical 的 interval 也许有帮助). 而且, Critical 不会阻止执行一些可能导致间接调用它自己的动作, 例如调用 SendMessage 或 DllCall.

## 内存

每次使用 CallbackCreate 都会分配少量内存(32 字节加上系统开销). 由于操作系统会在脚本退出时自动释放该内存, 因此分配了少量, 固定 数量的回调的任何脚本都不必显式释放内存. 相比之下, 不确定/无限制次数调用 CallbackCreate 的脚本应在任何未使用的回调中显式调用以下代码:

    CallbackFree(Address)

这也会释放对脚本的函数对象的回调引用.

## 相关

DllCall, OnMessage, OnExit, OnClipboardChange, Sort 回调, Critical, PostMessage, SendMessage, 函数, 窗口消息列表, 线程

## 示例

显示所有顶层窗口的摘要.

    EnumAddress := CallbackCreate(EnumWindowsProc, "Fast")  ; 快速模式是可以的, 因为它只会在这个线程中被调用.

    DetectHiddenWindows True  ; 由于是快速模式, 所以此设置也会在回调中生效.

    ; 把控制传给 EnumWindows(), 它会重复调用回调:
    DllCall("EnumWindows", "Ptr", EnumAddress, "Ptr", 0)
    MsgBox Output  ; 显示由回调收集的信息.

    EnumWindowsProc(hwnd, lParam)
    {
        global Output
        win_title := WinGetTitle(hwnd)
        win_class := WinGetClass(hwnd)
        if win_title
            Output .= "HWND: " . hwnd . "`tTitle: " . win_title . "`tClass: " . win_class . "`n"
        return true  ; 告知 EnumWindows() 继续执行, 一直到枚举完所有的窗口.
    }

演示如何在脚本中通过把 GUI 窗口的 WindowProc 重定向到新的 WindowProc 来子类化窗口. 此时, 文本控件的背景颜色被改变为自定义颜色.

    TextBackgroundColor := 0xFFBBBB  ; BGR 格式的自定义颜色.
    TextBackgroundBrush := DllCall("CreateSolidBrush", "UInt", TextBackgroundColor)

    MyGui := Gui()
    Text := MyGui.Add("Text",, "Here is some text that is given`na custom background color.")

    ; 64 位脚本必须调用 SetWindowLongPtr 代替 SetWindowLong:
    SetWindowLong := A_PtrSize=8 ? "SetWindowLongPtr" : "SetWindowLong"

    WindowProcNew := CallbackCreate(WindowProc)  ; 避免子类化中使用快速模式.
    WindowProcOld := DllCall(SetWindowLong, "Ptr", MyGui.Hwnd, "Int", -4  ; -4 为 GWL_WNDPROC
        , "Ptr", WindowProcNew, "Ptr") ; 返回值必须设置为 "Ptr" 或 "UPtr" 而不是 "Int".

    MyGui.Show

    WindowProc(hwnd, uMsg, wParam, lParam)
    {
        Critical
        if (uMsg = 0x0138 && lParam = Text.Hwnd)  ; 0x0138 为 WM_CTLCOLORSTATIC.
        {
            DllCall("SetBkColor", "Ptr", wParam, "UInt", TextBackgroundColor)
            return TextBackgroundBrush  ; 返回 HBRUSH 来通知操作系统我们改变了 HDC.
        }
        ; 否则(因为上面没有返回), 将所有的未处理事件传递给原来的 WindowProc.
        return DllCall("CallWindowProc", "Ptr", WindowProcOld, "Ptr", hwnd, "UInt", uMsg, "Ptr", wParam, "Ptr", lParam)
    }

dftg