我正在尝试在 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)
当我启动记事本时,我希望看到“检测到焦点更改事件”打印在控制台上。
当我启动上面的程序时,它会打印“Script is running...”。
当我启动记事本时,上面的程序会静默终止,而不打印焦点更改事件消息或任何其他消息。
主要问题是使用
WinEventProc(focus_changed)
作为 SetWinEventHook
的参数。这是一个引用计数为零并在调用后立即释放的对象。来自 ctypes
文档:
注意:只要在 C 代码中使用
(和CFUNCTYPE()
)对象,请确保保留它们的引用。 ctypes 不会,如果您不这样做,它们可能会被垃圾收集,从而在进行回调时使您的程序崩溃。WINFUNCTYPE()
进行永久引用的一种方法是简单地用原型装饰回调函数。
下面的工作代码。另请注意,在 ctypes 函数上显式声明参数类型是一种很好的做法,以便更好地进行类型和错误检查。
import win32con
import pythoncom
import ctypes as ct
import ctypes.wintypes as w
# WinEventProc and GUITHREADINFO
WinEventProc = ct.WINFUNCTYPE(None, w.HANDLE, w.DWORD, w.HWND, w.LONG, w.LONG, w.DWORD, w.DWORD)
# Decorate the callback for a permanent reference
@WinEventProc
def focus_changed(hWinEventHook, event, hwnd, idObject, idChild, dwEventThread, dwmsEventTime):
print("Focus changed event detected")
# Good habit to explicitly declare arguments and result type of all functions
# used by ctypes for better type/error checking.
u32 = ct.WinDLL('user32')
u32.SetWinEventHook.argtypes = w.DWORD, w.DWORD, w.HMODULE, WinEventProc, w.DWORD, w.DWORD, w.DWORD
u32.SetWinEventHook.restype = w.HANDLE
u32.UnhookWinEvent.argtypes = w.HANDLE,
u32.UnhookWinEvent.restype = w.BOOL
# Main code
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
)
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
u32.UnhookWinEvent(hook)