python - 将键盘挂钩附加到特定窗口
问题描述
我是 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_callback
via 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()
问题:
GetProcAddress
当我指定导出的函数名称“keyboard_callback”时,不返回地址。我已经通过执行检查了导出的名称确实是“keyboard_callback”dumpbin /exports hookedProc.dll
。我不知道为什么它不起作用,所以我改用了序数(盲目地希望它确实指的是键盘钩子过程)。- 运行keyboard.py 会导致执行立即停止——尽管它应该触发消息循环。我很确定
hookId
不应该为零,所以我的SetWindowsHookExA
电话一定有问题,但我不确定是什么。
非常感谢您的洞察力/帮助。谢谢你。
解决方案
推荐阅读
- c# - ASP.Net MVC - String.Join()
- function - Julia - 在函数中更改方法定义
- ios - iOS SlideUp menu for phone numbers
- amazon-web-services - 运行反向代理的 Elastic Beanstalk 上的 TLS
- php - PHP / Laravel - update values of JSON object
- sql-server - 更改sqlserver服务帐户后的文件和文件夹权限
- android - React-native ListView“警告:无法调用setState”
- android - 添加新项目和 notifyDataSetChanged 后如何从 RecyclerView 获取最后一个项目视图?
- mongodb - 如何为文档流水线
- python - 如何将小部件的 id 从 kivy 发送和使用到 python