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

问题描述 投票:0回答:2

我试图在类中添加一个低级别的鼠标钩子。我可以通过将此函数放入我的CPP文件中来实现:

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

然后,我在类构造函数中设置了钩子,如下所示:

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

这适用于拦截鼠标事件,但由于回调函数未在我的类中定义,因此它无法访问任何类变量。

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

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

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

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类的一部分,因此该方法现在有何不同?

c++ windows winapi hook c++builder
2个回答
3
投票

您不能使用非静态类方法作为回调。非静态方法具有隐藏的this参数,因此回调的签名与SetWindowsHookEx()期望的签名不匹配。即使编译器允许它(只能通过强制转换完成)​​,API仍然无法考虑this参数。

如果你想让回调成为类的成员(所以它可以访问私有字段等),它必须被声明为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而不是SetWindowsHookEx()LowLevelMouseProc documentation甚至这样说:

注意调试挂钩无法跟踪此类低级别鼠标挂钩。如果应用程序必须使用低级别挂钩,它应该在专用线程上运行挂钩,该挂钩将工作传递给工作线程然后立即返回。在大多数应用程序需要使用低级别挂钩的情况下,它应该监视原始输入。这是因为原始输入可以异步监视与低级别挂钩相比更有效地针对其他线程的鼠标和键盘消息。有关原始输入的更多信息,请参阅Raw Input

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

如果您使用的是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,则没有用于处理窗口消息的WndProc()方法(FireMonkey根本不会将窗口消息分派给用户代码)。但是,您可以子类化FireMonkey在内部创建的窗口,这样您仍然可以收到WM_INPUT消息。需要静态方法,但您不必依赖全局指针,Form对象可以作为子类的参数传递:

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);
}

0
投票

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

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));
}

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

© www.soinside.com 2019 - 2024. All rights reserved.