如何使windll.user32.SetWinEventHook工作?

How do I make windll.user32.SetWinEventHook work?

提问人:David Hite 提问时间:10/9/2023 最后编辑:Mark TolonenDavid Hite 更新时间:10/29/2023 访问量:89

问:

我正在尝试在 windows 11 python 3.11 中使用以下程序挂钩焦点更改事件。

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)

当我启动记事本时,我希望看到控制台上打印“检测到焦点更改事件”。

当我启动上面的程序时,它会打印“脚本正在运行...”。

当我启动记事本时,上面的程序会静默终止,而不会打印焦点更改事件消息或任何其他消息。

python windows 事件 挂钩 ctypes

评论


答:

0赞 Mark Tolonen 10/29/2023 #1

主要问题是用作 的参数。这是一个对象,其引用计数为零,并在调用后立即释放。来自 ctypes 文档:WinEventProc(focus_changed)SetWinEventHook

注意:请确保保留对(WINFUNCTYPE())对象的引用,只要它们是在 C 代码中使用的。ctypes 不会,如果你不这样做,它们可能会被垃圾回收,在进行回调时导致程序崩溃。CFUNCTYPE()

进行永久引用的一种方法是简单地用原型装饰回调函数。

工作代码如下。另请注意,最好在函数上显式声明参数类型,以便更好地进行类型和错误检查。例如,假定非指针参数和所有返回值为 32 位整数。句柄是整数,但为 64 位。如果句柄足够大,则该值将被截断,除非将参数或返回值声明为句柄。ctypesctypes

import win32con
import pythoncom
import ctypes as ct
import ctypes.wintypes as w

WinEventProc = ct.WINFUNCTYPE(None, w.HANDLE, w.DWORD, w.HWND, w.LONG, w.LONG, w.DWORD, w.DWORD)

@WinEventProc  # Decorate the callback for a permanent reference
def focus_changed(hWinEventHook, event, hwnd, idObject, idChild, dwEventThread, dwmsEventTime):
    print('Focus changed event detected')

# Failure check helper for ctypes functinos
def fail_check(result, func, args):
    if not result:
        raise ct.WinError(ct.get_last_error())
    return result

# Good habit to explicitly declare arguments and result type of all functions
# used by ctypes for better type/error checking.
# "errcheck" suport will throw an exception with Win32 failure info if the function fails.
u32 = ct.WinDLL('user32', use_last_error=True)
u32.SetWinEventHook.argtypes = w.DWORD, w.DWORD, w.HMODULE, WinEventProc, w.DWORD, w.DWORD, w.DWORD
u32.SetWinEventHook.restype = w.HANDLE
u32.SetWinEventHook.errcheck = fail_check
u32.UnhookWinEvent.argtypes = w.HANDLE,
u32.UnhookWinEvent.restype = w.BOOL
u32.UnhookWinEvent.errcheck = fail_check

hook = u32.SetWinEventHook(
    win32con.EVENT_SYSTEM_FOREGROUND, win32con.EVENT_SYSTEM_FOREGROUND, 0,
    focus_changed, # changed from an object that immediately goes out of scope after hook call
    0, 0, win32con.WINEVENT_OUTOFCONTEXT)

print('Script is running...')
try:
    while True:
        pythoncom.PumpWaitingMessages()
except KeyboardInterrupt:
    print('Exiting...')
u32.UnhookWinEvent(hook)