python 中加载的 DLL 中的全局挂钩使 firefox 无响应,但前提是位数不匹配

Global hook in DLL loaded in python renders firefox unresponsive, but only if the bitness doesn't match

我正在编写一个程序来控制机器人样品更换器,它使用条形码扫描仪来检查加载了哪个样品。扫描仪被视为键盘,我希望能够捕获它的输入并防止它在其他程序中显示为击键。我使用 this solution,使用 ctypes 创建原始输入的一半,并使用 c++ 在 dll 中编写挂钩,因此它可以是全局的。它有效,但我注意到它使 Firefox 和 MobaXterm 没有响应。进一步调查,我发现 运行 64 位 python 仅适用于 64 位 Firefox,而 32 位 python 仅适用于 32 位 Firefox。我已经将它隔离到 dll 本身,程序的其余部分 运行 没有它不会导致任何问题。 dll 代码:

// dllmain.cpp : Defines the entry point for the DLL application.
#include "pch.h"
#include "myhook.h"

#pragma data_seg(".KBH")
HWND hWndServer = NULL;
#pragma data_seg()
#pragma comment(linker, "/section:.KBH,rws")

HINSTANCE hInstance;
UINT UWM_KBHOOK;
HHOOK hook;

static LRESULT CALLBACK msghook(int nCode, WPARAM wParam, LPARAM lParam);

BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                     )
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
        hInstance = (HINSTANCE)hModule;
        UWM_KBHOOK = RegisterWindowMessage(UWM_KBHOOK_MSG);
        return TRUE;
    case DLL_PROCESS_DETACH:
        if (hWndServer != NULL)
            clearMyHook(hWndServer);
        return TRUE;
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
        break;
    }
    return TRUE;
}

extern "C" KEYHOOKDLL_API BOOL WINAPI setMyHook(HWND hWnd) {
    if (hWndServer != NULL)
        return FALSE;

    hook = SetWindowsHookEx(
        WH_KEYBOARD,
        (HOOKPROC)msghook,
        hInstance,
        0
    );

    if (hook != NULL) {
        hWndServer = hWnd;
        return TRUE;
    }
    return FALSE;
}

extern "C" KEYHOOKDLL_API BOOL clearMyHook(HWND hWnd) {
    if (hWnd != hWndServer)
        return FALSE;
    BOOL unhooked = UnhookWindowsHookEx(hook);
    if (unhooked)
        hWndServer = NULL;
    return unhooked;
}

static LRESULT CALLBACK msghook(int nCode, WPARAM wParam, LPARAM lParam) {
    if (SendMessage(hWndServer, UWM_KBHOOK, wParam, lParam)) 
        return 1;
    return CallNextHookEx(hook, nCode, wParam, lParam);
}

extern "C" KEYHOOKDLL_API LPCSTR message_string() {
    return (LPCSTR)UWM_KBHOOK_MSG;
}

和头文件:

#pragma once
#include <Windows.h>
#include <tchar.h>

#ifdef KEYHOOKDLL_EXPORTS
#define KEYHOOKDLL_API __declspec(dllexport)
#else
#define KEYHOOKDLL_API __declspec(dllimport)
#endif

#define UWM_KBHOOK_MSG _T("UMW_KBHOOK-{B30856F0-D3DD-11d4-A00B-006067718D04}")

extern "C" KEYHOOKDLL_API BOOL WINAPI setMyHook(HWND hWnd);

extern "C" KEYHOOKDLL_API BOOL clearMyHook(HWND hWnd);

extern "C" KEYHOOKDLL_API LPCSTR message_string();

我不知道这里发生了什么。我猜当位数不匹配时,全局挂钩会以某种方式干扰 Firefox 中的挂钩,这是一个已知问题吗?我在这里做的任何事情可能会弄乱钩链吗?我注意到在 Firefox 浏览器中输入 window 出于某种原因会发出一条双重消息。

终于找到问题了。我没有在调用 dll 的 python 进程中发送消息。由于 windows 处理 32 位/64 位 dll 和挂钩的方式,仅当进程之间的位数不匹配时,泵送消息才会导致问题。如果其他人 运行 遇到类似问题,这里是旧的 python 循环:

from ctypes import *
from ctypes.wintypes import *

class KeyboardHookProc(Process):
    def __init__(self, hwnd, commandQueue=None, *args, **kwargs):
        super(KeyboardHookProc, self).__init__(*args, **kwargs)
        self.hwnd = hwnd
        if commandQueue is None:
            self.commandQueue = Queue()
        else:
            self.commandQueue = commandQueue

    def run(self):
        print("Starting keyhook dll")
        dll = WinDLL(dllPath)
        hwnd = HWND(self.hwnd)
        res = dll.setMyHook(hwnd)
        if res != 1:
            raise RuntimeError("Failed to set hook")
        while True:
            if not self.commandQueue.empty():
                command = self.commandQueue.get()
                if command == "QUIT":
                    break

固定版本:

from ctypes import *
from ctypes.wintypes import *

LPMSG = POINTER(MSG)

PeekMessage = windll.user32.PeekMessageW
PeekMessage.argtypes = (LPMSG, HWND, UINT, UINT, UINT)

TranslateMessage = windll.user32.TranslateMessage
TranslateMessage.argtypes = (LPMSG,)


DispatchMessage = windll.user32.DispatchMessageW
DispatchMessage.argtypes = (LPMSG,)

class KeyboardHookProc(Process):
    def __init__(self, hwnd, commandQueue=None, *args, **kwargs):
        super(KeyboardHookProc, self).__init__(*args, **kwargs)
        self.hwnd = hwnd
        if commandQueue is None:
            self.commandQueue = Queue()
        else:
            self.commandQueue = commandQueue

    def run(self):
        print("Starting keyhook dll")
        dll = WinDLL(dllPath)
        hwnd = HWND(self.hwnd)
        res = dll.setMyHook(hwnd)
        if res != 1:
            raise RuntimeError("Failed to set hook")
        msg = MSG()
        while True:
            if not self.commandQueue.empty():
                command = self.commandQueue.get()
                if command == "QUIT":
                    break
            PeekMessage(byref(msg), hwnd, 0, 0, PM_REMOVE)
            TranslateMessage(byref(msg))
            DispatchMessage(byref(msg))

即使您不对这三个函数执行任何操作,您也需要 运行 它们以确保消息可以沿着链传播。