Newsgroup: it.comp.lang.c++
Titolo: Esempio-giocattolo di hook globale

Il recente thread "intercettare qualsiasi tasto premuto"
mi fa pensare che possa esserci un qualche interesse
nell'uso degli hook globali, e visto che, come ho detto
in quel thread, occorrono per usarli certe avvertenze
e precauzioni (anzitutto perche` la procedura di hook
deve "vivere" in una DLL, ed e` posta in esecuzione
nel contesto di qualsiasi thread del sistema...), penso
che un piccolo esempio di uso possa essere utile.
Ecco anzitutto un "programma principale", una
applicazione di console per semplicita`, che aggancia
una hook procedure e ne riceve messaggi, sulla base
dei quali "fa qualcosa" (in questo esempietto, mostra
semplicemente i messaggi ricevuti).
Onde permettere alla DLL di "trovare" il programma
e inviargli messaggi, usiamo una finestra con una
classe particolare, idealmente "unica" (per garantire
questo occorrerebbe in realta` aggiungere un mutex
di sincronizzazione, ma qui ho scelto di ritagliare
sulla completezza a fini di chiarezza).  Ecco dunque
l'applicazione, composta dall'unico sorgente hookex.cpp:
#define STRICT
#include <windows.h>
#include <iostream>
using std::cout;
using std::hex;
using std::dec;
#define CLASSNAME "HookExample"
// dichiarazione forward della window procedure
LRESULT CALLBACK 
HEwp(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); 
// diagnostica d'errore (qui molto semplificata!)
int erdiag(const char* id) {
    DWORD dw = GetLastError();
    cout<<"Error "<<dw<<" "<<id<<"\n";
    return 2;
}
int main()
{
    // anzitutto, evitiamo di fare partire un altra
    // istanza del programma se gia` ne esiste una
    HWND hWin = FindWindow(CLASSNAME, 0);
    if(hWin) {
        cout<<CLASSNAME<<" gia` attiva\n";
        return 1;
    }
    // OK, dobbiamo dunque lavorare... preliminari:
    // 1. registriamo la "window class" d'interesse:
    WNDCLASS wc;
    ZeroMemory(&wc, sizeof(wc));
    wc.lpfnWndProc = HEwp;
    wc.hInstance = GetModuleHandle(0);
    wc.lpszClassName = CLASSNAME;
    if(!RegisterClass(&wc))
        return erdiag("registering window class");
    // 2. generiamo la finestra (invisibile):
    hWin = CreateWindow(CLASSNAME, "He",
        WS_POPUP|WS_DISABLED,
        0, 0, 1, 1, 0, 0, wc.hInstance, 0);
    if(!hWin)
        return erdiag("creating window");
    // 3. troviamo la dll, la funzione in essa,
    // e l'agganciamo come hook
    HINSTANCE hDll = LoadLibrary("HookExample.Dll");
    if(!hDll)
        return erdiag("loading HookExample.Dll");
    FARPROC fp = GetProcAddress(hDll, "_HookProc@12");
    if(!fp)
        return erdiag("finding _HookProc@12 in Dll");
    HHOOK hh = 
        SetWindowsHookEx(WH_KEYBOARD, (HOOKPROC)fp, hDll, 0);
    if(!hh)
        return erdiag("setting hook");
    // 4. "pubblichiamo" l'hh come nostro window-text
    char buf[80];
    wsprintf(buf,"%d",hh);
    if(!SetWindowText(hWin, buf)) {
        erdiag("publishing hook handle");
        cout<<"Proceeding anyway...\n";
    }
    // finalmente inizia il lavoro vero e proprio:
    cout<<"Starting -- close this Dos-Box to terminate\n";
    // loop dei messaggi
    for(;;) {
        MSG msg;
        while(PeekMessage(&msg, 0, 0, 0, PM_REMOVE)) {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
        WaitMessage();
    }
    // in realta` non uscira` mai da qui...:
    return 0;
}
// la window-procedure:
LRESULT CALLBACK 
HEwp(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch(uMsg) {
    case WM_USER:
        cout<<"W="<<dec<<wParam;
        cout<<" L="<<hex<<lParam<<"\n";
        break;
    }
    return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
Ed ecco il singolo sorgente che viene a costituire
la DLL da chiamare HookExample.Dll e mettere ad
esempio nello stesso directory dell'exe dell'app. di
console appena vista -- HookExample.cpp...:
#define STRICT
#include <windows.h>
#include <stdlib.h>
#define CLASSNAME "HookExample"
extern "C"
__declspec(dllexport)
LRESULT CALLBACK HookProc(
    int nCode, 
    WPARAM wParam, 
    LPARAM lParam)
{
    static HWND hWin;
    static HHOOK hh;
    if(!hWin) {
        // troviamo la finestra, e, se possibile,
        // la nostra handle di hook
        hWin = FindWindow(CLASSNAME,0);
        if(hWin) {
            char buf[80];
            if(GetWindowText(hWin,buf,80))
                hh = atoi(buf);
        } else {
            hWin = (HWND)-1;
        }
    }
    if(hWin && hWin!=(HWND)-1)
        PostMessage(hWin, WM_USER, wParam, lParam);
    if(hh)
        return CallNextHookEx(hh,nCode,wParam,lParam);
    else
        return 0;   // lasciamo processare il msg a Windows
}
Spero che tutte le peculiarita` qui esemplificate siano
sostanzialmente chiare, come il nome "_HookProc@12"
per la procedura-gancio (tipica conseguenza della
"decorazione" delle funzioni di tipo __stdcall, avendo
con extern "C" evitato la normale "decorazione" C++).
Naturalmente, applicazione e DLL del gancio possono
comunicare anche in vari altri modi (memoria condivisa
come "file-mapping", oggetti-evento di kernel, ecc), ma
lo scambio di "messaggi di finestra" e` un modo rapido
e semplice, e un tipico idioma Windows, come lo e`
quello di generare una finestra apposita per questo tipo
di comunicazioni per non sovraccaricare il compito di
altre finestre esistenti.  Notiamo anche che, se la DLL
abbisognasse di un "risultato di ritorno" dal messaggio
che lancia all'applicazione, non userebbe PostMessage
(che e` asincrono -- l'applicazione vedra` e usera` quel
messaggio "piu` tardi") ma il sincrono SendMessage,
che "blocca" chi spedisce sino al ritorno dalla window
procedure che riceve, ma, in cambio, permette di avere
il valore di ritorno della window procedure stessa come
risultato del SendMessage.
Resto naturalmente a disposizione per ulteriori aiuti
o chiarimenti (chiedere sul gruppo, per favore, NON
nella mia mailbox che e` sin troppo piena, grazie).
Alex

        return erdiag("finding _HookProc@12 in Dll");
Grazie!!
Demetrio Filocamo (
dfil...@tin.it
)

dll....come posso fare?
Puoi esaminare la DLL (non so che strumenti offrisse BC++B 1.0 a
questo scopo) per vedere esattamente come cavolo ha chiamato
la tua "hook procedure" -- con Microsoft Visual C++, ad esempio,
io uso "dumpbin /exports pippo.dll" e mi elenca le stringhe esatte dei
nomi che la DLL stessa esporta (qui, "_HookProc@12").  Una volta
che, di riffe o di raffe, hai scoperto quale e` la stringa di nome con
la quale BC++B 1 ha deciso di esporre la tua funzione dalla DLL,
sostituisci la stringa stessa nella GetProcAddress e riprova.
Forse qualche utente BC++B 1 puo` offrirti aiuto piu` dettagliato sul
problema "scoprire con che nome esatto e` esposta una certa
funzione da una DLL"...?
Alex

Ciao!!!
Ho provato in una Console Application e adesso funziona tutto bene.....ho
solo due problemi....
1) Per ogni tasto premuto mi arrivano due messaggi (per il KEY_UP e il
KEY_DOWN)....come faccio per riceverne soltanto uno?
2) Non sono riuscito a farlo funzionare in un'applicazione normale (cioč con
un form vero e proprio)...ho inserito il codice di inizializzazione
nell'evento OnFormShow ma non va proprio....avreste qualche suggerimento?
Grazie!
Saluti!!
Demetrio Filocamo (
dfil...@tin.it
)

nell'evento OnFormShow ma non va proprio....avreste qualche suggerimento?
Non avendo la piu` pallida idea di cosa sia OnFormShow, non
so che dirti in merito.  Che significa "non va proprio"?  Sei sicuro
di stare catturando i messaggi, o quel che sia, che la DLL di
hook spedisce all'applicazione per segnalare gli eventi di tastiera
di interesse?  Oppure hai errori in fase di inizializzazione, e
quali?  O che altro?
Alex

quali?  O che altro?
Utilizzando il Borland C++Builder ho creato un'applicazione e al momento
dell'inizializzazione dell'applicazione ho inserito il codice di aggancio e
settaggio dell'hook....il codice mi dice che tutto č andato bene (anche
SetWindowsHookEx)...ma poi la funzione che dovrebbe rispondere ai messaggi
di sistema non viene mai chiamata (o almeno cosě sembra) e praticamente non
funziona.....
come posso fare?
Ciao e grazie!!
Demetrio Filocamo (
dfil...@tin.it
)

come posso fare?
A questo punto hai bisogno di esperti di C++Builder, direi; puoi
verificare che i messaggi WM_qualcosa vengano opportunamente
inviati quando serve (usando Spy++ o analoghe utility), ma non
dubito che lo siano, il problema e`, cosa ci fa la tua finestra con
quei messaggi una volta che li riceve, cioe`, come hai proceduto
per agganciare l'accoppiata windowhandle/windowmessage alla
chiamata a una tua specifica funzione, e come avresti invece
dovuto procedere; e su questo, masticando poco Builder, posso
esserti di poco aiuto.  Ti consiglio di continuare a chiedere aiuto
in questo gruppo, cambiando il subject perche`, a questo punto,
il fatto che i messaggi arrivino da un hook globale, o da un puffo
postino, non fa molta differenza; e` invece tutta questione di
riceverli e smistarli correttamente entro gli schemi di Builder.
Alex