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,更不适合长时间空等。

对新手来说,先保留默认参数,用示例里的计时函数多测几次,再判断自己的任务是否真的需要高精度等待,这样最稳妥。

声明:站内资源为整理优化好的代码上传分享与学习研究,如果是开源代码基本都会标明出处,方便大家扩展学习路径。请不要恶意搬运,破坏站长辛苦整理维护的劳动成果。本站为爱好者分享站点,所有内容不作为商业行为。如若本站上传内容侵犯了原著者的合法权益,请联系我们进行删除下架。