首页 > 解决方案 > 将键盘挂钩附加到特定窗口

问题描述

我是 Windows API 和 DLL 的新手。我正在使用 Python 3.6.4 32 位和 pkgs cffi、pywin32。

我想将键盘挂钩附加到特定的正在运行的应用程序(例如记事本)并打印出按下的键码,直到我停止监视程序。

我注意到微软文档说对于SetWindowsHookExA函数,dwThreadId可以用来将钩子与特定线程相关联(但钩子过程必须包含在 DLL 中)。

所以,我已经通过cffi模块将我的钩子程序从 python 转换为 DLL。

hookProc.py

ffibuilder.embedding_api("""
    LRESULT keyboard_callback(int, WPARAM, LPARAM);
""")

ffibuilder.set_source("DLLs.hook_proc",
"""
    #include <Windows.h>
""")

ffibuilder.embedding_init_code("""
    from hook_proc import ffi
    from ctypes import *
    import sys

    @ffi.def_extern()
    def keyboard_callback(nCode, wParam, lParam):
        print(nCode, wParam, lParam[0])
        if (lParam[0] == 162):
            sys.exit(0)
        # First arg is ignored according to https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-callnexthookex
        return windll.user32.CallNextHookEx(0, nCode, wParam, lParam)
""")

ffibuilder.compile(target="hookedProc.*", verbose=True)

运行它会产生hookProc.dll. 我得到一个 DLL 句柄 viaLoadLibraryW和导出函数的地址keyboard_callbackvia GetProcAddress。我SetWindowsHookExA通过GetWindowThreadProcessId.

键盘.py

import win32gui;
import atexit;
import ctypes
from ctypes import *;
from ctypes import wintypes;
from typing import Union;
from ctypes.wintypes import DWORD

WH_KEYBOARD = c_int(2)
LEFT_CTRL = 162
WINDOW_TITLE = "Untitled - Notepad"

windll.user32.SetWindowsHookExA.argtypes = (c_int, wintypes.HANDLE, wintypes.HMODULE, wintypes.DWORD)

class Window:
    def __init__(self, hwnd, x, y, width, height):
        self.hwnd = hwnd
        self.x = x
        self.y = y
        self.width = width
        self.height = height
    def get_thread_id(self):
        threadId = windll.user32.GetWindowThreadProcessId(self.hwnd, None)
        return threadId

class WindowMngr:
    def __init__(self) -> None:
        pass

    @staticmethod
    def get_window(title: str) -> Union[Window, None]:
        hwnd: str = win32gui.FindWindow(None, title)
        if not hwnd is None:
            left, top, right, bottom = win32gui.GetWindowRect(hwnd)
            return Window(hwnd, left, top, right - left, bottom - top)
        return None

# https://docs.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-kbdllhookstruct?redirectedfrom=MSDN
class KBDLLHOOKSTRUCT(ctypes.Structure):
    __fields__ = [
                    ("vkCode", DWORD),
                    ("scanCode", DWORD),
                    ("flags", DWORD),
                    ("time", DWORD),
                    ("dwExtraInfo", POINTER(wintypes.ULONG)),
                ]

class KeyboardMngr:
    def __init__(self) -> None:
        self.hookId = None

    def register_hook(self) -> bool:

        tid = DWORD(WindowMngr.get_window(WINDOW_TITLE).get_thread_id())
        print(f"Window '{WINDOW_TITLE}' thread id = {tid}")

        dllHandle = windll.kernel32.LoadLibraryW("DLLs/hookedProc")
        print("DLL Handle @ ", dllHandle)
        hkProcPtr = windll.kernel32.GetProcAddress(dllHandle, 2)
        print("Hook proc  @ ", hkProcPtr)

        self.hookId = windll.user32.SetWindowsHookExA(
            WH_KEYBOARD,
            hkProcPtr,
            dllHandle,
            tid
        )

        if not self.hookId:
            return False

        # Register to remove the hook when the interpreter exits.
        atexit.register(windll.user32.UnhookWindowsHookEx, self.hookId)
        print("Hook Id is ", self.hookId)

        while True:
            msg = windll.user32.GetMessageW(None, 0, 0,0)
            windll.user32.TranslateMessage(byref(msg))
            windll.user32.DispatchMessageW(byref(msg))

keyboardMngr = KeyboardMngr()

def main():
    keyboardMngr.register_hook()

if __name__ == "__main__":
    main()

问题:

非常感谢您的洞察力/帮助。谢谢你。

标签: pythonwindowswinapi

解决方案


推荐阅读