Windows11+Python3.11下windll.user32.SetWinEventHook无法正常工作求助
解决Windows 11下Python监听焦点变更事件时程序静默终止的问题
在Windows 11、Python 3.11环境中,尝试通过SetWinEventHook监听EVENT_SYSTEM_FOREGROUND事件检测窗口焦点变更,但启动记事本后程序直接静默终止,未触发预期的事件打印。原代码如下:
import win32con import pythoncom from ctypes import wintypes, windll, WINFUNCTYPE # WinEventProc and GUITHREADINFO WinEventProc = WINFUNCTYPE( None, wintypes.HANDLE, wintypes.DWORD, wintypes.HWND, wintypes.LONG, wintypes.LONG, wintypes.DWORD, wintypes.DWORD ) # focus_changed function def focus_changed(hWinEventHook, event, hwnd, idObject, idChild, dwEventThread, dwmsEventTime): print("Focus changed event detected") # Main code hook = windll.user32.SetWinEventHook( win32con.EVENT_SYSTEM_FOREGROUND, win32con.EVENT_SYSTEM_FOREGROUND, 0, WinEventProc(focus_changed), 0, 0, win32con.WINEVENT_OUTOFCONTEXT ) if not hook: print(f"SetWinEventHook failed") print("Script is running...") while True: try: pythoncom.PumpWaitingMessages() except KeyboardInterrupt: print("Exiting...") break # Cleanup: Unhook events and release resources windll.user32.UnhookWinEvent(hook)
问题原因
- 回调函数被垃圾回收:原代码中
WinEventProc(focus_changed)是临时创建的对象,未被持久化引用,Python垃圾回收机制会在后续回收该对象,系统触发钩子事件时调用已回收的内存地址,导致程序崩溃终止。 - 函数参数类型未定义:直接调用
windll.user32.SetWinEventHook时,ctypes无法正确处理参数和返回值类型,可能引发栈不平衡,导致程序异常。
修复后的代码
import win32con import pythoncom import time from ctypes import wintypes, windll, WINFUNCTYPE # 显式定义API函数的参数和返回值类型,避免栈不平衡 windll.user32.SetWinEventHook.argtypes = [ wintypes.DWORD, wintypes.DWORD, wintypes.HMODULE, WINFUNCTYPE, wintypes.DWORD, wintypes.DWORD, wintypes.DWORD ] windll.user32.SetWinEventHook.restype = wintypes.HANDLE windll.user32.UnhookWinEvent.argtypes = [wintypes.HANDLE] windll.user32.UnhookWinEvent.restype = wintypes.BOOL # 定义WinEventProc回调类型 WinEventProc = WINFUNCTYPE( None, wintypes.HANDLE, wintypes.DWORD, wintypes.HWND, wintypes.LONG, wintypes.LONG, wintypes.DWORD, wintypes.DWORD ) # 焦点变更回调函数,过滤窗口级别的焦点事件 def focus_changed(hWinEventHook, event, hwnd, idObject, idChild, dwEventThread, dwmsEventTime): if idObject == win32con.OBJID_WINDOW and idChild == win32con.CHILDID_SELF: print("Focus changed event detected") # 主逻辑 if __name__ == "__main__": # 保留回调对象引用,防止被垃圾回收 hook_callback = WinEventProc(focus_changed) hook = windll.user32.SetWinEventHook( win32con.EVENT_SYSTEM_FOREGROUND, win32con.EVENT_SYSTEM_FOREGROUND, 0, hook_callback, 0, 0, win32con.WINEVENT_OUTOFCONTEXT ) if not hook: print(f"SetWinEventHook failed, error code: {windll.kernel32.GetLastError()}") print("Script is running...") try: while True: pythoncom.PumpWaitingMessages() time.sleep(0.01) # 降低CPU占用 except KeyboardInterrupt: print("Exiting...") finally: # 确保钩子被正确卸载 if hook: windll.user32.UnhookWinEvent(hook)
关键改动说明
- 保留回调引用:将
WinEventProc(focus_changed)赋值给hook_callback变量,阻止垃圾回收器回收回调对象,保证事件触发时能正常调用。 - 定义API类型:为
SetWinEventHook和UnhookWinEvent指定argtypes和restype,让ctypes正确处理参数传递,避免栈不平衡问题。 - 事件过滤:在回调中判断
idObject和idChild,只处理窗口级别的焦点变更,排除子控件的焦点事件干扰。 - 完善资源清理:使用
finally块确保程序退出时卸载钩子,避免资源泄漏。 - 降低CPU占用:在消息循环中添加短暂延迟,减少程序的CPU使用率。
内容的提问来源于stack exchange,提问作者David Hite
相关产品推荐
相关产品推荐

