Passare un metodo di CALLBACK come argomento per SetWindowsHookExA

di il
2 risposte

Passare un metodo di CALLBACK come argomento per SetWindowsHookExA

Buongiorno, sto tentando di scrivere una classe contenente delle utility per il mouse, tra queste utility ho bisogno di includere la possibilità di ottenere il delta dello scroll della rotella del mouse.

L'ambiente sul quale sviluppo è Windows, ed utilizzo C++14.

Cercando un po' in giro sono arrivato ad impostare la classe in questo modo:

Class MouseUtils
{
private:
	HHOOK mh;
	int delta;
	
	LRESULT CALLBACK MouseCB(int nCode, WPARAM wParam, LPARAM lParam)
	{
		if (nCode < 0)
		{
			return CallNextHookEx(mh, nCode, wParam, lParam);
		}

		MSLLHOOKSTRUCT* pMouseStruct = (MSLLHOOKSTRUCT*)lParam;

		if (pMouseStruct != NULL)
		{
			if (wParam == WM_MOUSEWHEEL)
			{
				if (HIWORD(pMouseStruct->mouseData) == 120)
				{
					delta = 1;
				}
				else
				{
					delta = -1;
				}
			}
			else
			{
				delta = 0;
			}
		}

		return CallNextHookEx(mh, nCode, wParam, lParam);
	}
	
public:
	int GetWheelScroll()
	{
		using namespace std::placeholders;

		delta = 0;
		if (!(mh = SetWindowsHookExA(WH_MOUSE_LL, std::bind(&MouseUtils::MouseCB, this, _1, _2, _3), NULL, 0))) // Qui ho l'errore su std::bind
		{
			delta = -404;
		}

		MSG message;
		bool peek = true;
		long tm = time(0);

		while (peek)
		{
			PeekMessage(&message, NULL, 0, 0, PM_REMOVE);
			if (~delta == 0 || tm < time(0))
			{
				peek = false;
			}
		}

		UnhookWindowsHookEx(mh);

		return delta;
	}
}

Il problema si presenta quando devo passare il metodo di Callback MouseCB a SetWindowsHookExA, essendo all'interno di una classe ho trovato 3 modi per rendere possibile ciò:

  1. Rendere il metodo statico, ma così facendo non potrei più usare gli attributi condivisi tra i due metodi MouseCB e GetWheelScroll.
  2. Utilizzare boost::bind, però il progetto è già molto pesante ed ho la necessità di mantenere tutte le librerie statiche quindi non voglio appesantirlo ulteriormente con altre librerie.
  3. Utilizzare std::bind, è la soluzione che ho deciso di adottare in questo caso, però, come da documentazione di Microsoft, SetWindowsHookExA vuole come secondo argomento un puntatore HOOKPROC, quindi passargli direttamente std::bind restituisce un errore.

Il problema su cui non riesco a venire a capo è trovare un modo per passare std::bind alla funzione SetWindowsHookExA. A dirla tutta non credo sia nemmeno possibile farlo, ma magari mi sto perdendo qualche dettaglio che non riesco bene ad inquadrare.

L'errore che mi viene restituito è il seguente:

no suitable conversion function from "std::_Binder<std::_Unforced, LRESULT (__stdcall MouseUtils::*)(int nCode, WPARAM wParam, LPARAM lParam), MouseUtils *, const std::_Ph<1> &, const std::_Ph<2> &, const std::_Ph<3> &>" to "HOOKPROC" exists

2 Risposte

  • Re: Passare un metodo di CALLBACK come argomento per SetWindowsHookExA

    Il problema che hai riscontrato è dovuto al fatto che std::bind restituisce un oggetto di tipo std::function, che non può essere passato direttamente come argomento a SetWindowsHookExA, poiché questa funzione richiede un puntatore a funzione di tipo HOOKPROC.

    Per risolvere questo problema, puoi utilizzare una lambda function come callback al posto di std::bind. Una lambda function è una funzione anonima che viene definita e invocata direttamente all'interno di un'espressione, ed è in grado di accedere agli attributi e ai metodi della classe in cui viene definita.

    Un esempio di codice che potresti utilizzare è il seguente:

    HHOOK mh;
    int delta;
    
    LRESULT MouseCB(int nCode, WPARAM wParam, LPARAM lParam)
    {
        // ...
        return CallNextHookEx(mh, nCode, wParam, lParam);
    }
    
    public:
        int GetWheelScroll()
        {
            delta = 0;
            mh = SetWindowsHookExA(WH_MOUSE_LL, [](int nCode, WPARAM wParam, LPARAM lParam) -> LRESULT {
                return MouseCB(nCode, wParam, lParam);
            }, NULL, 0);
            if (!mh)
            {
                delta = -404;
            }
    
            // ...
    
            UnhookWindowsHookEx(mh);
            return delta;
        }
    

    In questo modo puoi passare una lambda function come callback a SetWindowsHookExA, che a sua volta invoca il metodo MouseCB della classe. La lambda function può accedere agli attributi e ai metodi della classe in cui viene definita, quindi puoi utilizzare delta e mh all'interno di MouseCB come se fossero attributi della lambda function stessa.

    Spero di esserti stato d'aiuto. In caso di dubbi o domande, non esitare a chiedere.

  • Re: Passare un metodo di CALLBACK come argomento per SetWindowsHookExA

    Grazie, con la lambda sono riuscito a passare la funzione, per chiarezza ho dovuto aggiungere il riferimento this tra le parentesi quadre e la conversione ad HOOKPROC per renderla compatibile.

    Quindi per chi avesse bisogno in futuro, la versione finale funzionante è la seguente:

    mh = SetWindowsHookExA(WH_MOUSE_LL, (HOOKPROC&)[this](int nCode, WPARAM wParam, LPARAM lParam) -> LRESULT {
                return MouseCB(nCode, wParam, lParam);
            }, NULL, 0);

    Per il resto sto ottenendo degli errori di “Access violation” nella riga in cui è presente PeekMessage(&message, NULL, 0, 0, PM_REMOVE); solo quando muovo il muovo il mouse.

    E come se non bastasse per qualche motivo durante l'esecuzione del programma quando viene impostata la funzione di lambda il mouse va a scatti.

    Non credo siano problemi che riguardano direttamente l'impostazione della lambda ma nel dubbio li riporto, se trovo una soluzione la riporterò qua sotto per completezza.

    Comunque il topic principale è risolto, grazie ancora skillzgibbys per l'aiuto!

Devi accedere o registrarti per scrivere nel forum
2 risposte