Вызовите SetWindowsHookEx с методом, определенным в заголовочном файле

Я пытаюсь добавить низкоуровневый хук мыши к классу. Я могу сделать это, поместив эту функцию в мой файл 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 учебный класс?

2 ответа

Решение

Вы не можете использовать нестатические методы класса в качестве обратного вызова. Нестатические методы имеют скрытый 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 сообщение. Статический метод необходим, но вам не нужно полагаться на глобальный указатель, объект For m может быть передан в качестве параметра подкласса:

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

Я знаю, что это старый вопрос, но я просто хотел добавить свои два цента. Надеюсь, это кому-нибудь пригодится.

Другие вопросы по тегам