Finestre in Win32
From UIC
Finestre in Win32
Contents |
| Infos | |
|---|---|
| Author: | Spider |
| Email: | spider_xx87 (AT) hotmail (DOT) com |
| Website: | http://bigspider.has.it/ |
| Date: | ??/10/2001 (dd/mm/yyyy) |
| Level: |
|
| Language: | Italian |
| Comments: | Que: Grazie Spider il tute è davvero interessante |
Con questo tutorial intendo dare uno sguardo a ciò che riguarda la creazione e l'uso di finestre in Windows a 32 bits.
Tools
- Una buona API reference. Essenziale per programmare in Win32. Una la trovate su http://itassembly.cjb.net nella sezione "Documenti vari" (E anche alla UIC...Ma shhhh non lo dite a nessuno mi raccomando :P NdQue)
Essay
In Windows la gestione di finestre e pulsanti è affidata alla cosiddetta GUI (Graphical User Interface). Questo ci permette di risparmiare grandi quantità di codice, evitandoci di dover ogni volta riscrivere tutto il codice per il design e la programmazione delle varie finestre; inoltre la presenza della GUI fa sì che tutti i programmi seguano quello standard, semplificando la vita agli utenti che così non devono imparare tutto da capo per ogni programma.
Per finestre non intendiamo soltanto quelle con la barra di sopra, i menu, il pulsantini per ingrandire, ridurre a icona e chiudere... Con il nome "finestre" intendiamo anche le i pulsanti, le barre degli strumenti, le scritte (statics), le caselle di testo, e quasi tutti ciò che noi possiamo trovare all'interno dei programmi e di Windows stesso.
Windows ci mette a disposizione una gran quantità di API per la gestione della GUI. Se vogliamo creare una semplice finestra, possiamo seguire diversi approcci. Tuttavia abbiamo SEMPRE bisogno di un elemento fondamentale: la procedura di gestione dei messaggi, chiamata WndProc o WindowProc (anche se nel vostro programma potete chiamarla come vi pare, tanto quel nome lo vedrete solo voi :P). La WndProc riceve i messaggi che il sistema operativo manda alla nostra finestra. Ad esempio, se vogliamo intercettare il momento in cui un utente clicca sulla finestra, dobbiamo processare il messaggio WM_CLICK. La WndProc riceve 4 argomenti. Nell'ordine: hWnd, uMsg, wParam e lParam. hWnd contiene l'handle della finestra di cui ci stiamo occupando; un handle è un numero che, in windows, serve per identificare qualunque oggetto (finestre, brushes, files, ecc.). Ogni finestra avrà il suo handle che permetterà di distinguerla dalle altre.
Normalmente le applicazioni win32 DEVONO runnare in un loop infinito che continuerà fino alla chiusura del programma stesso. Questo loop deve essere simile a questo:
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDW
La chiamata a GetMessage non restituisce il controllo al nostro programma finché non ci sono messaggi destinati alla nostra applicazione. La funzione ritorna FALSE se è stato ricevuto il messaggio WM_QUIT, in modo che il programma potrà uscire dal loop e chiudersi.
TranslateMessage traduce la pressione di un tasto in un messaggio che sarà letto dalla successiva chiamata a GetMessage.
DispatchMessage si occupa di inviare il messaggio alla corretta window procedure.
Per creare una finestra di dialogo possiamo seguire due approcci principali. Diamo un'occhiata ai vari metodi e vediamo quali sono i pregi e i difetti di ogni approccio.
Primo approccio: CreateWindowEx
Il primo metodo, il più semplice, consiste nell'utilizzare la funzione API CreateWindowExA, che ci permette di creare un qualunque tipo di finestra. La CreateWindowExA. Dall'API Reference:
DWORD dwExStyle, // extended window style
LPCTSTR lpClassName, // pointer to registered class name
LPCTSTR lpWindowName, // pointer to window name
DWORD dwStyle, // window style
int x, // horizontal position of window
int y, // vertical position of window
int nWidth, // window width
int nHeight, // window height
HWND hWndParent, // handle to parent or owner window
HMENU hMenu, // handle to menu, or child-window identifier
HINSTANCE hInstance, // handle to application instance
LPVOID lpParam // pointer to window-creation data
);
Come vedete sono ben 12 parametri, ma in realtà è molto semplice. Vediamoli in dettaglio:
dwExStyle - Extended styles, ovvero degli stili di finestra particolari tipo WS_EX_TOPMOST, WS_EX_ACCEPTFILES, ecc. Se non vogliamo stili particolari per questo parametro dovremo passare NULL.
lpClassName - Il nome della classe che vogliamo creare. Ad esempio, se vogliamo creare un pulsante, questo parametro dovrà essere "Button"
lpWindowName - Il nome della finestra. Ad esempio, se creiamo una finestra di dialogo corrisponderà al titolo, o in un bottone al testo contenuto all'interno, ecc.
dwStyle - Gli stili standard della finestra. Consultare una API Reference per una lista completa. Tutte le costanti degli stili iniziano per WS_
x - La posizione orizzontale della finestra.
y - La posizione verticale della finestra.
nWidth - La larghezza della finestra.
nHeight - L'altezza della finestra.
hWndParent - L'handle della finestra genitore, ovvero quella che conterrà la finestra che noi vogliamo creare. Se dobbiamo creare una finestra di dialogo il genitore sarà il desktop; in tal caso è sufficiente passare NULL e il sistema operativo capirà che vogliamo una finestra figlia del desktop. :-)
hMenu - L'handle dell'eventuale menu che vorremo inserire nella nostra finestra. Se non vogliamo mettere menu, deve essere NULL.
hInstance - L'hInstance della nostra applicazione (ma va!...)
lpParam - Per ora vi basti sapere che se passate NULL funzionerà tutto :P lpParam serve per usi un po' più avanzati.
Spero vi sia sorta una domanda... Per creare un bottone si passa "Button" come lpClassName, per un testo si passa "Static"... e per una finestra di dialogo??? Semplice... Prima ci registriamo una classe che chiameremo come più ci piace... Poi la creiamo con CreateWindowEx, passando in lpClassName il nome che avevamo scelto. Per registrare una classe si utilizza RegisterClassEx. Suppongo che abbiate ancora più confusione di prima. Vediamo la descrizione di RegisterClassEx e poi facciamo un esempio che successivamente commenteremo.
CONST WNDCLASSEX *lpwcx // address of structure with class data
);
Si passa un solo parametro... Voi direte: ma è così semplice? No: il parametro passato è l'indirizzo di una struttura WNDCLASSEX, che ora andremo a vedere:
UINT cbSize;
UINT style;
WNDPROC lpfnWndProc;
int cbClsExtra;
int cbWndExtra;
HANDLE hInstance;
HICON hIcon;
HCURSOR hCursor;
HBRUSH hbrBackground;
LPCTSTR lpszMenuName;
LPCTSTR lpszClassName;
HICON hIconSm;
} WNDCLASSEX;
Un'altra mazzata di 12 elementi :) analizziamoli:
cbSize - La larghezza in bytes della struttura. È necessario settarla correttamente, altrimenti non funzionerà un bel niente. Per sapere la larghezza della struttura possiamo utilizzare l'operatore SIZEOF.
style - Specifica lo stile/gli stili della classe. Per una lista completa consultate l'API Reference. I vari stili vanno combinati con l'operatore OR.
lpfnWindowProc - Punta alla Window Procedure della classe.
cbClsExtra e cbWndExtra - Non li ho mai utilizzati, non ho ben chiaro neanch'io quale sia il loro uso... consultate una API Reference =)
hInstance - L'hInstance del nostro programma :)
hIcon - L'icona della nostra classe.
hCursor - Il cursore della classe.
hbrBackGround - Il brush per il background della class.
lpszMenuName - Punta ad una stringa che identifica il nome del menu all'interno delle risorse.
lpszClassName - Specifica il nome della nostra classe, ovvero quello che utilizzeremo per creare la finestra con CreateWindowEx.
hIconSm - Handle di una icona piccola associata alla classe.
Quando non vogliamo passare un parametro ma vogliamo utilizzare il valore standard, è sufficiente passare NULL.
Bene... Dopo la teoria passiamo alla pratica :) ora faremo un esempio in cui venga utilizzato questo primo approccio.
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
WinMain proto :DWORD,:DWORD,:DWORD,:DWORD
.data
ClassName db "FirstWindow",0
AppName db "Wow! La nostra prima finestra!",0
.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke GetCommandLine
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess,eax
WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
mov wc.cbSize,SIZEOF WNDCLASSEX
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
mov wc.lpszMenuName,NULL
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,\
WS_OVERLAPPEDWINDOW,\
CW_USEDEFAULT,CW_USEDEFAULT,258,160,NULL,NULL,\
hInst,NULL
mov ebx,eax
invoke ShowWindow, ebx,SW_SHOWNORMAL
invoke UpdateWindow, ebx
.WHILE TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDW
mov eax,msg.wParam
ret
WinMain endp
WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.IF uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax,eax
ret
WndProc endp
end start
Ora commentiamo le parti interessanti:
mov hInstance,eax
La chiamata a GetModuleHandle ci ritorna l'hInstance della nostra applicazione, che ci servirà per successive chiamate API.
mov CommandLine,eax
GetCommandLine ci ritorna un puntatore ad una stringa contenente la riga di comando della nostra applicazione. In questo caso è totalmente inutile, ma l'ho inclusa perché ritenevo utile documentarla.
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
mov wc.lpszMenuName,NULL
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, offset wc
Questa parte registra la classe della finestra principale del nostro programma, dopo aver ovviamente riempito la struttura wc. LoadIcon serve a caricare un'icona, e in questo caso l'icona definita dalla costante IDI_APPLICATION. LoadCursor carica un cursore, e IDC_ARROW è il cursore standard, il classico cursore a forma di freccetta.
WS_OVERLAPPEDWINDOW,\
CW_USEDEFAULT,CW_USEDEFAULT,258,160,NULL,NULL,\
hInst,NULL
mov ebx,eax
invoke ShowWindow, ebx,SW_SHOWNORMAL
invoke UpdateWindow, ebx
Dopo aver registrato la classe, la creiamo con una chiamata a CreateWindowEx. Lo stile passato è WS_OVERLAPPEDWINDOW. Questo stile crea una finestra con caption (barra del titolo), menu di sistema, ridimensionabile, con pulsanti di ingrandimento e riduzione ad icona. Dopo averla creata mettiamo temporaneamente in ebx il valore di eax, ovvero l'hwnd della finestra. In questo caso non ci serve salvarlo, ma lo utilizzeremo solo per qualche chiamata successiva. Perché proprio in ebx? Perché il valore del registro ebx non sarà MAI modificato da nessuna chiamata API. Le chiamate a ShowWindow ed UpdateWindow servono rispettivamente per visualizzare la finestra e costringerla ad un refresh, ovvero ridisegnarla. In realtà in questo caso avremmo potuto passare WS_VISIBLE come stile della CreateWindowEx e ci saremmo potuti risparmiare ShowWindow ed UpdateWindow.
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDW
mov eax,msg.wParam
ret
WinMain endp
Questo è il loop continuo di cui abbiamo già parlato. Notate ciò che facciamo all'uscita del loop: mettiamo in eax il valore di msg.wParam e ritorniamo (ret). Questo valore srà poi passato come parametro a ExitProcess. In realtà questo valore è ignorato da windows, ma la Microsoft ci dice di fare così, e quindi noi lo facciamo.
.IF uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax,eax
ret
WndProc endp
Questa è la Window Procedure della nostra finestra. È tramite questa procedura che noi possiamo interagire con l'utente, accorgerci se l'utente muove il mouse sulla nostra finestra, se clicca, se la sposta, ed essere notificati di qualunque altro evento degno di nota. La window procedure di una finestra principale deve SEMPRE processare almeno il messaggio WM_DESTROY. Questo messaggio viene inviato non appena l'utente chiude la nostra finestra, e durante la gestione di questo messaggio noi dobbiamo chiamare la PostQuitMessage, che manderà alla funzione GetMessage il messaggio WM_QUIT, provocando un valore di ritorno uguale a 0, che a sua volta provocherà l'uscita dal loop e la chiusura del programma. Da notare che quando la window procedure riceve il messaggio WM_DESTROY essa è già stata rimossa dallo schermo. Se vogliamo intercettare il momento in cui l'utente preme il pulsante di chiusura dobbiamo intercettare il messaggio WM_CLOSE, che ci permetterà di accertarci che l'utente voglia veramente chiudere, e in caso contrario annullare tutto.
Adesso facciamo un altro esempio, dove metteremo anche un pulsante nella finestra principale. :)
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
WinMain proto :DWORD,:DWORD,:DWORD,:DWORD
.data
ClassName db "FirstWindow",0
AppName db "Wow! La nostra prima finestra!",0
BtnClass db "Button",0
BtnCaption db "Pulsante",0
MsgBoxTitle db "MessageBox",0
MsgBoxText db "Hai cliccato sul pulsante!",0
.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke GetCommandLine
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess,eax
WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
mov wc.cbSize,SIZEOF WNDCLASSEX
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
mov wc.lpszMenuName,NULL
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,\
WS_OVERLAPPEDWINDOW,\
CW_USEDEFAULT,CW_USEDEFAULT,260,160,NULL,NULL,\
hInst,NULL
mov ebx,eax
invoke CreateWindowEx,NULL,ADDR BtnClass,ADDR BtnCaption,\
BS_PUSHBUTTON OR WS_VISIBLE OR WS_CHILD,\
47,25,150,70,ebx,NULL,\
hInst,NULL
invoke ShowWindow, ebx,SW_SHOWNORMAL
invoke UpdateWindow, ebx
.WHILE TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDW
mov eax,msg.wParam
ret
WinMain endp
WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.IF uMsg==WM_DESTROY
invoke PostQuitMessage, NULL
.ELSEIF uMsg==WM_COMMAND
invoke MessageBoxA, hWnd,offset MsgBoxText,offset MsgBoxTitle,NULL
.ELSE
invoke DefWindowProc, hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax,eax
ret
WndProc endp
end start
Bene, stavolta commentiamo solo ciò che è nuovo:
WS_OVERLAPPEDWINDOW,\
CW_USEDEFAULT,CW_USEDEFAULT,260,160,NULL,NULL,\
hInst,NULL
mov ebx,eax
invoke CreateWindowEx,NULL,ADDR BtnClass,ADDR BtnCaption,\
BS_PUSHBUTTON OR WS_VISIBLE OR WS_CHILD,\
47,25,150,70,ebx,NULL,\
hInst,NULL
invoke ShowWindow, ebx,SW_SHOWNORMAL
invoke UpdateWindow, ebx
La prima chiamata a CreateWindowEx crea la finestra principale. La seconda, invece crea il pulsante. Come classe stavolta passiamo "Button". Passiamo 3 stili: BS_PUSHBUTTON, che serve a dire a Windows di creare un PushButton (e non, ad esempio, una CheckBox); WS_VISIBLE, senza il quale il bottone non sarebbe visibile se non tramite una chiamata a ShowWindow, ma è sufficiente farlo con la finestra principale; WS_CHILD che è NECESSARIO con tutte le finestre che non sono figlie dirette del desktop, che altrimenti non funzionerebbero correttamente. Non salviamo il valore dell'handle del pulsante appena creato perché in questo caso non ci serve, dato che abbiamo un solo pulsante.
.IF uMsg==WM_DESTROY
invoke PostQuitMessage, NULL
.ELSEIF uMsg==WM_COMMAND
invoke MessageBoxA, hWnd,offset MsgBoxText,offset MsgBoxTitle,NULL
.ELSE
invoke DefWindowProc, hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax,eax
ret
WndProc endp
Anche qui la nostra bella Window Procedure. Stavolta gestiamo anche il messaggio WM_COMMAND, che ci permette di intercettare il momento in cui viene cliccato il nostro pulsante. Se ci fossero stati più pulsanti, sarebbe stato necessario controllare qual è quello interessato tramite l'hwnd, che è contenuto in lParam, mentre wParam contiene un identificativo (ID), che in questo caso non ci interessa ma si renderà necessario conoscere quando utilizzeremo le dialog boxes.
Secondo approccio: le DialogBoxes
Il metodo della creazione di finestre tramite CreateWindowEx può andare bene nel caso di finestre contenenti pochi controlli. Tuttavia c'è un metodo più professionale e più efficace, e che in molti casi risulta più semplice: l'utilizzo delle Dialog Boxes, contenute nelle risorse delle applicazioni.
Per inserire una finestra di dialogo dobbiamo innanzitutto creare il file di risorse per l'applicazione. Esso può anche essere creato a mano con il notepad, ma è più semplice utilizzare un editor di risorse, come ad esempio il Borland Resource Workshop.
Le finestre di dialogo possono contenere al loro interno tutti i controlli che possono essere contenuti negli altri tipi di finestre. Ogni controllo possiede, oltre al normale hwnd, un identificativo (ID), che viene utilizzato in modo simile all'hwnd. Di solito le API che lavorano con i controlli delle Dialog Boxes utilizzano questo ID.
Per comunicare con le finestre figlie della DialogBox si può utilizzare l'API SendDlgItemMessage. La SendDialogItemMessage richiede 5 parametri:
HWND hDlg, // handle of dialog box
int nIDDlgItem, // identifier of control
UINT Msg, // message to send
WPARAM wParam, // first message parameter
LPARAM lParam // second message parameter
);
hDlg - Identifica la Dialog Box che contiene il controllo.
nIDDlgItem - ID del controllo.
Msg - Messaggio da inviare.
wParam - Primo parametro del messaggio.
lParam - Secondo parametro del messaggio.
I messaggi destinati alla nostra Dialog Box vengono inviati ad una procedura chiamata Dialog Box Procedure, molto simile alle normali Window Procedure. Riceve esattamente gli stessi argomenti: hDlg (handle della DialogBox), iMsg (messaggio), wParam (Primo parametro), lParam (secondo parametro). L'unica differenza è che, come valore di ritorno, essa deve restituire TRUE o FALSE. TRUE è il valore da restituire nel caso in cui la funzione processa il messaggio. FALSE viene restituito in caso contrario.
Per utilizzare una DialogBox come finestra principale, si possono utilizzare due metodi. Il primo è quello di utilizzare la dialog per registrare una classe uguale utilizzando RegisterWindowEx. In questo modo si proseguirà poi alla solita maniera, con il loop e tutto il resto. Il secondo modo, più semplice, è quello di creare la DialogBox direttamente come finestra principale.
Ora analizzeremo un esempio di entrambi gli approcci. Questi esempi sono composti di 2 files, perché c'è anche il file di risorse.
<nowiki>= Rsrc.rc =</nowiki>
<nowiki>==================================================================</nowiki>
<nowiki>#</nowiki>define DS_3DLOOK 4
<nowiki>#</nowiki>define DS_MODALFRAME 128
<nowiki>#</nowiki>define DS_CENTER 2048
<nowiki>#</nowiki>#define WS_OVERLAPPED 0
<nowiki>#</nowiki>define WS_VISIBLE 268435456
<nowiki>#</nowiki>define WS_CAPTION 12582912
<nowiki>#</nowiki>define WS_SYSMENU 524288
<nowiki>#</nowiki>define WS_TABSTOP 65536
<nowiki>#</nowiki>define WS_BORDER 8388608
<nowiki>#</nowiki>define WS_VSCROLL 2097152
<nowiki>#</nowiki>define ES_MULTILINE 4
<nowiki>#</nowiki>define ES_READONLY 2048
<nowiki>#</nowiki>define ES_UPPERCASE 8
<nowiki>#</nowiki>define IDM_ESCI 1
<nowiki>#</nowiki>define IDM_FUNZ1 2
<nowiki>#</nowiki>define IDM_FUNZ2 3
<nowiki>#</nowiki>define IDC_BUTTON 100
MyDialog DIALOG 10, 10, 100, 60
STYLE 0x0004 | DS_CENTER | WS_CAPTION | WS_SYSMENU | WS_VISIBLE |
WS_OVERLAPPED | DS_MODALFRAME | DS_3DLOOK
CAPTION "La nostra prima Dialog!!!"
CLASS "DIALOGCLASS"
BEGIN
DEFPUSHBUTTON "Button", IDC_BUTTON, 20,10,60,13
END
MyMenu MENU
{
POPUP "&File"
{
MENUITEM "&Esci",IDM_ESCI
}
POPUP "F&unzioni"
{
MENUITEM "Funzione1",IDM_FUNZ1
MENUITEM "Funzione2",IDM_FUNZ2
}
}
<nowiki>==================================================================</nowiki>
<nowiki>= Dialog1.asm =</nowiki>
<nowiki>==================================================================</nowiki>
.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
include \masm32\M32LIB\masm32.inc
includelib \masm32\M32LIB\masm32.lib
WinMain proto :DWORD,:DWORD,:DWORD,:DWORD
.const
IDM_ESCI equ 1
IDM_FUNZ1 equ 2
IDM_FUNZ2 equ 3
IDC_BUTTON equ 100
.data
ClassName db "DIALOGCLASS",0
DlgName db "MyDialog",0
AppName db "La nostra prima Dialog!",0
MenuName db "MyMenu",0
MsgBoxTitle db "Click!",0
MsgBoxFunz1 db "Hai cliccato su Funzione1!!",0
MsgBoxFunz2 db "Hai cliccato su Funzione2!!",0
MsgBoxButton db "Hai cliccato sul pulsante!!",0
.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke GetCommandLine
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess,eax
WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hDlg:HWND
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,DLGWINDOWEXTRA
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_BTNFACE+1
mov wc.lpszMenuName,OFFSET MenuName
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateDialogParam,hInstance,ADDR DlgName,NULL,NULL,NULL
mov hDlg,eax
invoke ShowWindow, hDlg,SW_SHOWNORMAL
invoke UpdateWindow, hDlg
.WHILE TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke IsDialogMessage, hDlg, ADDR msg
.IF eax ==FALSE
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDIF
.ENDW
mov eax,msg.wParam
ret
WinMain endp
WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.IF uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.ELSEIF uMsg==WM_COMMAND
mov eax,wParam
.IF ax == IDM_ESCI
invoke SendMessageA, hWnd,WM_CLOSE,NULL,NULL
.ELSEIF ax == IDM_FUNZ1
invoke MessageBoxA, hWnd,offset MsgBoxFunz1,offset MsgBoxTitle, NULL
.ELSEIF ax == IDM_FUNZ2
invoke MessageBoxA, hWnd,offset MsgBoxFunz2,offset MsgBoxTitle, NULL
.ELSEIF ax == IDC_BUTTON
invoke MessageBoxA, hWnd,offset MsgBoxButton,offset MsgBoxTitle, NULL
.ENDIF
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax,eax
ret
WndProc endp
end start
E ora le solite spiegazioni:
<nowiki>#</nowiki>define DS_MODALFRAME 128
<nowiki>#</nowiki>define DS_CENTER 2048
<nowiki>#</nowiki>#define WS_OVERLAPPED 0
<nowiki>#</nowiki>define WS_VISIBLE 268435456
<nowiki>#</nowiki>define WS_CAPTION 12582912
<nowiki>#</nowiki>define WS_SYSMENU 524288
<nowiki>#</nowiki>define WS_TABSTOP 65536
<nowiki>#</nowiki>define WS_BORDER 8388608
<nowiki>#</nowiki>define WS_VSCROLL 2097152
<nowiki>#</nowiki>define ES_MULTILINE 4
<nowiki>#</nowiki>define ES_READONLY 2048
<nowiki>#</nowiki>define ES_UPPERCASE 8
<nowiki>#</nowiki>define IDM_ESCI 1
<nowiki>#</nowiki>define IDM_FUNZ1 2
<nowiki>#</nowiki>define IDM_FUNZ2 3
<nowiki>#</nowiki>define IDC_BUTTON 100
MyDialog DIALOG 10, 10, 100, 60
STYLE 0x0004 | DS_CENTER | WS_CAPTION | WS_SYSMENU | WS_VISIBLE |
WS_OVERLAPPED | DS_MODALFRAME | DS_3DLOOK
CAPTION "La nostra prima Dialog!!!"
CLASS "DIALOGCLASS"
BEGIN
DEFPUSHBUTTON "Button", IDC_BUTTON, 20,10,60,13
END
MyMenu MENU
{
POPUP "&File"
{
MENUITEM "&Esci",IDM_ESCI
}
POPUP "F&unzioni"
{
MENUITEM "Funzione1",IDM_FUNZ1
MENUITEM "Funzione2",IDM_FUNZ2
}
}
Questo codice è stato generato da un editor di risorse e successivamente modificato un po' a mano. Io uso il Borland Resource Workshop, ma in giro ne trovate molti altri, come quello incluso nel VC++.
È importante la parola chiave CLASS seguita dal nome della classe che vogliamo creare. Senza di essa, infatti, non potremmo utilizzare questo sistema.
IDM_ESCI equ 1
IDM_FUNZ1 equ 2
IDM_FUNZ2 equ 3
IDC_BUTTON equ 100
Queste costanti sono le stesse inserite nello script di risorse, e ci possono servire per molteplici scopi (ad esempio individuare i click nei menu o nei pulsanti).
ClassName db "DIALOGCLASS",0
DlgName db "MyDialog",0
AppName db "La nostra prima Dialog!",0
MenuName db "MyMenu",0
Lo scopo di queste stringhe è intuibile dal nome, giusto? :-)
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hDlg:HWND
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,DLGWINDOWEXTRA
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_BTNFACE+1
mov wc.lpszMenuName,OFFSET MenuName
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
Questa parte registra una classe identica alla dialog del nostro script. L'unica differenza rispetto agli esempi precedenti è che il membro cbWndExtra viene settato al valore di DLGWINDOWEXTRA, mentre precedentemente avevamo messo NULL. Notate che come ClassName passiamo il nome della CLASS usato nello script di risorse.
mov hDlg,eax
Stavolta per creare la finestra utilizziamo CreateDialogParam invece di CreateWindowEx. L'API CreateDialogParam serve appunto a creare una Dialog basata su un template contenuto nelle risorse di un programma. Vediamo il prototipo della funzione:
HINSTANCE hInstance, // handle to application instance
LPCTSTR lpTemplateName, // identifies dialog box template
HWND hWndParent, // handle to owner window
DLGPROC lpDialogFunc, // pointer to dialog box procedure
LPARAM dwInitParam // initialization value
);
hInstance - Questo lo conosciamo già: è l'handle dell'istanza della nostra applicazione.
lpTemplateName - In questo parametro dobbiamo passare il nome della Dialog, lo stesso nome usato nello script di risorse. Da non confondere con la keyword CLASS!
hWndParent - Handle della finestra genitrice. Generalmente NULL.
lpDialogFung - Puntatore alla window procedure della dialog.
dwInitParam - Valore passato al parametro lParam nel messaggio WM_INITDIALOG. Questo messaggio viene mandato prima della visualizzazione della finestra.
In questo caso per gli ultimi 3 parametri passiamo NULL. Passando NULL ad hWndParent assumiamo come parent la finestra Desktop. Passiamo NULL anche per lpDialogFunc perché la window procedure l'abbiamo già indicata tramite RegisterClassEx. dwInitParam invece in questo caso non ci serve, e avremmo potuto passare qualunque altro valore.
invoke UpdateWindow, hDlg
Come al solito visualizziamo e facciamo il refresh della finestra.
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke IsDialogMessage, hDlg, ADDR msg
.IF eax == FALSE
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDIF
.ENDW
mov eax,msg.wParam
ret
WinMain endp
Stavolta c'è una piccola modifica nel loop. Richiamiamo IsDialogMessage, che si occupa di processare i messaggi destinati alle dialogs. Quando il messaggio non viene processato restituisce FALSE, e quindi ci occupiamo del messaggio chiamando TranslateMessage e DispatchMessage.
.IF uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.ELSEIF uMsg==WM_COMMAND
mov eax,wParam
.IF ax == IDM_ESCI
invoke SendMessageA, hWnd,WM_CLOSE,NULL,NULL
.ELSEIF ax == IDM_FUNZ1
invoke MessageBoxA, hWnd,offset MsgBoxFunz1,offset MsgBoxTitle, NULL
.ELSEIF ax == IDM_FUNZ2
invoke MessageBoxA, hWnd,offset MsgBoxFunz2,offset MsgBoxTitle, NULL
.ELSEIF ax == IDC_BUTTON
invoke MessageBoxA, hWnd,offset MsgBoxButton,offset MsgBoxTitle, NULL
.ENDIF
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax,eax
ret
WndProc endp
E come al solito la Window Procedure, in cui proseguiamo come negli esempi precedenti.
Adesso esaminiamo il secondo approccio. Con questo sistema non sono più necessari il message-loop né la registrazione della class con RegisterClassEx.
<nowiki>= Rsrc.rc =</nowiki>
<nowiki>==================================================================</nowiki>
<nowiki>#</nowiki>define DS_3DLOOK 4
<nowiki>#</nowiki>define DS_MODALFRAME 128
<nowiki>#</nowiki>define DS_CENTER 2048
<nowiki>#</nowiki>define WS_OVERLAPPED 0
<nowiki>#</nowiki>define WS_VISIBLE 268435456
<nowiki>#</nowiki>define WS_CAPTION 12582912
<nowiki>#</nowiki>define WS_SYSMENU 524288
<nowiki>#</nowiki>define WS_TABSTOP 65536
<nowiki>#</nowiki>define WS_BORDER 8388608
<nowiki>#</nowiki>define WS_VSCROLL 2097152
<nowiki>#</nowiki>define ES_MULTILINE 4
<nowiki>#</nowiki>define ES_READONLY 2048
<nowiki>#</nowiki>define ES_UPPERCASE 8
<nowiki>#</nowiki>define IDM_ESCI 1
<nowiki>#</nowiki>define IDM_FUNZ1 2
<nowiki>#</nowiki>define IDM_FUNZ2 3
<nowiki>#</nowiki>define IDC_BUTTON 100
MyDialog DIALOG 10, 10, 100, 50
STYLE 0x0004 | DS_CENTER | WS_CAPTION | WS_SYSMENU | WS_VISIBLE |
WS_OVERLAPPED | DS_MODALFRAME | DS_3DLOOK
CAPTION "La nostra prima Dialog!!!"
MENU MyMenu
BEGIN
DEFPUSHBUTTON "Button", IDC_BUTTON, 20,10,60,13
END
MyMenu MENU
{
POPUP "&File"
{
MENUITEM "&Esci",IDM_ESCI
}
POPUP "F&unzioni"
{
MENUITEM "Funzione1",IDM_FUNZ1
MENUITEM "Funzione2",IDM_FUNZ2
}
}
<nowiki>==================================================================</nowiki>
<nowiki>= Dialog2.asm =</nowiki>
<nowiki>==================================================================</nowiki>
.386
.model flat,stdcall
option casemap:none
DlgProc proto :DWORD,:DWORD,:DWORD,:DWORD
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
.data
DlgName db "MyDialog",0
AppName db "La nostra prima dialog!!",0
MsgBoxTitle db "Click!",0
MsgBoxFunz1 db "Hai cliccato su Funzione1!!",0
MsgBoxFunz2 db "Hai cliccato su Funzione2!!",0
MsgBoxButton db "Hai cliccato sul pulsante!!",0
.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
.const
IDM_ESCI equ 1
IDM_FUNZ1 equ 2
IDM_FUNZ2 equ 3
IDC_BUTTON equ 100
.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke DialogBoxParam, hInstance, ADDR DlgName,NULL, addr DlgProc, NULL
invoke ExitProcess,eax
DlgProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.IF uMsg==WM_CLOSE
invoke SendMessage,hWnd,WM_COMMAND,IDM_ESCI,0
.ELSEIF uMsg==WM_COMMAND
mov eax,wParam
.IF lParam==0
.IF ax==IDM_ESCI
invoke EndDialog, hWnd,NULL
.ELSEIF ax==IDM_FUNZ1
invoke MessageBoxA, hWnd, OFFSET MsgBoxFunz1,\
OFFSET MsgBoxTitle, MB_ICONINFORMATION
.ELSEIF ax==IDM_FUNZ2
invoke MessageBoxA, hWnd, OFFSET MsgBoxFunz2,\
OFFSET MsgBoxTitle, MB_ICONINFORMATION
.ENDIF
.ELSE
mov edx,wParam
shr edx,16
.IF dx==BN_CLICKED
.IF ax==IDC_BUTTON
invoke MessageBoxA, hWnd, OFFSET MsgBoxButton,\
OFFSET MsgBoxTitle, MB_ICONINFORMATION
.ENDIF
.ENDIF
.ENDIF
.ELSE
mov eax,FALSE
ret
.ENDIF
mov eax,TRUE
ret
DlgProc endp
end start
<nowiki>==================================================================</nowiki>
Analizziamo quest'ultimo esempio:
STYLE 0x0004 | DS_CENTER | WS_CAPTION | WS_SYSMENU | WS_VISIBLE |
WS_OVERLAPPED | DS_MODALFRAME | DS_3DLOOK
CAPTION "La nostra prima Dialog!!!"
MENU MyMenu
BEGIN
DEFPUSHBUTTON "Button", IDC_BUTTON, 20,10,60,13
END
Stavolta non abbiamo bisogno della parola chiave CLASS. Notate invece che utilizziamo la parola chiave MENU, che ci permette di settare automaticamente il menu della dialog, cosa che altrimenti dovremmo fare a mano.
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke DialogBoxParam, hInstance, ADDR DlgName,NULL, addr DlgProc, NULL
invoke ExitProcess,eax
Stavolta non abbiamo bisogno di né di WinMain né di message-loop. Semplicemente richiamiamo l'API DialogBoxParam che provvederà a tutto il resto :-)
.IF uMsg==WM_CLOSE
invoke SendMessage,hWnd,WM_COMMAND,IDM_ESCI,0
Se vogliamo utilizzare questo sistema, dobbiamo anche processare il messaggio WM_CLOSE, in quanto il sistema operativo non si occupa di mandare il messaggio WM_DESTROY quando processa un WM_CLOSE. Se non processassimo WM_CLOSE quando l'utente cliccherebbe sulla X di chiusura finestra non succederebbe niente. In questo caso utilizziamo SendMessage per simulare un clic nel sottomenu "Esci". Sarebbe stata la stessa cosa richiamare EndDialog da qui.
mov eax,wParam
.IF lParam==0
.IF ax==IDM_ESCI
invoke EndDialog, hWnd,NULL
.ELSEIF ax==IDM_FUNZ1
invoke MessageBoxA, hWnd, OFFSET MsgBoxFunz1,\
OFFSET MsgBoxTitle, MB_ICONINFORMATION
.ELSEIF ax==IDM_FUNZ2
invoke MessageBoxA, hWnd, OFFSET MsgBoxFunz2,\
OFFSET MsgBoxTitle, MB_ICONINFORMATION
.ENDIF
.ELSE
mov edx,wParam
shr edx,16
.IF dx==BN_CLICKED
.IF ax==IDC_BUTTON
invoke MessageBoxA, hWnd, OFFSET MsgBoxButton,\
OFFSET MsgBoxTitle, MB_ICONINFORMATION
.ENDIF
.ENDIF
.ENDIF
.ELSE
mov eax,FALSE
ret
.ENDIF
mov eax,TRUE
ret
DlgProc endp
Il resto della Dialog Procedure è uguale al solito. Quando clickiamo sul menu Esci viene richiamata l'API EndDialog, che si occupa di terminare la nostra dialog. EndDialog richiede 2 parametri: il primo è l'handle della dialog; il secondo è il valore di ritorno che avrà l'API che abbiamo utilizzato per creare la DialogBox (in questo caso DialogBoxParam).
E con questo si chiude questa piccola guida sulla creazione di finestre in Windows.
- Spider
Note Finali
Ringrazio Albe che mi ha suggerito di scrivere questo tute e Iczelion i cui tute sono sempre un'ottima documentazione :)
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.