Newsgroup: it.comp.lang.c++
Titolo: Suggerimento

Ciao, tutti
avrei un domanda da porre a voi guru del c++ e spero anche
del visual c++.
Premetto che uso visual c++ 5.0 e si potrebbe dire che con questo
linguaggio sono alle prime armi (o quasi).
Ho bisogno di creare un'applicazione semplicissima che non abbia nessuna
interfaccia ma che intercetti il messaggio "mouse move" di tutto windows
(sul desktop e anche sulle applicazioni possibilmente) e che possa disegnare
qualcosa  direttamente sul desktop. E' possibile?
In alternativa al disegno sul desktop dovrei disegnare su una finestra senza
bordi, titolo, pulsanti di controllo vari, non ridimensionabile e che resti
sempre
in primo piano. Al massimo intercettare il doppio clik sulla finestra per
ucciderla. Direi che questa seconda soluzione con la finestra e' la
preferita.
Se qualcuno mi sapesse dare qualche suggerimento sarebbe molto gentile.
Sarebbe ancora piu' gentile se mi rispondesse anche (o solo) via e-mail
al mio indirizzo ottenuto togliendo le XX anti-spam da
XXam...@XXdigibank.it
Ciao, Andrea

al mio indirizzo ottenuto togliendo le XX anti-spam da
Qui chiedi troppo; se ci tieni ad avere la risposta anche per mail,
abbi almeno tu in primo luogo la cortesia di permettere, a chi gia`
ti offre gratuitamente il suo aiuto, di indirizzarti copia della risposta
con un semplice click opportuno invece di dovere anche editare
gli header della mail risultante!
Una risposta solo per mail non sarebbe massimamente produttiva
per nessuno.  Se ricevi una riposta imprecisa o sbagliata in modo
privato, come farai a saperlo!?  Se la ricevi come post sul gruppo,
invece, facilmente qualcun altro notera` e correggera` l'imprecisione.
Inoltre, naturalmente, le risposte in questione potrebbero essere di
interesse anche per qualcun altro che legge oggi o in futuro (tutte
le risposte pubbliche sono archiviate su 
www.dejanews.com
 e da
li` possono essere reperite col relativo motore di ricerca).
Alex

tua procedura passandole tutte le informazioni relative.
...ho pensato che, tutto sommato, il compito non e` proprio banale, e
quindi puo` valere la pena di esemplificare come portarlo a termine.
Qualunque hook di un messaggio di sistema deve risiedere in una
DLL, e quindi questo varra` anche per l'hook di mouse che vogliamo
scrivere.  Per settare il gancio bisogna fornire l'handle della dll, quindi
meglio caricarla esplicitamente (con LoadLibrary, che ci fornisce 
questa handle) che non affidarsi al linker e recuperare poi l'handle 
con GetModuleHandle -- o almeno, mi sembra piu` chiaro.
Ecco quindi un esempio di programma che carica una DLL del
genere e la aggancia appropriatamente come mouse hook.  Per
semplicita`, si tratta di un programma di console:
#define STRICT
#include <windows.h>
#include <stdio.h>
// una comoda macro x diagnosi eventuali errori
#define DiagErr(condition, reason) \
    if(condition) return Diag(reason); \
    else printf("%s: OK\n", reason);
// la funzione di diagnosi degli errori
int Diag(const char* reason)
{
    DWORD dw = GetLastError();
    printf("%s: error code=%d\n", reason, dw);
    LPVOID lpMsgBuf;
    FormatMessage( 
        FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
        NULL, dw,
        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
        (LPTSTR) &lpMsgBuf,
        0, NULL 
    );
    printf("Error msg: %s\n", lpMsgBuf);
    LocalFree(lpMsgBuf);
    return 1;
}
// il tipo della callback con cui informiamo la DLL
// della sua "handle di gancio" (hook handle)
typedef void (CALLBACK *SetInfo_p)(HHOOK hh);
// ed ecco il programma principale:
int main(int argc, char **argv)
{
    // per sincronizzarci con la DLL, usiamo un "evento":
    HANDLE hEvent = CreateEvent(0, 0, 0, "MySincEvent");
    DiagErr(!hEvent, "CreateEvent(MySincEvent)");
    // non dovrebbe essere necessario resettare questo
    // evento all'inizio, ma, non si sa mai:
    ResetEvent(hEvent);    // ignoriamo eventuale errore
    // carichiamo la DLL
    HINSTANCE hi = LoadLibrary("HookLib");
    DiagErr(!hi, "LoadLibrary(HookLib)");
    // localizziamo la funzione di aggancio del mouse
    HOOKPROC hp = (HOOKPROC) GetProcAddress(hi, "_MouseHook@12");
    DiagErr(!hp, "GetProcAddress(MouseHook)");
    // localizziamo la funzione ausiliaria di callback
    SetInfo_p psi = (SetInfo_p) GetProcAddress(hi, "_SetInfo@4");
    DiagErr(!psi, "GetProcAddress(SetInfo)");
    // agganciamo tutti gli eventi di mouse alla funzione
    HHOOK hh = SetWindowsHookEx(WH_MOUSE, hp, hi, 0);
    DiagErr(!hh, "HookWindowsHookEx");
    (*psi)(hh);
    // aspettiamo che la DLL ci dica di essere terminata o
    // che passino 20 secondi   
    int timeout = 20*1000;  // max 20 seconds
    printf("Waiting...\n");
    DWORD waitResult = WaitForSingleObject(hEvent, timeout);
    // diagnostica della ragione per cui usciamo:
    if(waitResult == WAIT_TIMEOUT) {
        printf("timeout period elapsed\n");
    } else if(waitResult == WAIT_OBJECT_0) {
        printf("normal termination\n");
    } else if(waitResult == WAIT_ABANDONED) {
        printf("unexpected abandonment\n");
    } else {
        printf("unexpected result %d from wait\n", waitResult);
    }
    // sganciamo la funzione della DLL dal mouse
    BOOL rc = UnhookWindowsHookEx(hh);
    DiagErr(!rc, "UnhookWindowsHookEx");
    // fine del programma
    return 0;
}
Visto che ci serve a sviluppare e debuggare la DLL stessa,
ho pensato bene di curare particolarmente la diagnostica
degli errori, e inoltre, giusto per il caso che la DLL abbia
bug che rischierebbero di bloccare l'intero sistema, di
porre un timeout di 20 secondi sul tempo massimo in cui
il tutto resta attivo.  Ci si potrebbero risparmiare le due
GetProcAddress spostando la SetWindowsHookEx nella
DLL stessa, il che sarebbe probabilmente consigliabile
per codice "di produzione" (creando, in pratica, una DLL
che aggancia il mouse quando caricata e lo sgancia quando
scaricata), ma qui mi e` sembrato piu` chiaro evidenziare
aggancio e scancio separatamente.
Bisognera` naturalmente che la DLL, quando questo EXE
gira e cerca di caricarla, sia in uno dei directory canonici
che permettono alla LoadLibrary di trovarla (directory
corrente, directory dell'EXE, directory della PATH e/o
di sistema).
La DLL avra`, com'e` buona prassi, un header che elenca
le funzioni che essa esporta, HookLib.h in questo caso:
extern "C"
__declspec(dllexport)
LRESULT CALLBACK 
MouseHook(int nCode, WPARAM wParam, LPARAM lParam); 
 
extern "C"
__declspec(dllexport)
void CALLBACK
SetInfo(HHOOK hh);
Tutto il codice puo` essere posto in un singolo sorgente,
HookLib.cpp ad esempio:
#define STRICT
#include <windows.h>
#include "HookLib.h"
// variabili globali della DLL:
HANDLE hEvent = 0;    // evento per sincronizzazione
HHOOK hhk = 0;            // il nostro hook-handle
// la finestrina su cui mostriamo il msg corrente
HWND hWnd = 0;
// testo del messaggio da mostrare
char messageBuff[256] = "No message yet";
// fine dell'esecuzione della DLL
BOOL
Terminate()
{
    // informa l'EXE della terminazione
    if(hEvent) {
        SetEvent(hEvent);
        hEvent = 0;
    }
    // chiude la finestrina di messaggio
    if(hWnd) {
        DestroyWindow(hWnd);
        hWnd = 0;
    }
    return TRUE;
}
// callback chiamata dall'EXE per dirci qual e` il
// nostro handle di hook (aggancio)
void CALLBACK
SetInfo(HHOOK hh)
{
    hhk = hh;
}
// diagnostica di eventuali errori
BOOL Diag(const char* reason)
{
    char error[80];
    DWORD dw = GetLastError();
    wsprintf(error, "%s: error=%d", reason, dw);
    LPVOID lpMsgBuf;
    FormatMessage( 
        FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
        NULL, dw,
        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
        (LPTSTR) &lpMsgBuf,
        0, NULL 
    );
    MessageBox(0, (const char*)lpMsgBuf, error, MB_OK);
    LocalFree(lpMsgBuf);
    Terminate();
    return 1;
}
// la procedura di aggancio del mouse
LRESULT CALLBACK 
MouseHook(int nCode, WPARAM wParam, LPARAM lParam)
{
    if(hWnd) {
        // recupera i parametri del messaggio di mouse
        MOUSEHOOKSTRUCT* pMhs = (MOUSEHOOKSTRUCT*)lParam;
        // formatta alcune informazioni sul messaggio di mouse
        wsprintf(messageBuff, "Mouse wp=%d (%d,%d)", wParam,
            pMhs->pt.x, pMhs->pt.y);
        // garantisce il redisplay della finestra di messaggio
        InvalidateRect(hWnd, 0, 1);
        UpdateWindow(hWnd);
    }
    // rispetta le regole delle catene di hook chiamando il prossimo:
    return CallNextHookEx(hhk, nCode, wParam, lParam);
}
// la procedura-finestra della finestra-messaggio: ci serve solo per
// gestire WM_PAINT mostrando il testo del messaggio, e per
// terminare al doppio-click -- per il resto, tutto e` come di default.
LRESULT CALLBACK 
MyWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    if(uMsg == WM_PAINT) {
        PAINTSTRUCT ps;
        HDC hdc = BeginPaint(hwnd, &ps);
        TextOut(hdc, 1, 1, messageBuff, strlen(messageBuff));
        EndPaint(hwnd, &ps);
    } else if(uMsg == WM_LBUTTONDBLCLK) {
        Terminate();
    }
    return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
// inizializzazione della DLL (al caricamento)
BOOL Init(HINSTANCE hModule)
{
    // definiamo la window-class della finestra di messaggio
    WNDCLASS clastruct;
    memset(&clastruct, 0, sizeof(clastruct));
    clastruct.style         = CS_DBLCLKS;
    clastruct.lpfnWndProc   = MyWindowProc;
    clastruct.hInstance     = hModule;
    clastruct.hCursor       = LoadCursor(0, IDC_ARROW);
    clastruct.hbrBackground = CreateSolidBrush(COLOR_BACKGROUND);
    clastruct.lpszClassName = "MyClass";
 
    // la registriamo
    ATOM at = RegisterClass(&clastruct);
    if(!at) return Diag("RegisterClass");
    // creiamo la finestra di messaggio
    hWnd = CreateWindowEx(WS_EX_TOPMOST,
        "MyClass", "MyWindow", WS_POPUP,
        10, 10, 200, 20, 0, 0, hModule, 0);
    if(!hWnd) return Diag("CreateWindowEx");
    // display della finestra di messaggio
    InvalidateRect(hWnd, 0, 1);
    UpdateWindow(hWnd);
    ShowWindow(hWnd, SW_SHOW);
    // prepariamo l'evento x sincronizzarci con l'EXE
    hEvent = CreateEvent(0, 0, 0, "MySincEvent");
    if(!hEvent) return Diag("CreateEvent");
    // TRUE==tutto a posto
    return TRUE;
}
// automaticamente chiamata da Window al momento
// del LoadLibrary e a quello della terminazione del
// processo (o precedente FreeLibrary):
BOOL APIENTRY DllMain(HINSTANCE hModule, 
                      DWORD  reason_for_call, 
                      LPVOID lpReserved)
{
    switch(reason_for_call) {
    case DLL_PROCESS_ATTACH:
        return Init(hModule);
    case DLL_PROCESS_DETACH:
        return Terminate();
    }
    return TRUE;
}
Ecco tutto... si possono raffinare e migliorare parecchi
dettagli, naturalmente, ma come scheletro generale di
un hook di sistema mi sembra abbastanza funzionale e
soprattutto discretamente istruttivo per un principiante.
Alex

>Alex Martelli ha scritto nel messaggio
Grazie per la risposta dettagliatissima. Ho provato
subito i programmi che mi hai dato e funzionavano alla
perfezione. Era esattamente quello di cui avevo bisogno.
Per quanto riguarda il disegno sul desktop grazie al tuo suggerimento
sono riuscito a fare quello che volevo. Ora non mi resta che unire
le due cose: fare l'hook del mouse e disegnare di conseguenza sul
desktop ;-) ma penso che quando avro' un po' di tempo e di pazienza
di riuscirci senza troppi problemi.
Ho notato solo un comportamento strano nel programma che fa' l'hook
del mouse move. Quando sono sulla finestra dos del programma tutto
ok, poi esco e funziona ancora tutto. Quando col mouse torno sulla
finestra dos non vengono piu' aggiornate le coordinate nella finestrella
di "controllo". Da quel momento in poi viene gestito l'evento move SOLO
fuori dalla finestra dos. Per me non e' un problema perche' il programma
che devo fare non ha alcuna finestra ma e' normale quel comportamento?
O per lo meno: e' spiegabile in qualche modo?
Ciao, ancora grazie, Andrea

finestra dos non vengono piu' aggiornate le coordinate nella
finestrella
Questo bug non si verifica in Windows NT 4 e in Windows 98, quindi
posso solo presumere che si tratti di una anomalia di Windows 95 (una
delle tante...).  Se non sbaglio fra i parametri della finestra DOS in
W95
ce ne sono alcuni relativi al trattamento del mouse, se e` cosi` prova
un
po' a giocherellarci (ma non mi sorprenderebbe se non ci fosse niente
da fare... W95, soprattutto quando entrano in gioco le interazioni col
Dos,
e` un colabrodo [solo Win 3.1 e` peggio!-)], W98 solo un po' meglio, e
per
avere un SO decente bisogna proprio passare a W/NT...).
Se devi funzionare a tutti i costi sotto W95, e l'anomalia risulta
grave,
posso solo suggerirti di provare altri hook al posto di quello del
mouse,
specificamente lo hook di "CBT" che ti aggancia praticamente TUTTO,
forse quello funziona e ti basta ignorarne le callback non di mouse (non
ho sotto mano un W95 per provare, sorry, solo NT4 e W98).
Alex

grave,
In teoria il mio programma non dovrebbe neanche avere una
finestra... Quindi il problema non è drammatico.
Invece ho provato ad unire le due cose (HOOK del mouse e disegno
sul desktop senza nessuna finesta dos.
Nella HookLib ho modificato la procedura di risposta cosi'
----------------
LRESULT CALLBACK
MouseHook(int nCode, WPARAM wParam, LPARAM lParam)
{
    if(hWnd) {
        // recupera i parametri del messaggio di mouse
        MOUSEHOOKSTRUCT* pMhs = (MOUSEHOOKSTRUCT*)lParam;
  CWnd *DesktopWin = (CWnd *) GetDesktopWindow();
  CDC *dc = DesktopWin->GetWindowDC();
  dc->SetPixel(pMhs->pt.x, pMhs->pt.y, RGB(0, 0, 255));
  ReleaseDC(DesktopWin->m_hWnd , dc->m_hDC );
    }
    // rispetta le regole delle catene di hook chiamando il prossimo:
    return CallNextHookEx(hhk, nCode, wParam, lParam);
}
----------------
ma non succede nulla sullo schermo... Il problema è qui ho
nel "settaggio dell'hook nell'applicazione?
Io ho fatto cosi' ho costruico col wizard un'applicazione dialog-based
e nella funzione che apre la finestra di dialogo ho messo il codice
di hook copiato da quello che mi hai mandato. Poi ovviamente
non chiamo la finestra.
--------------------------------
BOOL CFollowMeApp::InitInstance()
{
    HANDLE hEvent = CreateEvent(0, 0, 0, "MySincEvent");
    ResetEvent(hEvent);    // ignoriamo eventuale errore
    HINSTANCE hi = LoadLibrary("HookLib");
     HOOKPROC hp = (HOOKPROC) GetProcAddress(hi, "_MouseHook@12");
    SetInfo_p psi = (SetInfo_p) GetProcAddress(hi, "_SetInfo@4");
    HHOOK hh = SetWindowsHookEx(WH_MOUSE, hp, hi, 0);
     (*psi)(hh);
    int timeout = 20*1000;  // max 20 seconds
    DWORD waitResult = WaitForSingleObject(hEvent, timeout);
    BOOL rc = UnhookWindowsHookEx(hh);
    return 0;
return FALSE;
}
-------------------------------
Ho tolto tutti i commenti e spero di non aver cancellato per sbaglio
qualcosa nel
trascrivere il codice qui nel messaggio.
Va beh, è tutto.
Ciao, Buone vacanze. Se mi rispondi leggero' la risposta ad agosto. Spero
che
il messaggio sia ancora sul server.