Seb*_*ner 4 python winapi ctypes
我正在尝试使用 Python (3.4.3) 后台应用程序(在 Windows 7/8 中)全局跟踪鼠标。这涉及设置一个 WindowsHook,它应该向我返回该特定钩子的有效句柄 - 但我的句柄始终为 0。
仅跟踪鼠标位置非常容易GetCursorPos(作为替代方法GetCursorInfo):
from ctypes.wintypes import *
ppoint = ctypes.pointer(POINT())
ctypes.windll.user32.GetCursorPos(ppoint)
print('({}, {})'.format(ppoint[0].x, ppoint[0].y))
Run Code Online (Sandbox Code Playgroud)
仅跟踪位置也很方便GetMouseMovePointsEx,它跟踪最近的 64 个鼠标位置:
from ctypes.wintypes import *
# some additional types and structs
ULONG_PTR = ctypes.c_ulong
class MOUSEMOVEPOINT(ctypes.Structure):
_fields_ = [
("x", ctypes.c_int),
("y", ctypes.c_int),
("time", DWORD),
("dwExtraInfo", ULONG_PTR)
]
GMMP_USE_DISPLAY_POINTS = 1
# get initial tracking point
ppoint = ctypes.pointer(POINT())
ctypes.windll.user32.GetCursorPos(ppoint)
point = MOUSEMOVEPOINT(ppoint[0].x,ppoint[0].y)
# track last X points
number_mouse_points = 64
points = (MOUSEMOVEPOINT * number_mouse_points)()
ctypes.windll.user32.GetMouseMovePointsEx(ctypes.sizeof(MOUSEMOVEPOINT),
ctypes.pointer(point), ctypes.pointer(points), number_mouse_points,
GMMP_USE_DISPLAY_POINTS)
# print results
for point in points:
print('({}, {})'.format(point.x, point.y))
Run Code Online (Sandbox Code Playgroud)
但是,我还希望能够跟踪点击、拖动等。一个好的解决方案似乎是LowLevelMouseProc. (可能还有另一种方式有待探索:原始输入)
为了能够使用 LowLevelMouseProc,文档告诉我们使用SetWindowsHookEx(W/A),它也在各种(C++)教程(C#) 以及一些有趣的项目(也是 C#)中有所介绍。
文档在 C++ 中定义它如下:
HHOOK WINAPI SetWindowsHookEx(
_In_ int idHook,
_In_ HOOKPROC lpfn,
_In_ HINSTANCE hMod,
_In_ DWORD dwThreadId
);
Run Code Online (Sandbox Code Playgroud)
以下应该是我在 python 中的正确值:
idHook: WH_MOUSE_LL = 14hMod:(HINSTANCE(0)基本上是一个空指针)dwThreadId: ctypes.windll.kernel32.GetCurrentThreadId()对于lpfn我需要一些回调实现LowLevelMouseProc,这里LLMouseProc:
def _LLMouseProc (nCode, wParam, lParam):
return ctypes.windll.user32.CallNextHookEx(None, nCode, wParam, lParam)
LLMouseProcCB = ctypes.CFUNCTYPE(LRESULT, ctypes.c_int, WPARAM, LPARAM)
LLMouseProc = LLMouseProcCB(_LLMouseProc)
Run Code Online (Sandbox Code Playgroud)
把它们放在一起,我希望这能奏效:
from ctypes.wintypes import *
LONG_PTR = ctypes.c_long
LRESULT = LONG_PTR
WH_MOUSE_LL = 14
def _LLMouseProc(nCode, wParam, lParam):
print("_LLMouseProc({!s}, {!s}, {!s})".format(nCode, wParam, lParam))
return ctypes.windll.user32.CallNextHookEx(None, nCode, wParam, lParam)
LLMouseProcCB = ctypes.CFUNCTYPE(LRESULT, ctypes.c_int, WPARAM, LPARAM)
LLMouseProc = LLMouseProcCB(_LLMouseProc)
threadId = ctypes.windll.kernel32.GetCurrentThreadId()
# register callback as hook
print('hook = SetWindowsHookExW({!s}, {!s}, {!s}, {!s})'.format(WH_MOUSE_LL, LLMouseProc,
HINSTANCE(0), threadId))
hook = ctypes.windll.user32.SetWindowsHookExW(WH_MOUSE_LL, LLMouseProc,
HINSTANCE(0), threadId)
print('Hook: {}'.format(hook))
import time
try:
while True:
time.sleep(0.2)
except KeyboardInterrupt:
pass
Run Code Online (Sandbox Code Playgroud)
但输出显示hook == 0:
hook = SetWindowsHookExW(14, <CFunctionType object at 0x026183F0>, c_void_p(None), 5700)
Hook: 0
Run Code Online (Sandbox Code Playgroud)
我认为可能是回调函数的最后一个参数 namelParam并不像 LPARAM(即ctypes.c_long)那样正确,因为我认为真正期望的是指向此结构的指针:
class MSLLHOOKSTRUCT(ctypes.Structure):
_fields_ = [
("pt", POINT),
("mouseData", DWORD),
("flags", DWORD),
("time", DWORD),
("dwExtraInfo", ULONG_PTR)
]
Run Code Online (Sandbox Code Playgroud)
但是把签名改成LLMouseProcCB = ctypes.CFUNCTYPE(LRESULT, ctypes.c_int, WPARAM, ctypes.POINTER(MSLLHOOKSTRUCT))并不能解决问题,我还是有0的钩子。
这是跟踪鼠标的正确方法吗?我需要更改什么才能在 Windows 中正确注册钩子?
如果你检查GetLastError你应该发现错误是ERROR_GLOBAL_ONLY_HOOK(1429),即WH_MOUSE_LL需要设置一个全局钩子。该dwThreadId参数用于设置本地钩子。幸运的WH_MOUSE_LL是,全局钩子回调可以是钩子过程中的任何函数,而不是必须在 DLL 中定义,即hMod可以是NULL.
如果您需要支持 32 位 Windows,请注意调用约定。32 位 Windows API 通常需要stdcall(被调用者堆栈清理),因此需要通过WINFUNCTYPE而不是CFUNCTYPE.
另一个问题是您的代码缺少消息循环。设置钩子的线程需要运行一个消息循环,以便将消息分派给回调。在下面的示例中,我为此消息循环使用了一个专用线程。该线程设置挂钩并进入一个循环,该循环仅在出现错误或WM_QUIT发布消息时中断。当用户输入时Ctrl+C,我调用PostThreadMessageW优雅退出。
from ctypes import *
from ctypes.wintypes import *
user32 = WinDLL('user32', use_last_error=True)
HC_ACTION = 0
WH_MOUSE_LL = 14
WM_QUIT = 0x0012
WM_MOUSEMOVE = 0x0200
WM_LBUTTONDOWN = 0x0201
WM_LBUTTONUP = 0x0202
WM_RBUTTONDOWN = 0x0204
WM_RBUTTONUP = 0x0205
WM_MBUTTONDOWN = 0x0207
WM_MBUTTONUP = 0x0208
WM_MOUSEWHEEL = 0x020A
WM_MOUSEHWHEEL = 0x020E
MSG_TEXT = {WM_MOUSEMOVE: 'WM_MOUSEMOVE',
WM_LBUTTONDOWN: 'WM_LBUTTONDOWN',
WM_LBUTTONUP: 'WM_LBUTTONUP',
WM_RBUTTONDOWN: 'WM_RBUTTONDOWN',
WM_RBUTTONUP: 'WM_RBUTTONUP',
WM_MBUTTONDOWN: 'WM_MBUTTONDOWN',
WM_MBUTTONUP: 'WM_MBUTTONUP',
WM_MOUSEWHEEL: 'WM_MOUSEWHEEL',
WM_MOUSEHWHEEL: 'WM_MOUSEHWHEEL'}
ULONG_PTR = WPARAM
LRESULT = LPARAM
LPMSG = POINTER(MSG)
HOOKPROC = WINFUNCTYPE(LRESULT, c_int, WPARAM, LPARAM)
LowLevelMouseProc = HOOKPROC
class MSLLHOOKSTRUCT(Structure):
_fields_ = (('pt', POINT),
('mouseData', DWORD),
('flags', DWORD),
('time', DWORD),
('dwExtraInfo', ULONG_PTR))
LPMSLLHOOKSTRUCT = POINTER(MSLLHOOKSTRUCT)
def errcheck_bool(result, func, args):
if not result:
raise WinError(get_last_error())
return args
user32.SetWindowsHookExW.errcheck = errcheck_bool
user32.SetWindowsHookExW.restype = HHOOK
user32.SetWindowsHookExW.argtypes = (c_int, # _In_ idHook
HOOKPROC, # _In_ lpfn
HINSTANCE, # _In_ hMod
DWORD) # _In_ dwThreadId
user32.CallNextHookEx.restype = LRESULT
user32.CallNextHookEx.argtypes = (HHOOK, # _In_opt_ hhk
c_int, # _In_ nCode
WPARAM, # _In_ wParam
LPARAM) # _In_ lParam
user32.GetMessageW.argtypes = (LPMSG, # _Out_ lpMsg
HWND, # _In_opt_ hWnd
UINT, # _In_ wMsgFilterMin
UINT) # _In_ wMsgFilterMax
user32.TranslateMessage.argtypes = (LPMSG,)
user32.DispatchMessageW.argtypes = (LPMSG,)
@LowLevelMouseProc
def LLMouseProc(nCode, wParam, lParam):
msg = cast(lParam, LPMSLLHOOKSTRUCT)[0]
if nCode == HC_ACTION:
msgid = MSG_TEXT.get(wParam, str(wParam))
msg = ((msg.pt.x, msg.pt.y),
msg.mouseData, msg.flags,
msg.time, msg.dwExtraInfo)
print('{:15s}: {}'.format(msgid, msg))
return user32.CallNextHookEx(None, nCode, wParam, lParam)
def mouse_msg_loop():
hHook = user32.SetWindowsHookExW(WH_MOUSE_LL, LLMouseProc, None, 0)
msg = MSG()
while True:
bRet = user32.GetMessageW(byref(msg), None, 0, 0)
if not bRet:
break
if bRet == -1:
raise WinError(get_last_error())
user32.TranslateMessage(byref(msg))
user32.DispatchMessageW(byref(msg))
if __name__ == '__main__':
import time
import threading
t = threading.Thread(target=mouse_msg_loop)
t.start()
while True:
try:
time.sleep(1)
except KeyboardInterrupt:
user32.PostThreadMessageW(t.ident, WM_QUIT, 0, 0)
break
Run Code Online (Sandbox Code Playgroud)