您不能使用非静态类方法作为回调。非静态方法有一个隐藏的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);
}