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

Call SetWindowsHookEx with method defined in header file

本文关键字:方法 调用 SetWindowsHookEx 定义 文件      更新时间:2023-10-16

我正在尝试添加一个低级鼠标钩子到一个类。我可以把这个函数放在我的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课的一部分以来,现在的方法有什么不同?

不能使用非静态类方法作为回调。非静态方法有一个隐藏的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文档甚至这样说:

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

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

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

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));
}
我知道这是一个老问题,但我只是想提出我的观点。我希望这对大家有帮助。