首页 > 解决方案 > 必须在与消息泵相同的线程上注册 Windows 挂钩吗?

问题描述

我正在尝试使用 Windows Hooks 在我的应用程序发送自己的 gui 事件时拦截和阻止击键。

我想出了以下清单:

import pythoncom
import pyHook
import threading
import time


def on_keyboard_event(event):
    print 'MessageName:',event.MessageName
    print 'Message:',event.Message
    print 'Time:',event.Time
    print 'Window:',event.Window
    print 'WindowName:',event.WindowName
    print 'Ascii:', event.Ascii, chr(event.Ascii)
    print 'Key:', event.Key
    print 'KeyID:', event.KeyID
    print 'ScanCode:', event.ScanCode
    print 'Extended:', event.Extended
    print 'Injected:', event.Injected
    print 'Alt', event.Alt
    print 'Transition', event.Transition
    print '---'
    return False


class WindowsHooksWrapper:
    def __init__(self):
        self.started = False
        self.thread = threading.Thread(target=self.thread_proc)
        self.hook_manager = pyHook.HookManager()

    def start(self):
        if self.started:
            self.stop()

        # Register hook
        self.hook_manager.KeyDown = on_keyboard_event
        self.hook_manager.KeyUp = on_keyboard_event
        self.hook_manager.HookKeyboard()

        # Start the windows message pump
        self.started = True
        self.thread.start()

    def stop(self):
        if not self.started:
            return

        self.started = False
        self.thread.join()

        self.hook_manager.UnhookKeyboard()

    def thread_proc(self):
        print "Thread started"
        while self.started:
            pythoncom.PumpWaitingMessages()

        print "Thread exiting..."


class WindowsHooksWrapper2:
    def __init__(self):
        self.started = False
        self.thread = threading.Thread(target=self.thread_proc)

    def start(self):
        if self.started:
            self.stop()

        self.started = True
        self.thread.start()

    def stop(self):
        if not self.started:
            return

        self.started = False
        self.thread.join()

    def thread_proc(self):
        print "Thread started"

        # Evidently, the hook must be registered on the same thread with the windows msg pump or
        #     it will not work and no indication of error is seen
        # Also note that for exception safety, when the hook manager goes out of scope, it
        #     unregisters all outstanding hooks
        hook_manager = pyHook.HookManager()
        hook_manager.KeyDown = on_keyboard_event
        hook_manager.KeyUp = on_keyboard_event
        hook_manager.HookKeyboard()

        while self.started:
            pythoncom.PumpWaitingMessages()

        print "Thread exiting..."
        self.hook_manager.UnhookKeyboard()


def main():
    # hook_manager = pyHook.HookManager()
    # hook_manager.KeyDown = on_keyboard_event
    # hook_manager.KeyUp = on_keyboard_event
    # hook_manager.HookKeyboard()
    # pythoncom.PumpMessages()

    hook_wrapper = WindowsHooksWrapper2()
    hook_wrapper.start()
    time.sleep(30)
    hook_wrapper.stop()


if __name__ == "__main__":
    main()

main 中注释掉的部分来自 pyhook wiki 教程,它工作正常。

然后我尝试将其集成到一个类中,即“WindowsHooksWrapper”类。如果我使用了那个类,它就不起作用,键盘消息会传递到它们的预期目标。

凭直觉,然后我尝试了“WindowsHooksWrapper2”,在那里我将钩子的注册移动到与消息泵相同的线程。现在可以了。

我的预感是否正确,注册要求与泵在同一线程上?如果是这样,为什么?

请注意,我感觉这是 Windows 32 API 的要求,而不是 python 或 pyhook 库本身的要求,因为我是在 C++ 中完成的,并且直接使用“SetWindowsHook”得到了相同的结果。

标签: python-2.7winapipyhook

解决方案


您已经创建了一个线程范围挂钩。

这些钩子事件与特定线程或与调用线程相同的桌面中的所有线程相关联。

pythoncom.PumpWaitingMessages()在 Python 中和GetMessage/PeekMessage在 Win32 中是从“特定线程或与调用线程在同一桌面中的所有线程”获取消息的方法。

要创建一个全局挂钩,为了让所有进程都可以访问您的键盘挂钩,必须将它放在一个 DLL 中,然后将其加载到每个进程的地址空间中。有关如何制作全局键盘挂钩的详细信息,请参阅此答案。


推荐阅读