</head> <body> <h1>RegisterCallback() <span class="ver">[v1.0.47+]</span></h1> <p>创建机器码地址, 当它被调用时会重定向到脚本中的<a href="../Functions.htm">函数</a>.</p> <pre class="Syntax">Address := <span class="func">RegisterCallback</span>(Function <span class="optional">, Options, ParamCount, EventInfo</span>)</pre> <h2 id="Parameters">参数</h2> <dl> <dt>Function</dt> <dd> <p>要调用的函数名称(或在 <span class="ver">[v1.1.06+]</span> 中可以是<a href="../misc/Functor.htm">函数对象</a>). 要传递原义的函数名称, 要将其括在双引号中. 每当 <em>Address</em> 被调用时, 会自动调用此函数. 该函数还接收传递给 <em>Address</em> 的参数.</p> </dd> <dt>Options</dt> <dd><p>如果为空或省略, 每次调用 <em>Function</em> 时, 都会启动一个新<a href="../misc/Threads.htm">线程</a>, 并使用标准调用约定. 否则, 请指定一或多个下列选项. 在选项间使用空格分隔(例如 <code>C Fast</code>).</p> <p id="Fast"><strong>Fast</strong> 或 <strong>F</strong>: 避免每次调用 <em>Function</em> 时都启动新<a href="../misc/Threads.htm">线程</a>. 尽管这样执行的更好, 但必须避免调用 <em>Address</em> 的线程发生变化(例如当回调函数被传入的消息触发时). 这是因为 <em>Function</em> 能改变在它被调用时刚好在运行的线程的全局设置(例如 <a href="../misc/ErrorLevel.htm">ErrorLevel</a>, <a href="../Variables.htm#LastError">A_LastError</a> 和<a href="../misc/WinTitle.htm#LastFoundWindow">上次找到的窗口</a>). 有关详情, 请参阅<a href="#Threads">备注</a>.</p> <p><strong>CDecl</strong> 或 <strong>C</strong>: 让 <em>Address</em> 遵循 "C" 调用约定. 此选项通常省略, 因为在回调函数中标准调用约定更常用.</p></dd> <dt>ParamCount</dt> <dd><p>如果为空或省略, 默认为 <em>Function</em> 的<a href="../Functions.htm#define">定义</a>中强制参数的数目. 否则, 请指定为 <em>Address</em> 的调用者会传递给它的参数数目. 在这两种情况中, 必须确保调用者准确传递此数目的参数.</p></dd> <dt>EventInfo</dt> <dd> <p>如果省略, 默认为 <em>Address</em>. 如果为空, 默认 0. 否则, 请指定一个整数, 每当通过 <em>Address</em> 调用函数时, <em>Function</em> 将会看见的 <a href="../Variables.htm#EventInfo">A_EventInfo</a>. 这可用于一个 <em>Function</em> 被多个 <em>Address</em> 调用时. 注意: 与其他全局设置不同, <a href="../misc/Threads.htm">当前线程</a>的 A_EventInfo 不会受到<a href="#Fast">快速模式</a>的干扰.</p> <p>如果运行当前脚本的主程序为 32 位, 那么此参数必须介于 0 和 4294967295 之间. 如果主程序为 64 位, 那么此参数可以为 64 位整数. 尽管 <a href="../Variables.htm#EventInfo">A_EventInfo</a> 通常返回无符号整数, 但由于 AutoHotkey 不完全支持 64 位无符号整数, 因此对这个值进行某些操作可能让它溢出从而进入有符号整数的范围.</p></dd> </dl> <h2 id="Return_Value">返回值</h2> <p>一旦成功, RegisterCallback() 返回一个数字地址, 这个地址可以被 <a href="DllCall.htm">DllCall()</a> 或其他任何能够调用机器码函数的函数调用. 如果失败, 它将返回一个空字符串. 失败发生在 <em>Function</em>: 1) 不存在时; 2) 根据 <em>ParamCount</em> 接受过多或过少的参数; 或 3) 接受任何<a href="../Functions.htm#ByRef">ByRef 参数</a>.</p> <a id="The_Callback_Functions_Parameters"></a> <h2 id="The_Functions_Parameters"><em>Function</em> 的参数</h2> <p>分配了回调地址的<a href="../Functions.htm">函数</a>可以接受多达 31 个参数. 允许有<a href="../Functions.htm#optional">可选参数</a>, 这可用于 <em>Function</em> 被多个调用者调用时.</p> <p>正确的解析此参数需要您对 x86 的调用转换机制有所了解. 在 AutoHotKey 中没有形参的概念, 回调的参数列表由整数和一些必要的重解析组成.</p> <p><strong>32 位:</strong> 所有传入参数都是 32 位无符号整数. 如果某个传入参数应该为有符号整数, 则可以参照下面的其中一个例子得到负数:</p> <p>如果传入参数被当作有符号整数, 所有的负数将被显示成下列方法中的一种:</p> <pre><em>; 方法 #1</em> if (wParam &gt; 0x7FFFFFFF) wParam := -(~wParam) - 1 <em>; 方法 #2: 依赖于 AutoHotkey 有 64 位有符号整数的原生支持.</em> wParam := wParam &lt;&lt; 32 &gt;&gt; 32</pre> <p><strong>64 位:</strong> 所有传入参数都是 64 位有符号整数. AutoHotkey 没有对 64 位无符号整数的原生支持.</p> <p><strong>AutoHotkey 32 位/64 位:</strong> 如果传入参数被当作 8 位或 16 位(x64 上是 32 位) 整数, 此数值的高位可能会包含杂位, 可通过以下方法加以过滤:</p> <pre>Callback(UCharParam, UShortParam, UIntParam) { UCharParam &amp;= 0xFF UShortParam &amp;= 0xFFFF UIntParam &amp;= 0xFFFFFFFF <em>;...</em> }</pre> <p>如果某个传入参数应该为字符串, 那么它实际接收的是这个字符串的地址. 要获取这样的字符串, 请使用 <a href="StrGet.htm">StrGet</a>:</p> <pre>MyString := StrGet(MyParameter) <em>; 需要 <span class="ver">[AHK_L 46+]</span></em></pre> <p>如果某个传入参数为结构的地址, 则可以参照 <a href="DllCall.htm#struct">DllCall() 结构</a>中描述的步骤提取其中的各个成员.</p> <p id="Indirect"><strong>接收参数的地址</strong><span class="ver">[AHK_L 60+]</span>: 如果 <em>Function</em> 声明为<a href="../Functions.htm#Variadic">可变参数的</a>, 那么它的最后一个参数被赋值为首个没有赋值给脚本参数的回调参数 <i>地址</i>. 例如:</p> <pre> callback := RegisterCallback("TheFunc", "F", 3) <em>; 必须指定参数列表的大小.</em> TheFunc("TheFunc was called directly.") <em>; 直接调用 TheFunc.</em> DllCall(callback, "float", 10.5, "int64", 42) <em>; 通过回调调用 TheFunc.</em> TheFunc(params*) { if IsObject(params) MsgBox % params[1] else MsgBox % <a href="NumGet.htm">NumGet</a>(params+0, "float") ", " NumGet(params+A_PtrSize, "int64") }</pre> <p>大部分回调使用 <em>stdcall</em> 调用约定, 这种方式需要固定的参数数目. 在这种情况下, <em>ParamCount</em> 必须设置为参数列表的大小, Int64 需要两倍于 32 位的参数.</p> <p>对于 <em>Cdecl</em> 或 64 位调用约定, <em>ParamCount</em> 仅被已经赋值的参数所影响. 如果省略, 所有可选参数被设为他们的默认值, 且不参与 <em>params</em> 保存地址大小的计算.</p> <a id="What_the_Function_Should_Return"></a> <h2 id="What_Function_Should_Return"><em>Function</em> 应该返回什么</h2> <p>如果 <em>Function</em> 使用不带任何参数的 <a href="Return.htm">Return</a>, 或指定空值如 ""(甚至从不使用 Return), 则返回 0 给回调的调用者. 否则, <em>Function</em> 应该返回一个整数, 然后返回给调用者. 32 位 AutoHotkey 将返回值截断为 32 位, 而 64 位 AutoHotkey 支持 64 位返回值. 不支持返回比这更大(按值) 的结构.</p> <h2 id="Threads">快速与慢速</h2> <p>默认/慢速的模式会让 <em>Function</em> 以设置的默认值启动, 例如 <a href="SendMode.htm">SendMode</a> 和 <a href="DetectHiddenWindows.htm">DetectHiddenWindows</a>. 这些默认值可以在<a href="../Scripts.htm#auto">自动执行段</a>改变.</p> <p>与之相比, <a href="#Fast">快速模式</a>会继承在调用 <em>Function</em> 时刚好在运行的<a href="../misc/Threads.htm">线程</a>的全局设置. 而且, <em>Function</em> 对全局设置的任何修改(包括 <a href="../misc/ErrorLevel.htm">ErrorLevel</a> 和<a href="../misc/WinTitle.htm#LastFoundWindow">上次找到的窗口</a>) 都会在<a href="../misc/Threads.htm">当前线程</a>生效. 因此, 快速模式应该只在确切知道 <em>Function</em> 会被哪个线程调用时才使用.</p> <p>要避免被自己(或其他任何线程) 中断, 回调 <em>Function</em> 可以在它的首行使用 <a href="Critical.htm">Critical</a>. 然而, 通过小于 0x0312 消息达到的间接调用 <em>Function</em> 时, 这并不是完全有效的(增加 Critical 的<a href="Critical.htm#Interval">间隔</a>也许有帮助). 而且, <a href="Critical.htm">Critical</a> 不会阻止执行一些可能导致间接调用它自己的动作, 例如调用 <a href="PostMessage.htm">SendMessage</a> 或 <a href="DllCall.htm">DllCall()</a>.</p> <h2 id="Memory">内存</h2> <p>每次使用 RegisterCallback() 都会分配少量的内存(32 字节加上系统开销). 由于操作系统在脚本退出时会自动释放这些内存, 所以任何分配少量, <em>固定</em> 数量的回调的脚本不需要明确地(显式) 释放内存. 与之相比, 不确定/无限制次数调用 RegisterCallback() 的脚本应明确对任何不使用的回调执行下列操作:</p> <pre>DllCall("GlobalFree", "Ptr", Address, "Ptr")</pre> <h2 id="Related">相关</h2> <p><a href="DllCall.htm">DllCall()</a>, <a href="OnMessage.htm">OnMessage()</a>, <a href="OnExit.htm">OnExit</a>, <a href="OnClipboardChange.htm">OnClipboardChange</a>, <a href="Sort.htm#callback">Sort 的回调</a>, <a href="Critical.htm">Critical</a>, <a href="PostMessage.htm">Post/SendMessage</a>, <a href="../Functions.htm">函数</a>, <a href="../misc/SendMessageList.htm">Windows 消息</a>, <a href="../misc/Threads.htm">线程</a></p> <h2 id="Examples">示例</h2> <div class="ex" id="ExWinList"> <p><a class="ex_number" href="#ExWinList"></a> 显示所有顶层窗口的摘要.</p> <pre><em>; 考虑到性能和内存的保持, 只为指定的回调调用一次 RegisterCallback():</em> if not EnumAddress <em>; 由于只能从这个线程调用, 所以可以使用快速模式:</em> EnumAddress := <strong>RegisterCallback</strong>("EnumWindowsProc", "Fast") DetectHiddenWindows On <em>; 由于是快速模式, 所以此设置也会在回调中生效.</em> <em>; 把控制传给 EnumWindows(), 它会重复调用回调:</em> DllCall("EnumWindows", "Ptr", EnumAddress, "Ptr", 0) MsgBox %Output% <em>; 显示由回调收集的信息.</em> 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 <em>; 告知 EnumWindows() 继续执行, 一直到枚举完所有的窗口.</em> }</pre> </div> <div class="ex" id="ExSubclassGUI"> <p><a class="ex_number" href="#ExSubclassGUI"></a> 演示如何在脚本中通过把 GUI 窗口的 WindowProc 重定向到新的 WindowProc 来子类化窗口. 此时, 文本控件的背景颜色被改变为自定义颜色.</p> <pre>TextBackgroundColor := 0xFFBBBB <em>; BGR 格式的自定义颜色.</em> TextBackgroundBrush := DllCall("CreateSolidBrush", "UInt", TextBackgroundColor) Gui, Add, Text, HwndMyTextHwnd, Here is some text that is given`na custom background color. Gui +LastFound GuiHwnd := WinExist() <em>; 64 位脚本必须调用 SetWindowLongPtr 代替 SetWindowLong:</em> SetWindowLong := A_PtrSize=8 ? "SetWindowLongPtr" : "SetWindowLong" WindowProcNew := <strong>RegisterCallback</strong>("WindowProc", "" <em>; 指定 "" 来避免子类化中使用快速模式.</em> , , MyTextHwnd) <em>; 在 <span class="ver">[v1.1.12+]</span> 中, 可以像这样省略 ParamCount.</em> WindowProcOld := DllCall(SetWindowLong, "Ptr", GuiHwnd, "Int", -4 <em>; -4 是 GWL_WNDPROC</em> , "Ptr", WindowProcNew, "Ptr") <em>; 返回值必须设置为 Ptr 或 UPtr 而不是 Int.</em> Gui Show return WindowProc(hwnd, uMsg, wParam, lParam) { Critical global TextBackgroundColor, TextBackgroundBrush, WindowProcOld if (uMsg = 0x0138 &amp;&amp; lParam = A_EventInfo) <em>; 0x0138 为 WM_CTLCOLORSTATIC.</em> { DllCall("SetBkColor", "Ptr", wParam, "UInt", TextBackgroundColor) return TextBackgroundBrush <em>; 返回 HBRUSH 来通知操作系统我们改变了 HDC.</em> } <em>; 否则(如果上面没有返回), 传递所有的未处理事件到原来的 WindowProc.</em> return DllCall("CallWindowProc", "Ptr", WindowProcOld, "Ptr", hwnd, "UInt", uMsg, "Ptr", wParam, "Ptr", lParam) } GuiClose: ExitApp</pre> </div> </body> </html>