Tutorial 3: Una semplice Finestra
From UIC
Tutorial 3: Una semplice Finestra
Contents |
| Infos | |
|---|---|
| Author: | Iczelion |
| Email: | Traduttore: -NeuRaL_NoiSE |
| Website: | Mirror |
| Date: | 01/01/2001 (dd/mm/yyyy) |
| Level: |
|
| Language: | Italian |
| Comments: | |
Introduzione
In questo tutorial, costruiremo un programma per Windows che mostra una finestra pienamente funzionante sul desktop.
Preliminari
I programmi per Windows si basano molto sulle funzioni API per la loro GUI (Graphic User Interface, Interfaccia Grafica Utente, NdT). Questo approccio va a vantaggio sia degli utenti che dei programmatori. Per gli utenti, il vantaggio e' che non devono imparare come navigare nella GUI di ogni nuovo programma, poiche' le GUI dei programmi Window sono simili. Per i programmatori, il vantaggio e' che i codici per la GUI stanno gia' li', testati e pronti all'uso. Il rovescio della medaglia per i programmatori e' la maggiore complessita' implicata. Per poter creare o manipolare ogni oggetto della GUI come finestre, menu o icone, i programmatori devono seguire una strada obbligata e molto stretta (come avreste tradotto voi "strict recipe" ?!?, NdT ;). Ma cio' puo' essere superato grazie alla programmazione modulare o al paradigma OOP.
Vi mostrero' qui di seguito i passi necessari per poter creare una finestra sul desktop:
1.Trovare l'instance handle del vostro programma (necessario)
2.Ricevere la riga di comando (non necessario a meno che il vostro programma non riceva una riga di comando)
3.Registrare la classe della finestra (window class) (necessario, a meno che non vengano usati tipi di finestre predefinite, come ad esempio la MessageBox)
4.Creare la finestra (necessario)
5.Mostrare la finestra sul desktop (necessario a meno che non si voglia mostrare la finestra immediatamente)
6.Rinfrescare (refresh) la client area della finestra
7.Entrare in un loop infinito, che controlla l'arrivo di messaggi da Windows
8.Se arrivano messaggi, vengono elaborati da una funzione specializzata che e' responsabile della finestra
9.Uscire dal programma se l'utente chiude la finestra
Come potete vedere, la struttura di un programma per Windows e' abbastanza complessa se messa a confronto con quella di un programma per DOS. Ma il mondo di Windows e' drasticamente diverso dal mondo del DOS. I programmi per Windows devono essere capaci di coesistere pacificamente. Devono seguire delle regole ferree. Voi, in quanto programmatori, dovrete parimenti diventare piu' severi nello stile e nelle abitudini di programmazione.
Contenuto
Qui di seguito c'e' il codice del nostro semplice programma di creazione della finestra. Prima di inoltrarci nei dettagli insanguinati della programmazione in assembly per Win32, vi delineero' alcuni punti importanti che vi semplificheranno la programmazione.
- Dovreste inserire tutte le costanti, strutture e prototipi di funzioni di Windows in un file di include e includerlo all'inizio del vostro file .asm. Cio' vi fara' risparmiare moltissimo tempo e vi aiutera' ad evitare errori di battitura. La maggior parte delle volte, potrete usare il file di include di alcuni esempi di asm per Win32. Io ho usato windows.inc dall'esempio "Small Is Beautiful" di Steve Gibson (scaricatevi quest'esempio anche se windows.inc e' contenuto nel package dell'esempio di questo tutorial...Small Is Beautiful e' da vedere per crederci, NdT :) e ci ho fatto alcune aggiunte personali.
- Usate la direttiva IncludeLib per specificare le import libraries usate dal vostro programma. Ad esempio, se il vostro programma chiama MessageBoxA, dovete inserire la riga:
IncludeLib user32.lib all'inizio del vostro file .asm. Questa direttiva dice a MASM che il vostro programma fara' uso di funzioni presenti in quella import library. Se il vostro programma chiama funzioni in piu' di una libreria, semplicemente aggiungete un IncludeLib per ogni libreria usata. Usando la direttiva IncludeLib, non dovete preoccuparvi delle import libraries al momento del link. Potete comunque usare lo switch /LIBPATH del linker per comunicare a Link la path delle librerie.
- Quando dichiarate i prototipi per le funzioni API, le strutture o le costanti nel vostro file di include, cercate di utilizzare i nomi originali usati nei file include di Windows, inclusa la formattazione (maiuscole/minuscole). Cio' vi evitera' un bel po' di mal di testa quando cercherete qualcosa nella Win32 API reference.
- Usate makefile per automatizzare il processo di assemblaggio. Cio' vi fara' evitare di scrivere molto.
includelib user32.lib // ; chiamate a funzioni incluse in user32.lib e kernel32.lib
includelib kernel32.lib
.DATA // ; dati inizializzati
ClassName db "SimpleWinClass",0 // ; il nome della classe della nostra finestra
AppName db "Our First Window",0 // ; il nome della nostra finestra
.DATA? // ; dati NON inizializzati
hInstance HINSTANCE ? // ; Instance handle del nostro programma
CommandLine LPSTR ?
.CODE // ; Qui comincia il nostro codice
start:
invoke GetModuleHandle, NULL // ; trova l'instance handle del nostro programma.
// ; Sotto Win32, hmodule==hinstance
mov hInstance,eax
invoke GetCommandLine // ; rileva la riga di comando. Non dovete chiamare questa funzione SE
// ; il vostro programma non elabora la riga di comando
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT // ; chiama la funzione principale
invoke ExitProcess,eax // ; esci dal nostro programma. Il codice di uscita e' conservato in
// ; eax dopo WinMain.
WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:SDWORD
LOCAL wc:WNDCLASSEX // ; crea variabili locali sulla stack
LOCAL msg:MSG
LOCAL hwnd:HWND
mov wc.cbSize,SIZEOF WNDCLASSEX // ; riempi con valori i membri di wc
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInstance
pop wc.hInstance
mov wc.hbrBackground,COLOR_WINDOW+1
mov wc.lpszMenuName,NULL
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,0
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc // ; registra la classe della nostra finestra
invoke CreateWindowEx,NULL,\
ADDR ClassName,\
ADDR AppName,\
WS_OVERLAPPEDWINDOW,\
CW_USEDEFAULT,\
CW_USEDEFAULT,\
CW_USEDEFAULT,\
CW_USEDEFAULT,\
NULL,\
NULL,\
hInst,\
NULL
mov hwnd,eax
invoke ShowWindow, hwnd,CmdShow // ; mostra la nostra finestra sul desktop
invoke UpdateWindow, hwnd // ; rinfresca la client area
.WHILE TRUE // ; Entra nel loop dei messaggi
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDW
mov eax,msg.wParam // ; metti il codice di uscita in eax
ret
WinMain endp
WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
mov eax,uMsg // ; metti il messaggio di windows in eax per efficienza
.IF eax==WM_DESTROY // ; se l'utente chiude la nostra finestra
invoke PostQuitMessage,NULL // ; Esci dalla nostra applicazione
xor eax,eax
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam // ; Elaborazione di
// ; default (predefinita, NdT)
// ; dei messaggi
.ENDIF
ret
WndProc endp
end start
Potreste essere sconvolti dal fatto che un semplice programma per Windows richiede tutto questo codice. Ma la maggior parte di queste istruzioni sono semplicemente delle istruzioni di *template* che potrete copiare da un codice sorgente ad un altro. O, se preferite, potete assemblare alcuni di queste istruzioni in una libreria da usare come prologo ed epilogo. Potete scrivere solo le istruzioni nella funzione WinMain. Infatti, questo e' quello che fanno i compilatori C. Vi fanno scrivere le istruzioni di WinMain senza farvi preoccupare delle faccende di casa. L'unico rovescio della medaglia e' che dovrete avere una funzione denominata WinMain o altrimenti i compilatori C non saranno capaci di combinare il vostro codice con il prologo e l'epilogo. Non avete tale restrizione in assembly. Potete usare qualsiasi nome di funzione invece di WinMain o anche nessuna funzione.
Preparatevi. Questo sara' un lungo, lungo tutorial. Analizziamo questo programma fino all'osso!
include windows.inc
includelib user32.lib
includelib kernel32.lib
Dobbiamo includere windows.inc all'inizio del codice. Esso contiene prototipi di funzioni API importanti, strutture e costanti che vengono utilizzate dal nostro programma. Il file include, windows.inc, e' un semplice file di testo. Potete aprirlo con un qualsiasi editor di testo. Le prime due linee sono .386 e le direttive .model, per cui non dovete specificare queste due linee all'inizio del vostro codice. Successivamente, ci sono diverse macros che il suo autore (Steve Gibson) usa frequentemente. Il resto del file contiene strutture importanti, costanti e prototipi di funzioni. Notate che windows.inc non contiente tutte le strutture, costanti e prototipi di funzioni di Windows. Esso contiene solo le piu' usate. Potete aggiungere nuove cose se snon sono nel file.
Il nostro programma chiama funzioni API che risiedono in user32.dll (CreateWindowEx, RegisterWindowClassEx, per esempio) e kernel32.dll (ExitProcess), percio' dovremo linkare il nostro programma con quelle due import libraries. La prossima domanda e': come faccio a sapere quali import libraries devono essere linkate al mio programma? La risposta: dovete sapere dove risiedono le funzioni API chiamate dal vostro programma. Per esempio, se chiamate una funzione API in gdi32.dll, dovrete linkare con gdi32.lib.
Questo e' l'approccio di MASM. Il sistema di importazione di librerie di TASM e' molto piu' semplice: semplicemente, bisogna linkare ad UN solo file: import32.lib.
ClassName db "SimpleWinClass",0
AppName db "Our First Window",0
.DATA?
hInstance HINSTANCE ?
CommandLine LPSTR ?
Successivamente incontriamo le sezioni "DATA".
In .DATA, dichiariamo due stringhe terminate con zero (ASCIIZ strings): ClassName che e' il nome della classe della nostra finestra e AppName che e' il nome della nostra finestra. Notate che le due variabili sono inizializzate.
In .DATA?, vengono dichiarate tre variabili: hInstance (instance handle del nostro programma), CommandLine (riga di comando del nostro programma), e CommandShow (stato della nostra finestra alla prima comparsa). I tipi di dati poco familiari, HINSTANCE e LPSTR, sono in realta' nuovi nomi per DWORD. Puoi esaminarli in windows.inc. Notate che nessuna variabile in .DATA? e' inizializzata, a significare che non devono contenere un valore specifico all'avvio, ma ne vogliamo riservare lo spazio per un uso futuro.
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke GetCommandLine
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess,eax
.....
end start
.CODE contiene tutte le vostre istruzioni. Il vostro codice deve risiedere tutto tra <etichetta iniziale>: e end <etichetta iniziale>. Il nome dell'etichetta non e' importante. Potete chiamarla come volete, sempre se non viola la convenzione dei nomi di MASM. La nostra prima istruzione e' la chiamata a GetModuleHandle per trovare l'instance handle del nostro programma. Sotto Win32, l'instance handle e l'handle del modulo sono una sola cosa. Potreste pensare all'instance handle come all' ID del nostro programma. Esso e' usato come parametro per molte funzioni API che il nostro programma deve chiamare, percio' e' generalmente una buona idea trovarlo all'inizio del nostro programma. Al ritorno da una funzione Win32, il valore di ritorno (return value), se esiste, puo' essere trovato in eax. Tutti gli altri valori sono ritornati attraverso variabili passate nella lista dei parametri di funzione che avete definito per la call. Quando chiamate una funzione Win32, essa preservera' sempre i segment registers e i registri ebx, edi, esi e ebp. Contrariamente, ecx e edx sono considerati registri di scratch (cioe' un po' come il general purpose, NdT), e non potete mai prevedere come saranno al ritorno da una funzione Win32. La conclusione e' questa: quando chiamate una funzione API, aspettatevi che il valore di ritorno sia in eax. Se una delle vostre funzioni verranno chiamate da Windows, dovete ugualmente giocare seguendo la regola: salvate e ripristinate i segment registers, ebx, edi, esi e ebp al ritorno da una funzione o il vostro programma crashera' in breve tempo. La chiamata a GetCommandLine e' inutile se il vostro programma non elabora una riga di comando. In questo esempio, io vi mostro come utilizzarla in caso ne abbiate bisogno nei vostri programmi. Successivamente troviamo la call a WinMain(). Qui riceve quattro parametri: l'instance handle del nostro programma, l'instance handle della precedente istanza del nostro programma, la riga di comando e lo stato della finestra alla prima comparsa. Sotto Win32, NON ESISTE istanza precedente. Ogni programma e' solo nel suo address space, percio' il valore di hPrevInst e' sempre 0. Questo parametro e' stato lasciato dai tempi di Win16. Nota: Non dovete dichiarare il nome della funzione come WinMain. Infatti, avete liberta' completa riguardo a questo. Non siete per niente obbligati ad usare funzioni equivalenti a WinMain. Potete copiare le istruzioni contenute in WinMain subito dopo GetCommandLine e il vostro programma funzionera' ancora perfettamente. Al ritorno da WinMain, eax contiene l'exit code (codice di uscita, NdT). Noi passiamo l'exit code come parametro ad ExitProcess che chiude la nostra applicazione. WinMain proc Inst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:SDWORD La riga qui sopra e' la dichiarazione di funzione di WinMain. Notate le coppie Parametro:TIPO che seguono la direttiva PROC. Essi sono parametri che WinMain riceve dal caller. Potete riferirvi a questi parametri per nome piuttosto che per manipolazione di stack. In piu', MASM generera' le istruzioni di prologo ed epilogo per la funzione. Pertanto non ci dobbiamo preoccupare dello stack frame all'ingresso ed all'uscita dalle funzioni.
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
La direttiva LOCAL riserva memoria dalla stack per variabili locali usate nella funzione. La direttiva LOCAL e' immediatamente seguita da <nome della variabile locale>:<tipo di variabile>. Percio' LOCAL wc:WNDCLASSEX dice a MASM di riservare dalla stack una quantita' di memoria della dimensione della struttura WNDCLASSEX per la variabile denominata wc. Possiamo riferirci a wc nel nostro codice senza nessuna difficolta' implicata nella manipolazione della stack. E' un vero dono di Dio, a mio parere. Il rovescio della medaglia e' che le variabili locali non possono essere usate al di fuori della funzione in cui sono state create, e vengono automaticamente distrutte quando la funzione ritorna al caller. Un altro svantaggio e' che non potete inizializzare variabili locali perche' esse consistono in memoria di stack riservata dinamicamente all'inizio della funzione. Dovete assegnarvi manualmente il valore desiderato dopo la direttiva LOCAL.
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInstance
pop wc.hInstance
mov wc.hbrBackground,COLOR_WINDOW+1
mov wc.lpszMenuName,NULL
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,0
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc ; registra la classe della nostra finestra
Le spaventose righe qui sopra sono in realta' molto semplici nel concetto. E' solo che ci vogliono molte linee di codice per compierlo. Il concetto che giace dietro tutte queste linee e' una window class (classe della finestra, NdT). Una window class non e' nient'altro che il "progetto" di una finestra. Essa definisce molte importanti caratteristiche di una finestra, come la sua icona, il suo cursore, la funzione responsabile per essa, il suo colore ecc. Voi create una finestra da una window class. Questo e' una specie di concetto "object oriented" (orientato agli oggetti, NdT). Se volete creare piu' di una finestra con le stesse caratteristiche, e' ragionevole conservare tutte queste caratteristiche in un solo posto e riferirsi ad esse quando se ne ha bisogno. Questo schema risparmiera' molta memoria, evitando la duplicazione delle informazioni. Ricordate, Windows e' stato disegnato in passato, quando i costi dei chip di memoria erano proibitivi e la maggior parte dei computer aveva 1 MB di memoria. Windows deve essere molto efficiente nell'uso di scarse risorse di memoria. Il punto e': se definite la vostra finestra personale, dovete riempire le caratteristiche desiderate per la vostra finestra in strutture WNDCLASS o WNDCLASSEX e chiamare RegisterClass o RegisterClassEx prima di essere in grado di creare la vostra finestra. Dovete registrare la window class una sola volta per ogni tipo di finestra da cui volete crearne un'altra.
Windows ha molte window classes predefinite, come "button" e "edit box". Per queste finestre (o controls - controlli, NdT), non dovete registrare una window class, semplicemente chiamerete CreateWindowEx con il class name predefinito.
L'unico membro importante in WNDCLASSEX e' lpfnWndProc. lpfn significa "long pointer to function". Sotto Win32, non ci sono puntatori "near" o "far", bensi' solo "pointer", a causa del nuovo modello di memoria FLAT. Ma questa, ancora una volta, e' una reminescenza dai tempi di Win16. Ogni window class deve essere associata con una funzione chiamata window procedure. La window procedure e' responsabile per la gestione dei messaggi di tutte le finestre create dalla window class associata. Windows mandera' messaggi alla window procedure per notificarle eventi importanti inerenti le finestre di cui e' responsabile, come input da tastiera o da mouse. Sta poi alla window procedure rispondere adeguatamente ad ogni messaggio che riceve. Passerete la maggior parte del tempo a scrivere codice che gestisce gli eventi in una window procedure.
Descrivero' qui di seguito ogni elemento di WNDCLASSEX:
UINT cbSize;
UINT style;
WNDPROC lpfnWndProc;
int cbClsExtra;
int cbWndExtra;
HINSTANCE hInstance;
HICON hIcon;
HCURSOR hCursor;
HBRUSH hbrBackground;
LPCSTR lpszMenuName;
LPCSTR lpszClassName;
HICON hIconSm;
} WNDCLASSEX;
cbSize: La dimensione della struttura WNDCLASSEX in bytes. Possiamo usare l'operatore SIZEOF per ottenere il valore.
style: Lo stile delle finestre create da questa class. Potete combinare diversi stili usando l'operatore "or".
lpfnWndProc: L'indirizzo della window procedure responsabile per le finestre create da questa class.
cbClsExtra: Specifica il numero di extra bytes da riservare in seguito alla struttura window-class. Il sistema operativo inzializza questi bytes con zero.
cbWndExtra: Specifica il numero di extra bytes to allocate in seguito alla window instance. Il sistema operativo inzializza questi bytes con zero. Se un'applicazione usa la struttura WNDCLASS per registrare un dialog box creato usando la direttiva CLASS nel file .res (resource file), la stessa deve modificare questo membro in DLGWINDOWEXTRA.
hInstance: L'instance handle del modulo.
hIcon: Handle dell'icona. Lo si ottiene dalla call a LoadIcon.
hCursor: Handle del cursore. Lo si ottiene dalla call a LoadCursor.
hbrBackground: Colore di sfondo delle finestre create da questa class.
lpszMenuName: Menu handle predefinito per le finestre create da questa class.
lpszClassName: Il nome di questa window class.
hIconSm: Handle di una piccola icona che viene associata con la window class. Se questo membro e' NULL, il sistema cerca l'icon resource specificata dal membro hIcon per una icona di dimensione adeguata ad essere usata come icona piccola.
ADDR ClassName,\
ADDR AppName,\
WS_OVERLAPPEDWINDOW,\
CW_USEDEFAULT,\
CW_USEDEFAULT,\
CW_USEDEFAULT,\
CW_USEDEFAULT,\
NULL,\
NULL,\
hInst,\
NULL
Dopo aver registrato la window class, possiamo chiamare CreateWindowEx per creare la nostra finestra in base alla window class scelta. Notate che ci sono 12 parametri per questa funzione. Il prototipo di funzione in C per CreateWindowEx e' il seguente:
WINAPI
CreateWindowExA(
DWORD dwExStyle,
LPCSTR lpClassName,
LPCSTR lpWindowName,
DWORD dwStyle,
int X,
int Y,
int nWidth,
int nHeight,
HWND hWndParent ,
HMENU hMenu,
HINSTANCE hInstance,
LPVOID lpParam);
Diamo un'occhiata alla descrizione dettagliata per ciascun parametro:
dwExStyle: Extra window styles (stili extra per la finestra, NdT). Questo e' il nuovo parametro che e' stato aggiunto alla vecchia CreateWindow. Potete inserire nuovi stili per Windows 95 ed NT qui. Potete specificare il vostro window style ordinario in dwStyle ma se volete qualche stile speciale come topmost window, dovete specificarli qui. Potete usare NULL se non volete extra window styles.
lpClassName: (Necessario). Indirizzo della stringa ASCIIZ contenente il nome della window class che volete usare come template per questa finestra. La class puo' essere la vostra propria classe registrata o una window class predefinita. Come detto prima, ogni finestra che create deve essere basata su una window class.
lpWindowName: Indirizzo della stringa ASCIIZ contenente il nome della finestra. Sara' mostrata sulla barra di titolo della finestra. Se questo parametro e' NULL, la barra di titolo della finestra sara' vuota.
dwStyle: Stili della finestra. Dovete specificare l'apparenza della finestra qui. Passare NULL va bene ma la finestra non avra' un box con menu di sistema, nessun bottone riduci a icona/ingrandisci, e nessun bottone chiudi finestra. La finestra non servirebbe poi a tanto. Dovreste premere Alt+F4 per chiuderla. Il window style piu' comune e' WS_OVERLAPPEDWINDOW. Un window style e' solo una bit flag. In tal modo puoi combinare molti stili con l'operatore "or" per ottenere il desiderato look della finestra. Lo stile WS_OVERLAPPEDWINDOW e' in realta' una combinazione degli stili piu' comuni, messi insieme con questo metodo.
X,Y: Le coordinate dell'angolo superiore sinistro della finestra. Normalmente questo valore dovrebbe essere CW_USEDEFAULT, che significa che sara' Windows a decidere per voi dove posizionare la finestra sul desktop.
nWidth, nHeight: La larghezza e l'altezza della finestra in pixels. Potete anche usare CW_USEDEFAULT per lasciare a Windows il compito di scegliere una larghezza ed un'altezza appropriate per voi.
hWndParent: L'handle della parent window della finestra (se esiste). Questo parametro dice a Windows se questa finestra e' una child window (subordinata) di qualche altra finestra, e, se lo e', quale finestra e' la parent. Notate che questa non e' la stessa relazione parent-child degli MDI (multiple document interface, interfaccia multi-documento, NdT). Le child windows non sono legate alla client area della parent window. Questa relazione e' esclusivamente per un uso interno a Windows. Se la parent window e' distrutta, tutte le child windows verranno distrutte automaticamente. E' davvero cosi' semplice. Poiche' nel nostro esempio c'e' solo una finestra, noi definiremo questo parametro come NULL.
hMenu: L'handle del menu della finestra. NULL se deve essere usato il menu della window class. Ritornate un attimo al membro lpszMenuName della struttura WNDCLASSEX. lpszMenuName specifica il menu predefinito per la window class. Ogni finestra creata per questa window class avra' lo stesso menu per default, a meno che voi non specifichiate un *overriding* menu per una specifica finestra tramite il suo parametro hMenu. hMenu e' in realta' un parametro con due scopi. Nel caso in cui la finestra che volete creare e' di tipo predefinito (ad esempio un control), tale control non puo' possedere un menu. hMenu verra' usato come l'ID di quel control. Windows puo' distinguere se hMenu e' un menu handle o una control ID mediante il parametro lpClassName. Se e' il nome di una window class predefinita, hMenu e' una control ID. Altrimenti, e' un handle del menu della finestra.
hInstance: L'instance handle del program module che crea la finestra.
lpParam: Puntatore opzionale ad una struttura di dati passata alla finestra. E' usato dalle finestre MDI per passare i dati CLIENTCREATESTRUCT. Normalmente, questo valore e' NULL, a significare che nessun dato e' passato tramite CreateWindow(). La finestra puo' ritrovare il valore di questo parametro tramite la chiamata alla funzione GetWindowLong.
invoke ShowWindow, hwnd,CmdShow
invoke UpdateWindow, hwnd
Al ritorno con successo da CreateWindowEx, l'handle e' presente in eax. Dobbiamo conservare questo valore per un uso futuro. La finestra che abbiamo appena creato non e' mostrata automaticamente. Dovete chiamare ShowWindow con l'handle della finestra e il desiderato *display state* della finestra per far si che essa venga mostrata su schermo. Successivamente potete chiamare UpdateWindow per ordinare alla vostra finestra di ridisegnare la sua client area. Questa funzione e' utile quando volete aggiornare il contenuto della client area. Potete comunque omettere questa call.
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDW
A questo punto, la nostra finestra e' presente sullo schermo. Ma non puo' ricevere input dal mondo. Percio' dobbiamo *informarla* di eventi rilevanti. Facciamo cio' con un message loop. C'e' un unico message loop per ogni modulo. Questo message loop controlla continuamente se ci sono messaggi da Windows con la call a GetMessage. GetMessage passa un puntatore ad una struttura MSG a Windows. Questa struttura MSG sara' riempita con informazioni inerenti il messaggio che Windows vuole inviare ad una finestra nel modulo. La funzione GetMessage non ritornera' finche' non c'e' un messaggio per una finestra nel modulo. Durante quel lasso di tempo, Windows puo' dare il controllo ad altri programmi.can give control to other programs. Questo e' il principio che delinea lo schema di multitasking cooperativo della piattaforma Win16. GetMessage restituisce FALSE se viene ricevuto il messaggio WM_QUIT, che nel message loop, terminera' il loop ed uscira' dal programma.
TranslateMessage e' una funzione che prende il semplice input dalla tastiera e genera un nuovo messaggio (WM_CHAR) che viene aggiunto alla coda dei messaggi. Il messaggio con WM_CHAR contiene il valore ASCII del tasto premuto, con il quale e' sicuramente piu' semplice interagire che con lo scan code del tasto premuto sulla tastiera. Potete omettere questa call se il vostro programma non elabora pressioni di tasti.
DispatchMessage manda i dati del messaggio alla window procedure responsabile per la specifica finestra a cui e' destinato il messaggio.
ret
WinMain endp
Se il message loop termina, l'exit code e' presente nel membro wParam della struttura MSG. Puoi conservare questo exit code in eax per restituirlo a Windows. Al momento, Windows non fa uso del valore di ritorno, ma e' meglio essere sicuri e giocare secondo le regole.
WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
Questa e' la procedura della nostra finestra, la nostra window procedure. Non siete obbligati a chiamarla WndProc. Il primo parametro, hWnd, e' l'handle della finestra a cui il messaggio e' destinato. uMsg e' il messaggio. Notate che uMsg non e' una struttura MSG. E' solo un numero, davvero. Window definisce centinaia di messaggi, la maggior parte dei quali non interesseranno mai il vostro programma. Windows mandera' un appropriato messaggio ad una finestra nel caso in cui qualcosa di rilevante accada a quella finestra. La window procedure riceve il messaggio e reagisce di conseguenza. wParam ed lParam sono solo parametri extra usati da alcuni message. Alcuni messaggi non mandano nessun dato o valore assieme al messaggio stesso. Questi dati sono passati alla window procedure tramite lParam e wParam.
.IF eax==WM_DESTROY
invoke PostQuitMessage,NULL
xor eax,eax
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
.ENDIF
ret
WndProc endp
Ecco la parte fondamentale. Qui e' dove risiede la gran parte dell'intelligenza del vostro programma. Il codice che risponde ad ogni messaggio di Windows risiede nella window procedure. Il vostro codice deve controllare i messaggi di Windows per vedere se vi e' interessato o no. Se lo e', fate qualsiasi cosa vogliate in risposta a quel messaggio e quindi ritornate con zero in eax. Se non lo e', voi DOVETE passare TUTTI i parametri per l'elaborazione di default del messaggio, eseguita da DefWindowProc. Questa DefWindowProc e' una funzione API che elabora i messaggi a cui il vostro programma non e' interessato. L'unico messaggio a cui DOVETE rispondere e' WM_DESTROY. Questo messaggio e' inviato alla vostra window procedure ogni qual volta la vostra finestra viene chiusa. Nel momento in cui la vostra window procedure riceve questo messaggio, la vostra finestra viene rimossa dallo schermo. Questa e' solo una notifica del fatto che la vostra finestra e' ormai distrutta, e voi dovreste prepararvi a ritornare a Windows. In risposta a questo, potete sbrigare le "faccende di casa" prima di ritornare a Windows. Non avete scelta se non uscire quando si arriva a questo punto. Se volete avere la possibilita' di fermare l'utente dal chiudere la vostra finestra, dovete elaborare il messaggio WM_CLOSE. Ma torniamo a WM_DESTROY. Dopo aver sbrigato le faccende di casa, dovete chiamare PostQuitMessage che rimandera' il messaggio WM_QUIT indietro al vostro module. WM_QUIT fara' si che GetMessage ritorni con il valore zero in eax, che, a sua volta, terminera' il message loop e uscira' a Windows. Potete mandare il messaggio WM_DESTROY alla vostra stessa window procedure chiamando la funzione DestroyWindow.
Note finali
Questi tutorials erano presenti nel sito di RingZero. Li rimettiamo a disposizione a chiunque voglia poter leggerli nella loro traduzione in italiano che fu curata da NeuralNoise.
phobos
Disclaimer
I documenti qui pubblicati sono da considerarsi pubblici e liberamente distribuibili, a patto che se ne citi la fonte di provenienza. Tutti i documenti presenti su queste pagine sono stati scritti esclusivamente a scopo di ricerca, nessuna di queste analisi è stata fatta per fini commerciali, o dietro alcun tipo di compenso. I documenti pubblicati presentano delle analisi puramente teoriche della struttura di un programma, in nessun caso il software è stato realmente disassemblato o modificato; ogni corrispondenza presente tra i documenti pubblicati e le istruzioni del software oggetto dell'analisi, è da ritenersi puramente casuale. Tutti i documenti vengono inviati in forma anonima ed automaticamente pubblicati, i diritti di tali opere appartengono esclusivamente al firmatario del documento (se presente), in nessun caso il gestore di questo sito, o del server su cui risiede, può essere ritenuto responsabile dei contenuti qui presenti, oltretutto il gestore del sito non è in grado di risalire all'identità del mittente dei documenti. Tutti i documenti ed i file di questo sito non presentano alcun tipo di garanzia, pertanto ne è sconsigliata a tutti la lettura o l'esecuzione, lo staff non si assume alcuna responsabilità per quanto riguarda l'uso improprio di tali documenti e/o file, è doveroso aggiungere che ogni riferimento a fatti cose o persone è da considerarsi PURAMENTE casuale. Tutti coloro che potrebbero ritenersi moralmente offesi dai contenuti di queste pagine, sono tenuti ad uscire immediatamente da questo sito.
Vogliamo inoltre ricordare che il Reverse Engineering è uno strumento tecnologico di grande potenza ed importanza, senza di esso non sarebbe possibile creare antivirus, scoprire funzioni malevoli e non dichiarate all'interno di un programma di pubblico utilizzo. Non sarebbe possibile scoprire, in assenza di un sistema sicuro per il controllo dell'integrità, se il "tal" programma è realmente quello che l'utente ha scelto di installare ed eseguire, né sarebbe possibile continuare lo sviluppo di quei programmi (o l'utilizzo di quelle periferiche) ritenuti obsoleti e non più supportati dalle fonti ufficiali.
Categories: Assembly | Iczelion | 2001