RegisterCallback() [v1.0.47+]

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

Address := RegisterCallback(Function , Options, ParamCount, EventInfo)

参数

Function

要调用的函数名称(或在 [v1.1.06+] 中可以是函数对象). 要传递原义的函数名称, 要将其括在双引号中. 每当 Address 被调用时, 会自动调用此函数. 该函数还接收传递给 Address 的参数.

Options

如果为空或省略, 每次调用 Function 时, 都会启动一个新线程, 并使用标准调用约定. 否则, 请指定一或多个下列选项. 在选项间使用空格分隔(例如 C Fast).

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

CDeclC: 让 Address 遵循 "C" 调用约定. 此选项通常省略, 因为在回调函数中标准调用约定更常用.

ParamCount

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

EventInfo

如果省略, 默认为 Address. 如果为空, 默认 0. 否则, 请指定一个整数, 每当通过 Address 调用函数时, Function 将会看见的 A_EventInfo. 这可用于一个 Function 被多个 Address 调用时. 注意: 与其他全局设置不同, 当前线程的 A_EventInfo 不会受到快速模式的干扰.

如果运行当前脚本的主程序为 32 位, 那么此参数必须介于 0 和 4294967295 之间. 如果主程序为 64 位, 那么此参数可以为 64 位整数. 尽管 A_EventInfo 通常返回无符号整数, 但由于 AutoHotkey 不完全支持 64 位无符号整数, 因此对这个值进行某些操作可能让它溢出从而进入有符号整数的范围.

返回值

一旦成功, RegisterCallback() 返回一个数字地址, 这个地址可以被 DllCall() 或其他任何能够调用机器码函数的函数调用. 如果失败, 它将返回一个空字符串. 失败发生在 Function: 1) 不存在时; 2) 根据 ParamCount 接受过多或过少的参数; 或 3) 接受任何ByRef 参数.

Function 的参数

分配了回调地址的函数可以接受多达 31 个参数. 允许有可选参数, 这可用于 Function 被多个调用者调用时.

正确的解析此参数需要您对 x86 的调用转换机制有所了解. 在 AutoHotKey 中没有形参的概念, 回调的参数列表由整数和一些必要的重解析组成.

32 位: 所有传入参数都是 32 位无符号整数. 如果某个传入参数应该为有符号整数, 则可以参照下面的其中一个例子得到负数:

如果传入参数被当作有符号整数, 所有的负数将被显示成下列方法中的一种:

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

; 方法 #2: 依赖于 AutoHotkey 有 64 位有符号整数的原生支持.
wParam := wParam << 32 >> 32

64 位: 所有传入参数都是 64 位有符号整数. AutoHotkey 没有对 64 位无符号整数的原生支持.

AutoHotkey 32 位/64 位: 如果传入参数被当作 8 位或 16 位(x64 上是 32 位) 整数, 此数值的高位可能会包含杂位, 可通过以下方法加以过滤:

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

如果某个传入参数应该为字符串, 那么它实际接收的是这个字符串的地址. 要获取这样的字符串, 请使用 StrGet:

MyString := StrGet(MyParameter)  ; 需要 [AHK_L 46+]

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

接收参数的地址[AHK_L 60+]: 如果 Function 声明为可变参数的, 那么它的最后一个参数被赋值为首个没有赋值给脚本参数的回调参数 地址. 例如:

callback := RegisterCallback("TheFunc", "F", 3)  ; 必须指定参数列表的大小.
TheFunc("TheFunc was called directly.")  ; 直接调用 TheFunc.
DllCall(callback, "float", 10.5, "int64", 42)  ; 通过回调调用 TheFunc.
TheFunc(params*) {
    if IsObject(params)
        MsgBox % params[1]
    else
        MsgBox % NumGet(params+0, "float") ", " NumGet(params+A_PtrSize, "int64")
}

大部分回调使用 stdcall 调用约定, 这种方式需要固定的参数数目. 在这种情况下, ParamCount 必须设置为参数列表的大小, Int64 需要两倍于 32 位的参数.

对于 Cdecl 或 64 位调用约定, ParamCount 仅被已经赋值的参数所影响. 如果省略, 所有可选参数被设为他们的默认值, 且不参与 params 保存地址大小的计算.

Function 应该返回什么

如果 Function 使用不带任何参数的 Return, 或指定空值如 ""(甚至从不使用 Return), 则返回 0 给回调的调用者. 否则, Function 应该返回一个整数, 然后返回给调用者. 32 位 AutoHotkey 将返回值截断为 32 位, 而 64 位 AutoHotkey 支持 64 位返回值. 不支持返回比这更大(按值) 的结构.

快速与慢速

默认/慢速的模式会让 Function 以设置的默认值启动, 例如 SendModeDetectHiddenWindows. 这些默认值可以在自动执行段改变.

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

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

内存

每次使用 RegisterCallback() 都会分配少量的内存(32 字节加上系统开销). 由于操作系统在脚本退出时会自动释放这些内存, 所以任何分配少量, 固定 数量的回调的脚本不需要明确地(显式) 释放内存. 与之相比, 不确定/无限制次数调用 RegisterCallback() 的脚本应明确对任何不使用的回调执行下列操作:

DllCall("GlobalFree", "Ptr", Address, "Ptr")

DllCall(), OnMessage(), OnExit, OnClipboardChange, Sort 的回调, Critical, Post/SendMessage, 函数, Windows 消息, 线程

示例

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

; 考虑到性能和内存的保持, 只为指定的回调调用一次 RegisterCallback():
if not EnumAddress  ; 由于只能从这个线程调用, 所以可以使用快速模式:
    EnumAddress := RegisterCallback("EnumWindowsProc", "Fast")

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

; 把控制传给 EnumWindows(), 它会重复调用回调:
DllCall("EnumWindows", "Ptr", EnumAddress, "Ptr", 0)
MsgBox %Output%  ; 显示由回调收集的信息.
    
EnumWindowsProc(hwnd, lParam)
{
    global Output
    WinGetTitle, title, ahk_id %hwnd%
    WinGetClass, class, ahk_id %hwnd%
    if title
        Output .= "HWND: " . hwnd . "`tTitle: " . title . "`tClass: " . class . "`n"
    return true  ; 告知 EnumWindows() 继续执行, 一直到枚举完所有的窗口.
}

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

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

Gui, Add, Text, HwndMyTextHwnd, Here is some text that is given`na custom background color.
Gui +LastFound
GuiHwnd := WinExist()

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

WindowProcNew := RegisterCallback("WindowProc", ""  ; 指定 "" 来避免子类化中使用快速模式.
    , , MyTextHwnd)  ; 在 [v1.1.12+] 中, 可以像这样省略 ParamCount.
WindowProcOld := DllCall(SetWindowLong,  "Ptr", GuiHwnd, "Int", -4  ; -4 是 GWL_WNDPROC
    ,  "Ptr", WindowProcNew,  "Ptr") ; 返回值必须设置为 Ptr 或 UPtr 而不是 Int.

Gui Show
return

WindowProc(hwnd, uMsg, wParam, lParam)
{
    Critical
    global TextBackgroundColor, TextBackgroundBrush, WindowProcOld
    if (uMsg = 0x0138 && lParam = A_EventInfo)  ; 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)
}

GuiClose:
ExitApp