Newsgroup: it.comp.programmare.win32
Titolo: Una procedura interessante

Dato che si parlava di hook e spazi di indirizzamento separati, colgo
l'occasione per illustrare una piccola ma interessante tecnica per superare
certe limitazioni. Non so se sia gia` stata proposta da qualche rivista, io
l'ho sviluppata "in proprio" e la uso molto spesso.
Diciamo che vogliate fare cio` che dicevo nell'altro messaggio, ossia trovare
informazioni sulle icone sul desktop, ma direttamente spedendo dei messaggi al
controllo ListView che le contiene. Disponendo dell'handle della finestra,
intuitivamente verrebbe da scrivere:
LVITEM item;
char szText[1024];
item.mask = LVIF_TEXT;
item.iItem = 0;
item.iSubItem = 0;
item.pszText = szText;
item.cchTextMax = 1024;
ListView_GetItem( hListView, &item);
Provateci e... PUFF! Errore di protezione generale dell'Explorer. Perche`
succede? Semplice: perche` passate la struttura LVITEM per indirizzo. Peccato
che tale indirizzo, che passate a un altro processo (l'Explorer), sia invece
riferito alla memoria del *vostro* processo! Quando la window procedure del
controllo ListView tenta di scrivere a tale indirizzo, i risultati sono del
tutto imprevedibili e vanno dal GPF ad errori piu` "subdoli"; in ogni caso la
struttura nel vostro processo non verra` alterata in alcun modo, ovviamente.
Come risolvere il problema? Con uno hook, ma non c'e` bisogno di creare thread
o usare tecniche funamboliche. E` sufficiente che voi disponiate di un
indirizzo all'interno dello spazio di indirizzamento "remoto" che sia
effettivamente valido, e per fare questo basta un blocco di memoria condivisa.
Queste poche righe di codice per la DLL dello hook sono sufficienti:
typedef struct tagSHAREDATA
{
    struct tagSHAREDATA *remoteaddress;
    HHOOK hHook;
    BOOL bLoaded;
    LVITEM item;
    char szName[1024];
} SHAREDATA, *LPSHAREDATA;
#pragma data_seg(".sdata")
__declspec( dllexport) SHAREDATA ShareData = { NULL};
#pragma data_seg()
LRESULT CALLBACK CallWndProc( int code, WPARAM wParam, LPARAM lParam)
{
    if( ShareData.remoteaddress == NULL)
    {
        ShareData.remoteaddress = &ShareData;
        ShareData.item.pszText = ShareData.szName;
        ShareData.item.cchTextMax = 1024;
        ShareData.bLoaded = TRUE;
    }
    return CallNextHookEx( ShareData.hHook, code, wParam, lParam);
}
E, nel file .DEF della DLL (la prima direttiva e` per evitare il "name
mangling" del Visual C++, la seconda per definire la sezione di memoria
condivisa):
EXPORTS
    CallWndProc
SECTIONS
    .sdata READ WRITE SHARED
Nel vostro programma bastera` importare ShareData con __declspec(dllimport) e
caricare lo hook nel processo desiderato; dopodiche` potrete chiamare funzioni
che passano argomenti per indirizzo (con gli opportuni aggiustamenti, se si
tratta di altri dati) in modo quasi naturale.
Ecco la funzione che, dato l'handle della finestra destinazione, carica l'hook
(spetta a voi rilasciarlo quando non lo usate piu`):
BOOL InstallHook( HWND hwnd)
{
    DWORD dwThreadID;
    HINSTANCE hModule;
    HOOKPROC p;
    if( (dwThreadID = GetWindowThreadProcessId( hwnd, NULL)) == 0)
        return FALSE;
    if( (hModule = LoadLibraryEx( "IlVostroHook.dll", NULL, 0)) == NULL)
        return FALSE;
    p = (HOOKPROC)GetProcAddress( hModule, "CallWndProc");
    ShareData.bLoaded = FALSE;
    if( (ShareData.hHook = SetWindowsHookEx( WH_GETMESSAGE, p,
        hModule, dwThreadID)) == NULL)
          return FALSE;
    while( !ShareData.bLoaded)
    {
        PostMessage( hwnd, WM_MOUSEMOVE, 0, 0);
        Sleep( 100);
    }
    return TRUE;
}
La funzione e` semplice, non richiede grosse spiegazioni. L'unica cosa un po'
fumosa e` il ciclo while alla fine. Il problema e` che a volte gli hook
vengono installati in modo "ritardato", soltanto quando sono necessari; quindi
questo codice, spedendo dei messaggi "dummy" alla finestra destinazione,
sprona l'OS a caricare effettivamente la DLL dello hook. Se avete fatto caso,
e` la hook procedure che durante la sua inizializzazione pone
ShareData.bLoaded a TRUE e indica al processo chiamante che e` tutto OK.
Bene, e` tutto. Con una cinquantina di righe di codice, ora potete spedire
messaggi fra processi passando degli argomenti per indirizzo. Ad esempio il
codice per trovare il nome della prima icona diventa il seguente:
ShareData.item.mask = LVIF_TEXT;
ShareData.item.iSubItem = 0;
ShareData.item.iItem = 0;
ListView_GetItem( hListView, &((ShareData.remoteaddress)->item));
Ora tutti i dati di cui avete bisogno sono nella struttura ShareData. A cui
potete accedere direttamente, dato che e` nel vostro spazio di indirizzamento.
--
Bye! Lorenzo Lutti (
http://www.geocities.com/SiliconValley/Peaks/6258/
)

 esegue per conto del processo.
Mmmh, mi sfugge la differenza. Intendi forse dire che, visto che ogni processo
"vede" il kernel (nei 2 GB superiori del suo spazio di indirizzamento), quando
chiama una funzione del kernel di fatto si puo` pensare che essa venga
eseguita direttamente da lui? Contrapponendosi al meccanismo "alla Linux",
dove invece il kernel e` totalmente invisibile e le chiamate di sistema sono
fatte con un interrupt? Mi sembrerebbe un po' una forzatura: internamente
anche in NT le chiamate di sistema vengono effettuate con un interrupt (o con
meccanismi simili)...


eseguita direttamente da lui?
In windows certe operazioni non sono fatte dal processo che le invoca,
esse vengono intercettate ed eseguite dal kernel del s.o. passando
in uno stato supervisore (stato appunto utilizzato per proteggere
zone sensibili del sistema). Di fatto cio` equivale ad avere
indirizzi comuni tra piu` processi.
Tutto questo non succede su un sistema a spazi di indirizzamento
separati, che solitamente funziona con un meccanismo di capability.
In sostanza i diritti di accesso di ogni processo ai vari oggetti
di sistema (codice, dati, file...) sono dinamicamente assegnati e
revocati in modo che nessuno abbia piu` diritti di quelli strettamente
necessari. I controlli di protezione sono fatti ad hardware in parallelo
alla rilocazione. Niente perdite di tempo in inutili cambi di ring.
Il sistema e` per'altro assai piu` sicuro di quello adottato da 
windows...
Paolo
-----------------------------------------------
  Paolo Marotta
  Computer Science, University of Pisa
  home:   
http://www.cli.di.unipi.it/~marotta
-----------------------------------------------

 indirizzi comuni tra piu` processi.
Aaahh, in quel senso! Ora ho capito. Io - come Richter et al. - usavo
un'accezione molto piu` maccheronica del termine "spazi di indirizzamento
separati". Con questa espressione intendo semplicemente che ogni applicazione
"vede" la memoria come se ci fosse soltanto se stessa e il kernel; "separati"
nel senso appunto che l'indirizzo utente 0x12345678 in un processo punta a un
dato diverso dell'indirizzo utente 0x12345678 di un altro processo.
Grazie cmq per l'interessante spiegazione... d'ora in poi saro` piu` rigoroso.
:)
BTW, quali sistemi operativi usano questo "vero" schema di indirizzamento
separato?


separato?
Nomi non te ne so fare (non ne ricordo uno... avevano dei nomi che piu`
che altro erano sigle e poi sono poco diffusi per via dei costi), ma in
genere tutti i sistemi a microkernel (tipo quello che gira sui
calcolatori VAX) specie quelli per macchine parallele e ad alte
prestazioni (i VAX rientrano in questa categoria).
Non so se Unix o linux (pur non essendo microkernel) rientrano tra
questi.
