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