使用头文件中定义的方法调用 SetWindowsHookEx

Call SetWindowsHookEx with method defined in header file

我正在尝试向 class 添加低级鼠标挂钩。我可以通过将这个函数放在我的 CPP 文件中来做到这一点:

LRESULT CALLBACK MouseHookProc(int nCode, WPARAM wParam, LPARAM lParam)
{
    //my hook code here
    return CallNextHookEx(0, nCode, wParam, lParam);
} 

然后,我在 class 构造函数中设置挂钩,如下所示:

HHOOK mousehook = SetWindowsHookEx(WH_MOUSE_LL, MouseHookProc, NULL, 0);

这可以很好地拦截鼠标事件,但是由于回调函数没有在我的 class 中定义,它无法访问我的任何 class 变量。

因此,我尝试在我的头文件中定义回调函数,如下所示:

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

在我的 CPP 文件中是这样的(TMainForm 是 class):

LRESULT CALLBACK TMainForm::MouseHookProc(int nCode, WPARAM wParam, LPARAM lParam)
{
     //my hook code here
     return CallNextHookEx(0, nCode, wParam, lParam);
}

但是,当我尝试这样编译时,出现以下错误:

[bcc32 Error] MainFormU.cpp(67): E2034 Cannot convert 'long (__stdcall * (_closure )(int,unsigned int,long))(int,unsigned int,long)' to 'long (__stdcall *)(int,unsigned int,long)'

[bcc32 Error] MainFormU.cpp(67): E2342 Type mismatch in parameter 'lpfn' (wanted 'long (__stdcall *)(int,unsigned int,long)', got 'void')

我到底做错了什么?该方法现在有何不同,因为我已将其作为我的一部分 TMainForm class?

您不能使用非静态 class 方法作为回调。非静态方法有一个隐藏的 this 参数,因此回调的签名与 SetWindowsHookEx() 期望的签名不匹配。即使编译器允许它(只能通过转换来完成),API 无论如何也无法解释 this 参数。

如果你想让回调成为 class 的成员(这样它就可以访问私有字段等),它必须声明为 static 以删除 this 参数,但是你将不得不在需要时使用表单的全局指针来访问它,例如:

class TMainForm : public TForm
{
private:
    HHOOK hMouseHook;
    static LRESULT CALLBACK MouseHookProc(int nCode, WPARAM wParam, LPARAM lParam);
    void MouseHook(int nCode, WPARAM wParam, LPARAM lParam);
public:
    __fastcall TMainForm(TComponent *Owner);
    __fastcall ~TMainForm();
};
extern TMainForm *MainForm;

__fastcall TMainForm::TMainForm(TComponent *Owner)
    : TForm(Owner)
{
    hMouseHook = SetWindowsHookEx(WH_MOUSE_LL, &MouseHookProc, NULL, 0);
}

__fastcall TMainForm::~TMainForm()
{
    if (hMouseHook)
        UnhookWindowsHookEx(hMouseHook);
}

LRESULT CALLBACK TMainForm::MouseHookProc(int nCode, WPARAM wParam, LPARAM lParam)
{
    MainForm->MouseHook(nCode, wParam, lParam);
    return CallNextHookEx(0, nCode, wParam, lParam);
}

void TMainForm::MouseHook(int nCode, WPARAM wParam, LPARAM lParam)
{
    // my hook code here
}

话虽如此,您应该考虑使用 Raw Input API instead of SetWindowsHookEx(). The LowLevelMouseProc documentation 甚至是这样说的:

Note Debug hooks cannot track this type of low level mouse hooks. If the application must use low level hooks, it should run the hooks on a dedicated thread that passes the work off to a worker thread and then immediately returns. In most cases where the application needs to use low level hooks, it should monitor raw input instead. This is because raw input can asynchronously monitor mouse and keyboard messages that are targeted for other threads more effectively than low level hooks can. For more information on raw input, see Raw Input.

使用原始输入,鼠标将 WM_INPUT 消息直接发送到您的 window。

如果您使用的是 VCL,您可以覆盖虚拟 WndProc() 方法来处理 WM_INPUT 消息,不需要静态方法:

class TMainForm : public TForm
{
protected:
    virtual void __fastcall CreateWnd();
    virtual void __fastcall WndProc(TMessage &Message);
};

void __fastcall TMainForm::CreateWnd()
{
    TForm::CreateWnd();

    RAWINPUTDEVICE Device = {0};
    Device.usUsagePage =  0x01;
    Device.usUsage = 0x02;
    Device.dwFlags = RIDEV_INPUTSINK;
    Device.hwndTarget = this->Handle;

    RegisterRawInputDevices(&Device, 1, sizeof(RAWINPUTDEVICE));
}

void __fastcall TMainForm::WndProc(TMessage &Message)
{
    if (Message.Msg == WM_INPUT)
    {
        HRAWINPUT hRawInput = (HRAWINPUT) Message.LParam;
        UINT size = 0;
        if (GetRawInputData(hRawInput, RID_INPUT, NULL, &size, sizeof(RAWINPUTHEADER)) == 0)
        {
            LPBYTE buf = new BYTE[size];

            if (GetRawInputData(hRawInput, RID_INPUT, buf, &size, sizeof(RAWINPUTHEADER)) != 0)
            {
                RAWINPUT *input = (RAWINPUT*) buf;
                // use input->data.mouse or input->data.hid as needed...
            }

            delete[] buf;
        }
    }

    TForm::WndProc(Message);
}

如果您使用的是 FireMonkey,则没有用于处理 window 消息的 WndProc() 方法(FireMonkey 根本不会向用户代码发送 window 消息)。但是,您可以子class FireMonkey 内部创建的window,这样您仍然可以收到WM_INPUT 消息。需要一个静态方法,但你不必依赖全局指针,Form对象可以作为subclassing:

的参数传递
class TMainForm : public TForm
{
private:
    static LRESULT CALLBACK SubclassProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR  uIdSubclass, DWORD_PTR dwRefData);
protected:
    virtual void __fastcall CreateHandle();
};

void __fastcall TMainForm::CreateHandle()
{
    TForm::CreateHandle();

    HWND hWnd = Platform::Win::WindowHandleToPlatform(this->Handle)->Wnd;

    SetWindowSubclass(hWnd, &SubclassProc, 1, (DWORD_PTR)this);

    RAWINPUTDEVICE Device = {0};
    Device.usUsagePage =  0x01;
    Device.usUsage = 0x02;
    Device.dwFlags = RIDEV_INPUTSINK;
    Device.hwndTarget = hWnd;

    RegisterRawInputDevices(&Device, 1, sizeof(RAWINPUTDEVICE));
}

LRESULT CALLBACK TMainForm::SubclassProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR  uIdSubclass, DWORD_PTR dwRefData)
{
    TMainForm *pThis = (TMainForm*) dwRefData;

    switch (uMsg)
    {
        case WM_INPUT:
        {
            // ...
            break;
        }

        case WM_NCDESTROY:
        {
            RemoveWindowSubclass(hWnd, &SubclassProc, uIdSubclass);
            break;
        }
    }

    return DefSubclassProc(hWnd, uMsg, wParam, lParam);
}

我遇到了同样的问题,我发现对于我的特殊情况,最好的方法是创建一个指向我的 class 的静态指针数组。然后在静态钩子方法中,我只是遍历我的 class 指针并调用它们的钩子函数。

kb_hook.h

typedef KBDLLHOOKSTRUCT khookstruct;
typedef LRESULT lresult;
typedef void (*h_func)(uint64_t key_message, khookstruct* kbdhook);
typedef std::vector<kb_hook*> h_array;

class kb_hook
{
public:
    kb_hook();
    virtual ~kb_hook();

    h_func hook_func;

private:
    static h_array hook_array;

    static lresult static_hook(int code, uint64_t key_message, khookstruct* kbdhook);
};

kb_hook.cpp

kb_hook::kb_hook() : hook_func(NULL)
{
    this->hook_array.push_back(this);
}

lresult kb_hook::static_hook(int code, uint64_t key_message, khookstruct* kbdhook)
{
    if(code == HC_ACTION)
    {
        for(auto it=kb_hook::hook_array.begin();it!=kb_hook::hook_array.end();it++)
        {
            if((*it)->hook_func) std::thread((*it)->hook_func, key_message, kbdhook).detach();
        }
    }

    return CallNextHookEx(NULL, code, key_message, reinterpret_cast<LPARAM>(kbdhook));
}

我知道这是一个老问题,但我只想投入我的两分钱。我希望这对某人有帮助。