0

I am attempting to add a low level mouse hook to a class. I am able to do so by placing this function in my CPP file:

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

Then, I set up the hook in the class constructor like so:

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

This works fine for intercepting mouse events, however since the callback function is not defined in my class, it does not have access to any of my class variables.

Therefore, I tried defining the callback function in my header file like so:

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

and in my CPP file like this (TMainForm being the class):

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

However, when I attempt to compile like this, I get the following errors:

[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')

What exactly am I doing wrong here? How is the method now different since I have made it a part of my TMainForm class?

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
James Hogle
  • 3,010
  • 3
  • 22
  • 46
  • 2
    You can't pass a regular member function as a callback. According to [this answer](http://stackoverflow.com/a/8184231/886887) Visual Studio does allow you to pass a static member function, but it isn't legal C++. Instead, use a plain function (as in your first example) and have it call your class member function. (I assume the class in question is a singleton? If not, you'll have to have some other way of identifying the correct instance.) – Harry Johnston Oct 08 '15 at 20:01
  • 1
    Member functions have a hidden "this" parameter which Win32 APIs that use callbacks don't know about. – Jonathan Potter Oct 08 '15 at 20:04
  • @HarryJohnston That isn't what that answer says. A static member function is just a regular function and can validly be used here. A non-static member function is a very different beast. – Alan Stokes Oct 08 '15 at 21:51
  • 1
    Possible duplicate of [How can I pass a class member function as a callback?](http://stackoverflow.com/questions/400257/how-can-i-pass-a-class-member-function-as-a-callback) – Alan Stokes Oct 08 '15 at 21:52
  • 1
    You could make a static instance of your class in the hook function and use this instance to call the method in the class. – fassl Oct 08 '15 at 21:54
  • @AlanStokes: according to the comments in the answer I linked to, a static member function can't be declared as `extern "C"`. So it will have C++ linkage, which means it can't legally be called from C code. Is that not true? – Harry Johnston Oct 08 '15 at 21:58
  • @HarryJohnston Linkage is about finding the name. Here a pointer is being passed so linkage is irrelevant. C can call C++ functions and vice versa as long as the ABI supports it which it certainly does on Windows. – Alan Stokes Oct 08 '15 at 22:06
  • @AlanStokes: AFAIK, there *isn't* a standard C++ ABI on Windows. If I understand correctly, Visual Studio happens to use the C ABI for static member functions, but that isn't required of other compilers, either by the C++ standard or by any Windows standard that I'm aware of. (The C++ standard section 5.2.2 on function pointers says "Calling a function through an expression whose function type has a language linkage that is different from the language linkage of the function type of the called function’s definition is undefined".) – Harry Johnston Oct 08 '15 at 22:27
  • @AlanStokes: on second thoughts, as per the discussion under Remy's answer, the CALLBACK qualifier should either ensure that the correct calling standard is used, or if that is not possible, that an error will be generated at build time. So technically legal C++ or not, it should still be safe. – Harry Johnston Oct 09 '15 at 02:48

2 Answers2

5

You cannot use a non-static class methods as the callback. Non-static methods have a hidden this parameter, thus the signature of the callback does not match the signature that SetWindowsHookEx() is expecting. Even if the compiler allowed it (which can only be done with casting), the API would not be able to account for the this parameter anyway.

If you want to make the callback be a member of the class (so it can access private fields and such), it has to be declared as static to remove the this parameter, but then you will have to use the form's global pointer to reach it when needed, eg:

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
}

With that said, you should consider using the Raw Input API instead of SetWindowsHookEx(). The LowLevelMouseProc documentation even says so:

Note Debug hooks cannot track this type of low level mouse hooks. If the application must use low level hooks, it should run the hooks on a dedicated thread that passes the work off to a worker thread and then immediately returns. In most cases where the application needs to use low level hooks, it should monitor raw input instead. This is because raw input can asynchronously monitor mouse and keyboard messages that are targeted for other threads more effectively than low level hooks can. For more information on raw input, see Raw Input.

Using Raw Input, the mouse will send WM_INPUT messages directly to your window.

If you are using VCL, you can override the virtual WndProc() method to handle the WM_INPUT message, no static method needed:

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

If you are using FireMonkey, there is no WndProc() method for handling window messages (FireMonkey does not dispatch window messages to user code at all). However, you can subclass the window that FireMonkey creates internally so you can still receive the WM_INPUT message. A static method is needed, but you do not have to rely on a global pointer, the Form object can be passed as a parameter of the subclassing:

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);
}
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • +1. Do you happen to have a reference for it being safe to use a static member function in a Win32 callback? My reading of the C++ standard suggests this is formally considered to be undefined behaviour (because a pointer to a function with C++ linkage isn't valid as a pointer to a function with C linkage, as per 5.2.2) but I'm guessing Visual Studio supports it as an extension to the standard? – Harry Johnston Oct 09 '15 at 01:33
  • I don't think linkage applies here. If you had a `.c` source file and a `.cpp` source file in the same project, where the `.c` file is compiled as C and the `.cpp` file were compiled as C++, and the `.c` file were trying to call a function in the `.cpp` file, or vice versa, then linkage would apply. But not when passing function pointers across DLL boundaries. In this case, `SetWindowsHookEx()` only cares that it is given a pointer to a function that matches the signature defined by the `HOOKPROC` type. Whether it is implemented in C or C++, its linkage does not affect its signature. – Remy Lebeau Oct 09 '15 at 01:41
  • According to the standard, linkage is part of the signature. 7.5 paragraph 1 says "Two function types with different language linkages are distinct types even if they are otherwise identical." and 5.2.2 paragraph 1 says "Calling a function through an expression whose function type has a language linkage that is different from the language linkage of the function type of the called function’s definition is undefined". But I'm not trying to quibble here, I agree that it works in Visual Studio - I just hoped you'd have the reference to prove it. :-) – Harry Johnston Oct 09 '15 at 01:55
  • Not a definitive reference from the C++ standard, but I did find this (which does refer to the standard): [static specifier](http://en.cppreference.com/w/cpp/language/static) "*Address of a static member function may be stored in a regular [pointer to function](http://en.cppreference.com/w/cpp/language/pointer#Pointers_to_functions) ...*", which is all the Win32 API cares about. – Remy Lebeau Oct 09 '15 at 02:37
  • I don't think that conflicts; it is certainly a regular pointer to function, but that doesn't mean you can safely call it if the function signature doesn't match. In practical terms, though, the question is whether a static member function is guaranteed to be implemented with the correct ABI or not, and I would expect the `CALLBACK` qualifier to ensure that - or at a minimum, the compiler should generate an error if it is unable to do so. So, all things considered, I'm now confident that it is safe, and I'll shut up about it. :-) – Harry Johnston Oct 09 '15 at 02:44
  • `CALLBACK` is nothing more than a `#define` that maps to the `__stdcall` calling convention. – Remy Lebeau Oct 09 '15 at 03:10
  • I *think* that's all that's needed. If the compiler can't implement `__stdcall` for static member functions, you'll get an error message at build time. In x64 the `CALLBACK` macro is empty, but if MinGW, for example, required a special qualifier in order to allow Win32 callbacks to static member functions, I'd expect them to define `CALLBACK` accordingly in their version of the relevant header. – Harry Johnston Oct 09 '15 at 03:54
1

I came across the same issue and I found that the best method, for my particular case, was to create a static array of pointers to my class. Then inside the static hook method, I just iterate through my class pointers and call their hook functions.

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

I know it's an old question but I just wanted to throw in my two cents. I hope this is helpful to someone.