AHK v1 自带的 Sleep 足以应付大多数窗口和键鼠自动化,但遇到短周期采样、节拍控制或耗时测试时,系统调度带来的误差就容易被放大。我整理这个库,是为了给 AHK v1 补一个更适合毫秒级短等待的函数入口。
HighPrecisionSleep_v1 源自 thqby 的 v2 库,由 dbgba 移植到 v1。它结合高精度性能计数器和 32/64 位机器码执行等待,调用时只需要传入毫秒数。
适合用在什么地方
- 短周期轮询、采样或连续操作之间的节拍控制。
- 测试一段代码的执行时间,并希望降低普通 Sleep 误差的干扰。
- 需要在几十毫秒附近做更细等待控制的脚本。
窗口出现、控件加载、网页返回这类外部状态,仍然应该优先使用 WinWait、状态判断或超时循环。高精度等待不能替代条件等待。
两个主要入口
HighPrecisionSleep(milliseconds) 是实际等待函数。SetHighPrecisionSleepParams() 用来调整等待策略:第一个参数决定较长等待先交给 AHK 内置 Sleep 的阈值,第二个参数用于最后阶段的微调。默认参数已经适合先上手,只有实际测量后才建议修改。
库会根据 A_PtrSize 选择 32 位或 64 位代码,并在脚本退出时释放申请的内存。第一次调用会完成初始化,后续调用会复用已经准备好的执行地址。
示例代码:HighPrecisionSleep_v1_示例.ahk
库源码:HighPrecisionSleep_v1.ahk
; @description Support millisecond high-precision sleep.
; @author thqby (Original v2) / dbgba (Ported to v1)
; @date 2023/11/01
; @version 0.0.1
; @note Converted for AutoHotkey v1 compatibility by dbgba.
; Original source: https://github.com/thqby/ahk2_lib
HighPrecisionSleep_State() {
static state
if (!IsObject(state))
state := Object("CodePtr", 0, "CodeSize", 0, "CallbackPtr", 0, "ParamsPtr", 0, "OnExitRegistered", 0)
return state
}
HighPrecisionSleep(milliseconds) {
static pfn := 0
if (!pfn)
pfn := HighPrecisionSleep_Init()
return DllCall(pfn, "UInt", milliseconds)
}
; Setting parameters for high-precision sleep.
; sleep_until_milliseconds:
; When HighPrecisionSleep(milliseconds) is greater than this value,
; use AHK's built-in Sleep first and then start high-precision sleep.
; microseconds_for_correction:
; Microseconds used for fine-tuning.
SetHighPrecisionSleepParams(sleep_until_milliseconds := 20, microseconds_for_correction := 10) {
HighPrecisionSleep_Init()
state := HighPrecisionSleep_State()
NumPut(sleep_until_milliseconds + 0, state.ParamsPtr + 0, 0, "UInt")
NumPut(microseconds_for_correction + 0, state.ParamsPtr + 4, 0, "UInt")
return 1
}
HighPrecisionSleep_Init() {
state := HighPrecisionSleep_State()
if (state.CodePtr)
return state.CodePtr
if (A_PtrSize = 8) {
offset := 0xA0
code := "QFNIg+wgi9lIjUwkQP8VjQAAAEiNTCQ4/xWKAAAASItEJDi56AMAAEj340j38YsNiAAAAEyLyEiLRCQ4SPfhuUBCDwBI9/FMK8iLBWgAAABMAUwkQDvYdgor2IvL/xVNAAAASI1MJDj/FTIAAABIi0QkQEg5RCQ4fR9mDx9EAADzkEiNTCQ4/xUTAAAASItEJEBIOUQkOHznSIPEIFvDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
} else {
offset := 0xA9
code := "g+wQVleLPQAAAACNRCQIUP/XjUQkEFD/FQAAAACLTCQcuIPeG0P3ZCQQuNNNYhCL8vdkJBDB7hIzwA+vNQAAAADB6gYPr9Er1hvAAVQkCBFEJAyhAAAAADvIdgkryFH/FQAAAACNRCQQUP/Xi0QkFDtEJAx/K3wKi0QkEDtEJAhzH/OQjUQkEFD/14tEJBQ7RCQMfO1/CotEJBA7RCQIcuFfXoPEEMIEAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
}
size := 0
if (!DllCall("crypt32\CryptStringToBinary", "Str", code, "UInt", 0, "UInt", 1, "Ptr", 0, "UIntP", size, "Ptr", 0, "Ptr", 0))
throw Exception("CryptStringToBinary size query failed", -1, A_LastError)
mem := DllCall("kernel32\VirtualAlloc", "Ptr", 0, "Ptr", size, "UInt", 0x3000, "UInt", 0x04, "Ptr")
if (!mem)
throw Exception("VirtualAlloc failed", -1, A_LastError)
if (!DllCall("crypt32\CryptStringToBinary", "Str", code, "UInt", 0, "UInt", 1, "Ptr", mem, "UIntP", size, "Ptr", 0, "Ptr", 0)) {
err := A_LastError
DllCall("kernel32\VirtualFree", "Ptr", mem, "Ptr", 0, "UInt", 0x8000)
throw Exception("CryptStringToBinary decode failed", -1, err)
}
oldProtect := 0
if (!DllCall("kernel32\VirtualProtect", "Ptr", mem, "Ptr", size, "UInt", 0x40, "UIntP", oldProtect)) {
err := A_LastError
DllCall("kernel32\VirtualFree", "Ptr", mem, "Ptr", 0, "UInt", 0x8000)
throw Exception("VirtualProtect failed", -1, err)
}
mod := DllCall("kernel32\GetModuleHandle", "Str", "kernel32", "Ptr")
qpc := DllCall("kernel32\GetProcAddress", "Ptr", mod, "AStr", "QueryPerformanceCounter", "Ptr")
qpf := DllCall("kernel32\GetProcAddress", "Ptr", mod, "AStr", "QueryPerformanceFrequency", "Ptr")
if (!qpc || !qpf) {
DllCall("kernel32\VirtualFree", "Ptr", mem, "Ptr", 0, "UInt", 0x8000)
throw Exception("GetProcAddress failed", -1, A_LastError)
}
cb := RegisterCallback("HighPrecisionSleep_CallbackSleep", "", 1)
if (!cb) {
DllCall("kernel32\VirtualFree", "Ptr", mem, "Ptr", 0, "UInt", 0x8000)
throw Exception("RegisterCallback failed", -1)
}
p := mem + offset
NumPut(qpc, p + 0 * A_PtrSize, 0, "Ptr")
NumPut(qpf, p + 1 * A_PtrSize, 0, "Ptr")
NumPut(cb, p + 2 * A_PtrSize, 0, "Ptr")
NumPut(20, p + 3 * A_PtrSize, 0, "UInt")
NumPut(10, p + 3 * A_PtrSize + 4, 0, "UInt")
if (A_PtrSize = 4) {
for index, patchOffset in [0x07, 0x19, 0x61, 0x54, 0x3D]
NumPut(p + 4 * (index - 1), mem + patchOffset, 0, "Ptr")
}
state.CodePtr := mem
state.CodeSize := size
state.CallbackPtr := cb
state.ParamsPtr := p + 3 * A_PtrSize
if (!state.OnExitRegistered) {
OnExit(Func("HighPrecisionSleep_OnExit"))
state.OnExitRegistered := 1
}
return state.CodePtr
}
HighPrecisionSleep_CallbackSleep(milliseconds) {
Sleep, %milliseconds%
}
HighPrecisionSleep_OnExit(ExitReason := "", ExitCode := 0) {
state := HighPrecisionSleep_State()
if (state.CallbackPtr) {
DllCall("GlobalFree", "Ptr", state.CallbackPtr, "Ptr")
state.CallbackPtr := 0
}
if (state.CodePtr) {
DllCall("kernel32\VirtualFree", "Ptr", state.CodePtr, "Ptr", 0, "UInt", 0x8000)
state.CodePtr := 0
}
return 0
}
使用时要注意的边界
“高精度”不等于 Windows 变成实时系统,系统负载、线程调度和其他进程仍然会影响最终结果。这个实现会在短等待的最后阶段进行更积极的计时,频繁调用会增加 CPU 占用,所以不建议拿它替代所有普通 Sleep,更不适合长时间空等。
对新手来说,先保留默认参数,用示例里的计时函数多测几次,再判断自己的任务是否真的需要高精度等待,这样最稳妥。
声明:站内资源为整理优化好的代码上传分享与学习研究,如果是开源代码基本都会标明出处,方便大家扩展学习路径。请不要恶意搬运,破坏站长辛苦整理维护的劳动成果。本站为爱好者分享站点,所有内容不作为商业行为。如若本站上传内容侵犯了原著者的合法权益,请联系我们进行删除下架。

评论(0)