【问题标题】:Setting up a WindowsHook in Python (ctypes, Windows API)在 Python 中设置 WindowsHook(ctypes、Windows API)
【发布时间】:2015-07-13 08:58:02
【问题描述】:

我正在尝试使用 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))

也方便只跟踪位置是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))

但是,我还希望能够跟踪点击、拖动等。 一个好的解决方案似乎是LowLevelMouseProc。 (可能还有另一种方式有待探索:Raw Input

为了能够使用 LowLevelMouseProc,文档告诉我们使用 SetWindowsHookEx(W/A)various (C++) tutorials (C#) 以及一些有趣的 projects (也是 C#) .

文档在 C++ 中定义如下:

HHOOK WINAPI SetWindowsHookEx(
  _In_ int       idHook,
  _In_ HOOKPROC  lpfn,
  _In_ HINSTANCE hMod,
  _In_ DWORD     dwThreadId
);

以下应该是我在 python 中的正确值:

  • idHook: WH_MOUSE_LL = 14
  • hMod: 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)

把它们放在一起,我希望这会起作用:

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

但输出显示hook == 0

hook = SetWindowsHookExW(14, <CFunctionType object at 0x026183F0>, c_void_p(None), 5700)
Hook: 0

我认为也许回调函数的最后一个参数名称 lParam 与 LPARAM(即 ctypes.c_long)并不完全正确,因为我认为真正预期的是指向此结构的指针:

class MSLLHOOKSTRUCT(ctypes.Structure):
    _fields_ = [
        ("pt", POINT),
        ("mouseData", DWORD),
        ("flags", DWORD),
        ("time", DWORD),
        ("dwExtraInfo", ULONG_PTR)
    ]

但是把签名改成LLMouseProcCB = ctypes.CFUNCTYPE(LRESULT, ctypes.c_int, WPARAM, ctypes.POINTER(MSLLHOOKSTRUCT))并没有解决问题,我还有一个0的钩子。

这是跟踪鼠标的正确方法吗?我需要更改哪些内容才能在 Windows 中正确注册挂钩?

【问题讨论】:

    标签: python winapi ctypes


    【解决方案1】:

    如果您检查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
    

    【讨论】:

    • 我什至无法说出我从这个答案中学到了多少。很多关于 WinAPI 的有趣的东西,我不得不阅读 python 装饰器。我不完全理解的是为什么 WH_MOUSE_LL 如此特别,因为您不需要将其订阅到 DLL - 我从msdn.microsoft.com/en-us/library/windows/desktop/… 中了解到,所有人都可以将它们挂接到当前线程或 DLL。在我看来,唯一的区别是某些钩子只能在全球范围内工作,就他们获得的事件而言?
    • LowLevelMouseProc 文档指出,挂钩是在“安装它的线程的上下文”中通过“向线程发送消息”来调用的。在其他情况下,如果不使用或无法使用线程间消息传递,则需要预先加载具有钩子过程的 DLL(除非无法加载,例如 32 位 DLL 可以'不要在 64 位进程中加载​​;它的工作原理在 SetWindowsHookEx 文档中讨论)。
    • 我们如何传递我们已经在 C Dll 中获得的 hookproc? stackoverflow.com/questions/57047858/…
    【解决方案2】:

    已接受答案的简化版本

    注意:先pip install pywin32

    # Created by BaiJiFeiLong@gmail.com at 2022/2/10 22:27
    
    from ctypes import WINFUNCTYPE, c_int, Structure, cast, POINTER, windll
    from ctypes.wintypes import LPARAM, WPARAM, DWORD, PULONG, LONG
    
    import win32con
    import win32gui
    
    
    def genStruct(name="Structure", **kwargs):
        return type(name, (Structure,), dict(
            _fields_=list(kwargs.items()),
            __str__=lambda self: "%s(%s)" % (name, ",".join("%s=%s" % (k, getattr(self, k)) for k in kwargs))
        ))
    
    
    @WINFUNCTYPE(LPARAM, c_int, WPARAM, LPARAM)
    def hookProc(nCode, wParam, lParam):
        msg = cast(lParam, POINTER(HookStruct))[0]
        print(msgDict[wParam], msg)
        return windll.user32.CallNextHookEx(None, nCode, WPARAM(wParam), LPARAM(lParam))
    
    
    HookStruct = genStruct(
        "Hook", pt=genStruct("Point", x=LONG, y=LONG), mouseData=DWORD, flags=DWORD, time=DWORD, dwExtraInfo=PULONG)
    msgDict = {v: k for k, v in win32con.__dict__.items() if k.startswith("WM_")}
    windll.user32.SetWindowsHookExW(win32con.WH_MOUSE_LL, hookProc, None, 0)
    win32gui.PumpMessages()
    

    样本输出

    WM_MOUSEMOVE Hook(pt=Point(x=50,y=702),mouseData=0,flags=0,time=343134468,dwExtraInfo=<ctypes.wintypes.LP_c_ulong object at 0x000001A466CDF8C8>)
    WM_MOUSEMOVE Hook(pt=Point(x=49,y=704),mouseData=0,flags=0,time=343134484,dwExtraInfo=<ctypes.wintypes.LP_c_ulong object at 0x000001A466CDF8C8>)
    WM_MOUSEMOVE Hook(pt=Point(x=49,y=705),mouseData=0,flags=0,time=343134484,dwExtraInfo=<ctypes.wintypes.LP_c_ulong object at 0x000001A466CDF8C8>)
    WM_MOUSEMOVE Hook(pt=Point(x=49,y=705),mouseData=0,flags=0,time=343134500,dwExtraInfo=<ctypes.wintypes.LP_c_ulong object at 0x000001A466CDF8C8>)
    WM_MOUSEMOVE Hook(pt=Point(x=49,y=706),mouseData=0,flags=0,time=343134500,dwExtraInfo=<ctypes.wintypes.LP_c_ulong object at 0x000001A466CDF8C8>)
    WM_MOUSEMOVE Hook(pt=Point(x=48,y=707),mouseData=0,flags=0,time=343134515,dwExtraInfo=<ctypes.wintypes.LP_c_ulong object at 0x000001A466CDF8C8>)
    WM_LBUTTONDOWN Hook(pt=Point(x=48,y=707),mouseData=0,flags=0,time=343134593,dwExtraInfo=<ctypes.wintypes.LP_c_ulong object at 0x000001A466CDF8C8>)
    WM_LBUTTONUP Hook(pt=Point(x=48,y=707),mouseData=0,flags=0,time=343134671,dwExtraInfo=<ctypes.wintypes.LP_c_ulong object at 0x000001A466CDF8C8>)
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-01-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-02-07
      • 1970-01-01
      • 2010-10-01
      相关资源
      最近更新 更多