You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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)

问题原因

  1. 回调函数被垃圾回收:原代码中WinEventProc(focus_changed)是临时创建的对象,未被持久化引用,Python垃圾回收机制会在后续回收该对象,系统触发钩子事件时调用已回收的内存地址,导致程序崩溃终止。
  2. 函数参数类型未定义:直接调用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.09 01:40:18