Assembly Windows 32bit con MASM

Capitolo 6: La gestione del testo


In questo capitolo vengono analizzati i principali strumenti che win32 ci mette a disposizione per l'output delle stringhe di testo sullo schermo; per verificare in pratica i vari concetti esposti nel capitolo, viene utilizzato un apposito programma denominato FONTWIN.ASM mostrato in Figura 6.1. Analizzando il contenuto del file FONTWIN.ASM mostrato in Figura 6.1, si può subito notare che, come era stato anticipato nel precedente capitolo, è stato riciclato praticamente il 100% del codice sorgente presente nel file MAINWIN.ASM; questa caratteristica dei programmi per Windows, viene largamente sfruttata dai programmatori che vogliono risparmiare parecchio tempo nella scrittura del codice sorgente. Supponiamo ad esempio di avere a disposizione il codice sorgente di MAINWIN.ASM e di voler scrivere un nuovo programma chiamato FONTWIN.ASM; invece di ricominciare da zero, conviene aprire con un editor il file MAINWIN.ASM, salvarlo sul disco con il nuovo nome FONTWIN.ASM e apportare in seguito tutte le necessarie modifiche.

Cominciamo allora con l'analizzare proprio le principali modifiche apportate al file FONTWIN.ASM; prima di tutto, notiamo che le procedure dell'SDK che gestiscono stringhe ASCII, vengono chiamate senza la A finale (LoadIcon, LoadCursor, MessageBox, etc). Come è stato detto nel precedente capitolo, nei vari include files dell'SDK, tutte le procedure che gestiscono stringhe ASCII hanno nomi che vengono ridichiarati senza la A finale, come ad esempio: In questo modo, se dimentichiamo di scrivere la A finale, viene chiamata la versione ASCII della procedura; da questo capitolo in poi, la A finale di questi nomi verrà sempre omessa.

Il blocco dati inizializzati (_DATA) e il blocco dati non inizializzati (_BSS) di FONTWIN.ASM, assumono l'aspetto mostrato in Figura 6.2. Come si può notare, per le variabili principali come className, winTitle, etc, si è proceduto semplicemente a sostituire la stringa MainWin con la stringa FontWin; tutte le altre variabili definite nel blocco _DATA e nel blocco dati non inizializzati _BSS, vengono descritte nel seguito del capitolo.

Passiamo ora alle modifiche apportate nella fase di inizializzazione della main window; come già sappiamo, questa fase consiste nel riempimento di una struttura di tipo WNDCLASSEX.
Per il campo hIcon (handle dell'icona), viene utilizzata l'icona predefinita individuata dal codice IDI_INFORMATION; questa icona è la stessa che viene mostrata in una MessageBox con il codice MB_ICONINFORMATION.
Per il campo hCursor (handle del cursore del mouse), viene utilizzato il cursore predefinito individuato dal codice IDC_HAND; in questo modo, il cursore del mouse assume la forma di una mano.
Indubbiamente, le modifiche più interessanti riguardano il campo hbrBackground che come sappiamo, ci permette di assegnare il colore di sfondo alla finestra che stiamo inizializzando; nel precedente capitolo abbiamo visto che questo campo è una DWORD che contiene un codice di tipo HBRUSH. Questo codice rappresenta l'handle del pennello (brush = spazzola, pennello) che verrà utilizzato per colorare lo sfondo; se non abbiamo esigenze particolari, possiamo utilizzare uno dei tanti colori predefiniti di Windows. Nel precedente capitolo ad esempio, abbiamo utilizzato il colore predefinito COLOR_WINDOW+1 che rappresenta il colore standard per lo sfondo delle finestre; questo colore di sfondo viene assegnato da Windows a tutte le finestre che non fanno richiesta di colori personalizzati. Per modificare il colore per lo sfondo standard, bisogna cliccare sul desktop con il pulsante destro del mouse e selezionare Proprietà; nella finestra che compare bisogna poi selezionare Aspetto.
Nel caso dell'applicazione FONTWIN, viene utilizzato un colore personalizzato per lo sfondo della main window; prima di analizzare il procedimento da seguire, è necessario illustrare il metodo che viene impiegato da Windows per la gestione dei colori.
Come si sa dalla fisica ottica, è possibile ottenere un qualsiasi colore a partire dai tre colori rosso, verde e blu; per questo motivo, questi tre colori vengono definiti colori fondamentali. Per ottenere un determinato colore, bisogna miscelare tra loro i tre colori fondamentali dopo aver scelto opportunamente la tonalità di rosso, la tonalità di verde e la tonalità di blu; nel mondo del computer, i colori ottenuti in questo modo vengono chiamati colori RGB. La sigla RGB significa Red Green Blue (rosso, verde blu); si usa anche la definizione di terna RGB.
In Windows le terne RGB vengono gestite attraverso il tipo di dato COLORREF; un valore di tipo COLORREF è una DWORD che assume la struttura mostrata dalla Figura 6.3. Come possiamo notare, i bit da 0 a 7 contengono la tonalità di rosso, i bit da 8 a 15 contengono la tonalità di verde, i bit da 16 a 23 contengono la tonalità di blu, mentre i bit da 24 a 31 contengono il valore del "canale alfa" (Alpha Channel); il canale alfa viene utilizzato dai programmi di grafica per gestire l'effetto trasparenza delle immagini. Nel caso dei colori di sfondo delle finestre, il canale alfa non viene utilizzato, e quindi i bit da 24 a 31 di un COLORREF dovrebbero valere sempre zero; ci restano quindi a disposizione 24 bit (da 0 a 23) per definire numerosissimi colori personalizzati. Osserviamo che per definire la tonalità di ciascuno dei tre colori fondamentali, abbiamo a disposizione 8 bit; con 8 bit possiamo rappresentare 28=256 valori differenti. In totale quindi con 3*8=24 bit possiamo rappresentare:
28 * 28 * 28 = 28+8+8 = 224 = 16777216 colori = 16M colori
Naturalmente, sarà possibile visualizzare tutti i 16M di colori solo se abbiamo a disposizione una scheda video che gestisce 16M colori; in questo caso, tecnicamente si parla di schede video a 24 bit o anche di colori a 24 bit. Se oltre ai 16M di colori vogliamo gestire anche il canale alfa, dobbiamo disporre ovviamente di una scheda video a 32 bit; se il nostro computer è dotato di scheda video con caratteristiche inferiori (64K colori, 32K colori, etc), allora i colori non disponibili verranno "approssimati" da Windows. In pratica, se selezioniamo un colore non supportato dalla nostra scheda video, Windows ci fornisce il colore più somigliante a quello richiesto; è chiaro quindi che tanto maggiore è il numero di colori supportato dalla nostra scheda video, tanto migliore sarà la qualità delle immagini gestite attraverso Windows.
Per creare facilmente un dato di tipo COLORREF, possiamo scrivere ad esempio la seguente macro: Questa macro non fa altro che disporre nell'ordine giusto i tre valori intRed, intGreen e intBlue; alla fine il registro EAX contiene un dato in formato COLORREF.
Osserviamo che: Una volta chiariti questi aspetti, vediamo come si deve procedere per assegnare ad esempio il colore (0, 150, 150) allo sfondo della main window dell'applicazione FONTWIN; il problema da affrontare riguarda il fatto che il campo hbrBackground della struttura WNDCLASSEX richiede un HBRUSH e non un COLORREF. Per risolvere questo problema ci viene incontro la procedura CreateSolidBrush definita nella libreria GDI32; il prototipo di questa procedura è:
HBRUSH CreateSolidBrush(COLORREF crColor);
In pratica questa procedura riceve in input un argomento crColor di tipo COLORREF e lo converte in un HBRUSH che ci consente di "dipingere" con il colore richiesto; il codice di tipo HBRUSH come al solito viene restituito in EAX. In base a queste considerazioni possiamo scrivere (versione MASM): A questo punto possiamo divertirci a colorare lo sfondo della finestra con tutti i colori disponibili; naturalmente, il colore di sfondo va ad interessare solamente la client area della finestra, mentre le altre parti (bordo, barra del titolo, etc) vengono colorate da Windows con i colori di sistema.
A titolo di curiosità possiamo fare degli esperimenti anche con la procedura CreateHatchBrush definita sempre nella libreria GDI32; il prototipo di questa procedura è:
HBRUSH CreateHatchBrush(int fnStyle, COLORREF crColor);
Questa procedura converte un COLORREF in un HBRUSH che ha la caratteristica di essere non un pennello "solido" (solid brush), ma un pennello "retinato" (hatch brush); il parametro fnStyle è una DWORD che codifica appunto lo stile di retinatura. Per conoscere i vari stili disponibili possiamo consultare il Win32 Programmer's Reference o l'include file windows.inc (i vari codici iniziano per HS = hatch style). Per ottenere ad esempio una retinatura incrociata a 45 gradi, possiamo scrivere:
invoke CreateHatchBrush, HS_DIAGCROSS, eax
(con EAX che deve contenere come al solito un COLORREF); nei capitoli successivi vengono illustrate anche altre procedure che permettono di creare sfondi più sofisticati. Esaminiamo ora le modifiche apportate nella fase di creazione della main window; questa fase come sappiamo, viene gestita attraverso la procedura CreateWindowEx. Rispetto al caso di MAINWIN.ASM, le uniche modifiche apportate riguardano i parametri x, y, nWidth e nHeight che specificano le coordinate iniziali del vertice in alto a sinistra della main window e le dimensioni orizzontale e verticale della main window stessa; come si può notare, vengono utilizzati i valori x=10, y=10, nWidth=600 e nHeight=550. Nel precedente capitolo, per questi parametri abbiamo utilizzato la costante simbolica CW_USEDEFAULT che lascia la scelta a Windows; se non si hanno esigenze particolari, conviene sempre utilizzare CW_USEDEFAULT che permette a Windows di stabilire i valori più opportuni in base alla risoluzione grafica dello schermo.
Come è stato spiegato nel precedente capitolo, se la procedura CreateWindowEx termina con successo, ci restituisce in EAX l'handle della finestra appena creata; inoltre, prima di terminare, CreateWindowEx invia alla window procedure il messaggio WM_CREATE. L'applicazione FONTWIN intercetta questo messaggio per effettuare determinate inizializzazioni; tutti i dettagli relativi a questo aspetto vengono illustrati più avanti.

6.1 Modifiche apportate alla Window Procedure

Nel precedente capitolo abbiamo visto che il cuore di un'applicazione Windows è rappresentato sicuramente dalla procedura di finestra (window procedure); ogni finestra di un programma è dotata di un'apposita window procedure. Attraverso la window procedure, Windows invia i messaggi alla relativa finestra; il nostro compito all'interno della window procedure, è quello di decidere quali messaggi accettare e quali invece rifiutare restituendoli al mittente. Restituire un messaggio al mittente significa passarlo alla procedura DefWindowProc che è la window procedure predefinita di Windows; all'interno di DefWindowProc vengono effettuate tutte le elaborazioni predefinite relative ai vari messaggi in arrivo. Abbiamo anche visto che esistono particolari messaggi che dopo essere stati intercettati ed elaborati dalla nostra window procedure, devono essere passati ugualmente a DefWindowProc; questo è ad esempio il caso dei due messaggi WM_CREATE e WM_CLOSE.
Per applicazioni relativamente semplici, tutto il codice per l'elaborazione dei vari messaggi può essere inserito all'interno della window procedure; nel caso dell'esempio MAINWIN.ASM è stata seguita proprio questa strada. Quando però l'applicazione che stiamo scrivendo comincia ad assumere una certa complessità, conviene decisamente cambiare tattica scrivendo una apposita procedura per ogni messaggio che intendiamo elaborare; questo è proprio il metodo seguito nell'esempio FONTWIN.ASM. Per chiarire la situazione, vediamo in Figura 6.4 l'aspetto assunto dalla window procedure WndProc dell'applicazione FONTWIN (versione MASM). Come si può notare, l'applicazione FONTWIN elabora i 4 messaggi WM_CREATE, WM_PAINT, WM_CLOSE e WM_DESTROY; osserviamo anche che come è stato detto in precedenza, i due messaggi WM_CREATE e WM_CLOSE dopo essere stati intercettati ed elaborati da WndProc, vengono passati anche a DefWindowProc attraverso un salto all'etichetta messaggio_restituito. Per motivi di stile e di chiarezza, nella sequenza dei messaggi intercettati dalla window procedure, conviene mettere per primo il messaggio WM_CREATE; per gli stessi motivi, i messaggi WM_CLOSE e WM_DESTROY dovrebbero occupare rispettivamente la penultima e l'ultima posizione (vedi Figura 6.4).
Ciascuno dei messaggi intercettati viene elaborato in un'apposita procedura; come accade per WndProc, possiamo scegliere liberamente i nomi di queste procedure, mentre la lista dei parametri viene imposta obbligatoriamente da Windows. La Microsoft consiglia di utilizzare nomi del tipo Cls_OnMessage, dove Cls sta per class name (nome della classe finestra); al posto di Cls bisogna quindi scrivere il nome della classe finestra, mentre al posto di Message si deve scrivere il nome del messaggio. In questo modo, nel caso dell'applicazione FONTWIN, si ottengono nomi del tipo: FontWin_OnCreate, FontWin_OnClose, FontWin_OnDestroy, etc; in tutti gli esempi presentati nella sezione Assembly Windows 32bit, viene seguito proprio questo procedimento (vedi Figura 6.4).
Il problema fondamentale da affrontare, consiste nel reperire informazioni relative al tipo dei parametri e degli eventuali valori di ritorno di queste procedure; il consiglio migliore che si può dare è quello di procurarsi una copia dell'header file windowsx.h, distribuito insieme ai compilatori C/C++ per Windows. Questo header file è stato introdotto dalla Microsoft ai tempi di Windows95; al suo interno sono presenti i cosiddetti message crackers. Si tratta di una serie di macro attraverso le quali tutto il codice mostrato in Figura 6.4, nel caso di un programma C/C++ può essere semplificato come si vede in Figura 6.5. Come si può notare, la macro HANDLE_MSG ha tre parametri che rappresentano l'handle della finestra (hWnd), il messaggio da elaborare, e l'indirizzo della funzione che elabora il messaggio; ricordiamo che in C/C++ il nome di una funzione rappresenta l'indirizzo della funzione stessa. All'interno dell'header file windowsx.h è presente la dichiarazione: Nel caso ad esempio del messaggio WM_CREATE, attraverso l'operatore di preprocessor ## del C, avviene il concatenamento delle due stringhe HANDLE_ e WM_CREATE in modo da ottenere HANDLE_WM_CREATE; l'espansione della macro produce quindi:
case WM_CREATE: return HANDLE_WM_CREATE((hwnd), (wParam), (lParam), (fn))
In sostanza, la macro HANDLE_MSG chiama a sua volta la macro HANDLE_WM_CREATE. Cercando all'interno del file windowsx.h, ci imbattiamo nella dichiarazione di questa seconda macro che assume la seguente struttura: L'espansione di questa macro porta alla chiamata:
FontWin_OnCreate(hwnd, lParam);
Dal commento presente prima della dichiarazione di HANDLE_WM_CREATE, possiamo ricavare tutte le informazioni relative ai parametri e ai valori di ritorno della procedura Cls_OnCreate; attraverso ulteriori ricerche possiamo trovare i prototipi di tutte le altre procedure che elaborano i vari messaggi. Analizziamo in particolare i prototipi delle procedure relative ai quattro messaggi intercettati da WndProc (Figura 6.4); osserviamo che queste procedure vengono utilizzate solo dalla window procedure, per cui i loro prototipi sono stati dichiarati prima della definizione di WndProc.

6.2 Gestione del messaggio WM_CREATE

La procedura FontWin_OnCreate che elabora il messaggio WM_CREATE, ha il seguente prototipo:
BOOL FontWin_OnCreate(HWND hwnd, LPCREATESTRUCT lpCreateStruct);
Il parametro hwnd rappresenta l'handle della finestra per la quale è stata richiesta la creazione, mentre il parametro lpCreateStruct è l'indirizzo di una struttura di tipo CREATESTRUCT contenente una serie di informazioni addizionali per la creazione della finestra; nel precedente capitolo abbiamo visto che se vogliamo ricorrere a questa struttura, dobbiamo specificare il suo indirizzo attraverso il parametro lpParam di CreateWindowEx. In fase di creazione della finestra, CreateWindowEx spedisce un messaggio WM_CREATE; se decidiamo di intercettare questo messaggio, possiamo reperire l'indirizzo della struttura CREATESTRUCT attraverso il parametro lpParam di WndProc (Figura 6.4). Nel caso dell'applicazione FONTWIN, questa struttura non viene utilizzata, per cui la procedura FontWin_OnCreate viene chiamata con il secondo argomento che vale NULL; la struttura CREATESTRUCT verrà descritta al momento opportuno in un prossimo capitolo.
L'invio del messaggio WM_CREATE da parte della procedura CreateWindowEx assume una notevole importanza; infatti, intercettando questo messaggio possiamo effettuare una serie di inizializzazioni prima che la finestra compaia sullo schermo. Utilizzando la terminologia dei linguaggi ad oggetti come il C++, possiamo dire che la procedura che elabora WM_CREATE rappresenta il costruttore della classe finestra che stiamo creando.
Una osservazione importante riguarda il fatto che il messaggio WM_CREATE viene inviato dall'interno della procedura CreateWindowEx; questo significa che ancora non abbiamo a disposizione il valore di ritorno di CreateWindowEx (handle della finestra) da memorizzare nella variabile globale hWindow definita nel segmento _BSS. La procedura FontWin_OnCreate deve utilizzare quindi l'argomento hWnd inviato a WndProc da CreateWindowEx; in sostanza, all'interno di FontWin_OnCreate, la variabile hWindow non ha ancora nessun significato e non deve quindi essere usata.
Le vecchie versioni di Windows, richiedevano che la procedura Cls_OnCreate dovesse terminare restituendo in EAX il valore zero per indicare il successo della fase di inizializzazione; nelle attuali versioni di win32 questo valore di ritorno viene ignorato, per cui possiamo simbolicamente caricare in EAX il valore TRUE.

6.3 Gestione del messaggio WM_CLOSE

Il messaggio WM_CLOSE viene elaborato dalla procedura FontWin_OnClose; questa procedura ha il seguente prototipo:
void FontWin_OnClose(HWND hwnd);
Il messaggio WM_CLOSE viene inviato dal SO ogni volta che cerchiamo di chiudere una finestra; molte applicazioni hanno la necessità di intercettare questo messaggio per evitare situazioni spiacevoli. Pensiamo ad esempio al caso di un elaboratore di testi (wordprocessor) che contiene informazioni non ancora salvate sul disco; se chiudiamo l'applicazione senza intercettare il messaggio WM_CLOSE, tutte le informazioni non salvate vengono perse senza che l'utente abbia la possibilità di intervenire. Le situazioni di questo genere possono essere evitate intercettando ed elaborando il messaggio WM_CLOSE; l'elaborazione di questo messaggio consiste in genere nella visualizzazione di un messaggio di avvertimento che fornisce all'utente la possibilità di confermare o meno la richiesta di chiusura dell'applicazione. Nel caso in cui la chiusura venga confermata, è importantissimo passare il messaggio WM_CLOSE anche alla window procedure predefinita DefWindowProc; in questo modo il SO può effettuare le necessarie deinizializzazioni e può inviare infine il messaggio WM_DESTROY che autorizza la terminazione dell'applicazione.
Come si nota dal prototipo, la procedura Cls_OnClose richiede un argomento di tipo HWND che rappresenta l'handle della finestra per la quale è stata richiesta la chiusura; la procedura Cls_OnClose termina senza nessun valore di ritorno (void).

6.4 Gestione del messaggio WM_DESTROY

Il messaggio WM_DESTROY viene elaborato dalla procedura FontWin_OnDestroy; questa procedura ha il seguente prototipo:
void FontWin_OnDestroy(HWND hwnd);
Il messaggio WM_DESTROY viene inviato dal SO per autorizzare la chiusura di una finestra; in sostanza, con il messaggio WM_DESTROY il SO ci sta informando del fatto che le varie deinizializzazioni predefinite sono state portate a termine con successo, per cui la chiusura della finestra può avvenire in modo corretto. Intercettando questo messaggio, possiamo effettuare anche noi le nostre deinizializzazioni; questa fase consiste in genere nella restituzione al SO delle varie risorse utilizzate dalla finestra che vogliamo chiudere. Come già sappiamo, win32 e in particolare Windows2000 e WindowsXP, sono in grado di effettuare automaticamente questo lavoro; in ogni caso, per questioni di chiarezza e di stile, è meglio svolgere questa fase in modo esplicito.
Dalle considerazioni appena esposte, risulta evidente il ruolo svolto dalle due procedure Cls_OnCreate e Cls_OnDestroy; così come Cls_OnCreate rappresenta il costruttore della classe finestra che vogliamo creare, allo stesso modo Cls_OnDestroy rappresenta il distruttore della classe finestra che vogliamo "distruggere".
Prima di terminare, Cls_OnDestroy deve chiamare la procedura PostQuitMessage; come già sappiamo, questa procedura inserisce il messaggio WM_QUIT nella coda dei messaggi della finestra che intendiamo chiudere. Nel precedente capitolo abbiamo anche visto che il parametro nExitCode richiesto da PostQuitMessage, rappresenta il codice di uscita (exit code) della nostra applicazione; all'interno del loop dei messaggi, la successiva chiamata di GetMessage determina l'estrazione del messaggio WM_QUIT. In questo caso, la procedura GetMessage restituisce zero in EAX, determinando in questo modo la condizione di uscita dal loop dei messaggi; il contenuto di nExitCode può essere reperito nel campo wParam della struttura di tipo MSG passata a GetMessage.
Come si nota dal prototipo, la procedura Cls_OnDestroy richiede un argomento di tipo HWND che rappresenta l'handle della finestra per la quale è stata richiesta la chiusura; la procedura Cls_OnDestroy termina senza nessun valore di ritorno (void).

6.5 Gestione del messaggio WM_PAINT

Il messaggio WM_PAINT viene elaborato dalla procedura FontWin_OnPaint; questa procedura ha il seguente prototipo:
void FontWin_OnPaint(HWND hwnd);
In Windows il messaggio WM_PAINT assume una importanza fondamentale; questo messaggio viene inviato automaticamente dal SO ogni volta che la client area di una finestra ha bisogno di un aggiornamento. Questa situazione si verifica ad esempio quando modifichiamo le dimensioni orizzontale e verticale di una finestra, quando ripristiniamo una finestra dopo averla ridotta ad icona, quando una finestra viene portata in primo piano dopo essere stata parzialmente coperta da altre finestre, etc; in tutti questi casi, si dice tecnicamente che il contenuto della finestra si "sporca" e deve quindi essere ripristinato. Per poter procedere a questa fase di ripristino, dobbiamo chiaramente intercettare il messaggio WM_PAINT; l'elaborazione del messaggio WM_PAINT ci offre l'opportunità di conoscere alcune importanti caratteristiche di Windows.
In Windows, prima di poter visualizzare qualsiasi cosa nella client area di una finestra, dobbiamo richiedere al SO un cosiddetto Device Context; per capire il significato del Device Context possiamo servirci della Figura 6.6. Supponiamo che il blocco Device (dispositivo), rappresenti la scheda video del nostro computer; in questo caso, il blocco Device Driver (pilota di dispositivo) rappresenta il software sviluppato per Windows dal produttore della scheda video. Attraverso questo software, Windows può accedere direttamente all'hardware della scheda video sfruttandone tutte le caratteristiche; il Device Driver permette quindi a Windows di selezionare le varie modalità video (CGA, EGA, VGA, SVGA, etc), di selezionare il numero di colori da utilizzare, di visualizzare materialmente sullo schermo l'output del nostro programma, e così via. In ambiente DOS, un programma che vuole inviare il suo output allo schermo deve gestire in proprio tutti questi dettagli hardware; in ambiente Windows invece, le applicazioni delegano tutto questo lavoro al Device Context (contesto di dispositivo). Possiamo dire quindi che il Device Context rappresenta un'interfaccia tra le applicazioni Windows e il Device Driver di un dispositivo di output che può essere lo schermo, la stampante, il plotter, etc; in questo modo si ottiene una enorme semplificazione nella scrittura delle applicazioni. L'aspetto negativo è rappresentato dal fatto che tutti questi passaggi, portano ad una inevitabile diminuzione delle prestazioni generali dell'interfaccia grafica di Windows; d'altra parte, in assenza di questi accorgimenti, una applicazione Windows sarebbe libera di mostrare il suo output in qualunque punto dello schermo, anche al di fuori della client area.
In definitiva, se decidiamo di intercettare il messaggio WM_PAINT, prima di "scrivere" qualsiasi cosa sulla client area della nostra finestra dobbiamo procurarci un Device Context; a tale proposito, Windows ci mette a disposizione diverse procedure come BeginPaint e GetDC. È fondamentale che in relazione al messaggio WM_PAINT venga utilizzata esclusivamente la procedura BeginPaint; questa procedura infatti è stata concepita esplicitamente per questo particolare messaggio. La procedura BeginPaint è definita nella libreria USER32; il suo prototipo è il seguente:
HDC BeginPaint(HWND hwnd, LPPAINTSTRUCT lpPaint);
Il parametro hwnd rappresenta come al solito l'handle della finestra nella quale vogliamo scrivere; il parametro lpPaint rappresenta una DWORD contenente l'indirizzo di una struttura di tipo PAINTSTRUCT i cui campi verranno riempiti da BeginPaint. Nell'include file windows.inc, troviamo la seguente dichiarazione della struttura PAINTSTRUCT: Il campo hdc è una DWORD contenente un codice numerico di tipo HDC (handle to device context); questo campo viene quindi riempito da BeginPaint con l'handle del device context che utilizzeremo per disegnare.
Il campo fErase è un valore booleano che indica se deve essere aggiornato anche lo sfondo della finestra; se fErase vale TRUE, verrà aggiornato anche lo sfondo.
Il campo rcPaint è una struttura di tipo RECT (rettangolo) che indica il rettangolo della client area interessato all'aggiornamento; in questo modo Windows ha la possibilità di migliorare le prestazioni del programma in quanto viene aggiornata solo la porzione della finestra che è stata sporcata.
La struttura RECT viene dichiarata nel file windows.inc nel seguente modo: In pratica, left e top rappresentano il vertice in alto a sinistra del rettangolo; right e bottom rappresentano invece il vertice in basso a destra del rettangolo.
I campi fRestore, fIncUpdate e rgbReserved della struttura PAINTSTRUCT sono riservati a Windows e non devono essere assolutamente modificati dall'utente.

La procedura BeginPaint termina restituendo in EAX il device context che avevamo richiesto; se la richiesta fallisce, il registro EAX contiene il valore NULL. Una volta che abbiamo ottenuto il nostro device context, siamo pronti per disegnare nella client area della finestra da aggiornare; in sostanza, elaborare il messaggio WM_PAINT significa dire a Windows in cosa consiste l'output che il nostro programma visualizza nella client area della sua finestra.
Per indicare a Windows che l'elaborazione del messaggio WM_PAINT è terminata, dobbiamo utilizzare la procedura EndPaint definita anch'essa nella libreria USER32; il prototipo di questa procedura è il seguente:
BOOL EndPaint(HWND hwnd, CONST LPPAINTSTRUCT lpPaint);
Il parametro hwnd indica l'handle della finestra appena aggiornata; il parametro lpPaint è una DWORD contenente l'indirizzo della struttura di tipo PAINTSTRUCT precedentemente riempita da BeginPaint. La procedura EndPaint, termina sempre con un valore non nullo in EAX; questo valore può essere tranquillamente ignorato.

Per elaborare il messaggio WM_PAINT utilizziamo quindi la procedura FontWin_OnPaint che, come si nota dal prototipo, richiede come argomento l'handle della finestra da aggiornare, e termina senza nessun valore di ritorno (void); dalle considerazioni appena esposte, possiamo definire il seguente scheletro di una generica procedura Cls_OnPaint (versione MASM): Dopo aver illustrato le caratteristiche generali delle procedure che elaborano i quattro messaggi intercettati dalla window procedure di FONTWIN, possiamo passare all'analisi delle due principali procedure che vengono utilizzate in Windows per l'output di stringhe di testo sullo schermo.

6.6 La procedura TextOut

La prima procedura che andiamo ad esaminare viene chiamata TextOut; questa procedura che viene definita nella libreria GDI32, presenta il seguente prototipo C:
BOOL TextOut(HDC hdc, int nXStart, int nYStart, LPCTSTR lpString, int cbString);
Il parametro hdc rappresenta l'handle del device context che vogliamo usare per disegnare.
Il parametro nXStart è una DWORD che rappresenta l'ascissa del punto della client area dal quale inizia l'output.
Il parametro nYStart è una DWORD che rappresenta l'ordinata del punto della client area dal quale inizia l'output.
Il parametro lpString è una DWORD che rappresenta l'indirizzo di una stringa ASCII, non necessariamente terminata da uno zero; questa è ovviamente la stringa che verrà stampata sullo schermo.
Il parametro cbString è una DWORD che rappresenta il numero di byte (caratteri) che formano la stringa.
Se TextOut termina con successo, restituisce un valore non nullo in EAX; se invece TextOut fallisce, restituisce zero in EAX.

Supponiamo ad esempio di voler stampare la stringa strOut1 definita nel blocco _DATA mostrato in Figura 6.2; approfittando del fatto che questa stringa viene definita staticamente, possiamo determinare facilmente la sua lunghezza con il solito metodo del location counter. A questo punto possiamo visualizzare la stringa strOut1 scrivendo:
invoke TextOut, paintDC, 10, 10, offset strOut1, LEN_STROUT1
In questo modo, la stringa strOut1 viene stampata a partire dal punto (10, 10) della client area; le coordinate sono espresse in pixel e sono riferite al vertice in alto a sinistra della client area. È necessario ricordare che il sistema di riferimento cartesiano dello schermo del computer, ha l'origine nel vertice in alto a sinistra; le ascisse crescono da sinistra verso destra, mentre le ordinate crescono dall'alto verso il basso (in sostanza, l'asse y punta verso il basso).
Analizzando gli effetti prodotti da questa istruzione, ci accorgiamo che l'area della finestra interessata dall'output della stringa, ha subito una modifica dello sfondo; quest'area, prende il nome di sfondo della stringa. In particolare, si osserva che Windows stampa le stringhe utilizzando un colore predefinito per il testo, chiamato foreground color (colore di primo piano); analogamente, lo sfondo della stringa viene riempito con un colore predefinito chiamato background color (colore di sfondo). Generalmente, in assenza di indicazioni da parte del programmatore, Windows utilizza il bianco per lo sfondo, e il nero per il primo piano; naturalmente Windows ci mette a disposizione tutti gli strumenti necessari per modificare sia il colore di sfondo, sia il colore di primo piano.
Per modificare il colore di sfondo possiamo utilizzare la procedura SetBkColor; questa procedura viene definita nella libreria GDI32, ed ha il seguente prototipo:
COLORREF SetBkColor(HDC hdc, COLORREF crColor);
Il parametro hdc rappresenta l'handle del device context che stiamo usando per scrivere.
Il parametro crColor rappresenta il colore in formato COLORREF che vogliamo usare per lo sfondo della stringa da visualizzare.
Se SetBkColor termina con successo, restituisce in EAX il precedente colore di sfondo in formato COLORREF; se SetBkColor fallisce, restituisce in EAX la costante simbolica CLR_INVALID.
Volendo definire ad esempio uno sfondo nero per l'output, possiamo scrivere: Per modificare il colore di primo piano, possiamo utilizzare la procedura SetTextColor. Questa procedura viene definita nella libreria GDI32, ed ha il seguente prototipo:
COLORREF SetTextColor(HDC hdc, COLORREF crColor);
Il significato dei parametri è identico al caso di SetBkColor.
Se SetTextColor termina con successo, restituisce in EAX il precedente colore di primo piano in formato COLORREF; se SetTextColor fallisce, restituisce in EAX la costante simbolica CLR_INVALID.
Volendo usare ad esempio un colore rosso intenso per il testo, possiamo scrivere: Windows ci permette anche di definire con precisione la modalità di modifica dello sfondo dell'area interessata dall'output; a tale proposito possiamo servirci della procedura SetBkMode. Questa procedura viene definita nella libreria GDI32, e presenta il seguente prototipo:
int SetBkMode(HDC hdc, int iBkMode);
Il parametro hdc è il solito handle del device context che stiamo utilizzando per scrivere nella client area della nostra finestra.
Il parametro iBkMode è una DWORD che codifica la modalità di modifica dello sfondo; le due modalità più utilizzate sono espresse dalle due costanti simboliche OPAQUE e TRANSPARENT. La modalità OPAQUE indica che allo sfondo verrà assegnato il colore predefinito di Windows o il colore che eventualmente abbiamo scelto con SetBkColor; la modalità TRANSPARENT indica che l'output non provocherà nessuna modifica dello sfondo (output trasparente).
Se SetBkMode termina con successo, restituisce in EAX il codice della precedente modalità di modifica dello sfondo; se SetBkMode fallisce, restituisce in EAX il valore zero.
Osserviamo che utilizzando SetBkMode con la costante TRANSPARENT, non abbiamo la necessità di preoccuparci del colore di sfondo dell'output; in definitiva, se vogliamo stampare la stringa strOut1 con il colore (0, 250, 0) e senza alterare lo sfondo, possiamo scrivere (versione MASM):

6.7 La procedura DrawText

Se abbiamo bisogno di una gestione più sofisticata dell'output, possiamo servirci della procedura DrawText; questa procedura viene definita nella libreria USER32 e presenta il seguente prototipo:
int DrawText(HDC hdc, LPCTSTR lpString, int nCount, LPRECT lpRect, UINT uFormat);
Il parametro hdc è l'handle del device context che vogliamo utilizzare per l'output.
Il parametro lpString è una DWORD contenente l'indirizzo di una stringa ASCII, non necessariamente terminata da uno zero; questa è la stringa che verrà stampata sullo schermo da DrawText.
Il parametro nCount è una DWORD che indica la lunghezza in byte della stringa da stampare; se lpString contiene l'indirizzo di una stringa C, e vogliamo delegare a DrawText il calcolo della lunghezza della stringa, dobbiamo passare nCount = -1.
Il parametro lpRect è una DWORD contenente l'indirizzo di una struttura di tipo RECT; la stringa da stampare, verrà formattata all'interno di questo rettangolo.
Il parametro uFlags, è una DWORD contenente una serie di flags che indicano il tipo di formattazione che DrawText applicherà alla stringa; i vari flags possono essere combinati tra loro con l'operatore OR dell'Assembly.
Per conoscere i numerosi flags disponibili, conviene consultare il Win32 Programmer's Reference; nell'esempio FONTWIN, viene utilizzata la combinazione di flags:
DT_SINGLELINE OR DT_CENTER OR DT_VCENTER
In base a questa combinazione, all'interno dell'area rettangolare passata a DrawText attraverso lpRect, verrà stampata una stringa su una sola linea (DT_SINGLELINE); questa stringa verrà inoltre centrata orizzontalmente (DT_CENTER) e verticalmente (DT_VCENTER) all'interno del rettangolo.

Supponiamo ad esempio di voler stampare la stringa strOut3 definita nel blocco _DATA di Figura 6.2; come si può notare, si tratta di una stringa C (zero terminated string). Supponiamo inoltre di voler stampare questa stringa esattamente al centro della client area della main window dell'applicazione FONTWIN; per individuare il rettangolo che rappresenta l'intera client area, possiamo utilizzare la procedura GetClientRect. Questa procedura viene definita nella libreria USER32 e presenta il seguente prototipo:
BOOL GetClientRect(HWND hwnd, LPRECT lpRect);
Il parametro hwnd rappresenta l'handle della finestra di cui vogliamo determinare le coordinate della client area.
Il parametro lpRect rappresenta una DWORD contenente l'indirizzo di una struttura di tipo RECT che verrà riempita da GetClientRect con le informazioni richieste; considerando il fatto che le coordinate sono riferite al vertice in alto a sinistra della client area (0, 0), si otterrà ovviamente left=0, top=0, mentre right e bottom conterranno rispettivamente la larghezza e l'altezza della client area.
La prima cosa da fare consiste quindi nel definire una struttura di tipo RECT chiamata ad esempio paintRect; a questo punto, se vogliamo stampare la stringa con il colore (150, 0, 0), possiamo scrivere (versione MASM): Come possiamo notare, per quanto riguarda il colore di sfondo, il colore di primo piano e la modalità di modifica dello sfondo, valgono tutte le considerazioni già svolte per TextOut; in sostanza, anche con DrawText possiamo utilizzare le procedure SetBkMode, SetBkColor e SetTextColor.

Se DrawText termina con successo, restituisce in EAX l'altezza del testo appena stampato; in caso di fallimento DrawText restituisce in EAX il valore zero.

6.8 I font di Windows

Abbiamo visto quindi che in assenza di indicazioni da parte del programmatore, le due procedure TextOut e DrawText utilizzano valori predefiniti per il colore di sfondo, per il colore di primo piano e per la modalità di modifica dello sfondo dell'output; un'altro aspetto importante, è dato dal fatto che queste due procedure stampano il loro output utilizzando un tipo di carattere avente uno stile grafico predefinito. L'aspetto grafico (estetico) di un carattere prende il nome di font; Windows dispone di un numerosissimo insieme di font utilizzabili per conferire alle stringhe un aspetto grafico di elevata qualità.
Se non selezioniamo nessun font particolare, Windows utilizza un font predefinito chiamato font di sistema; generalmente il font predefinito è un tipo di carattere a spaziatura fissa simile a quello utilizzato dal DOS. Se ci serve invece un font più elegante, dobbiamo richiederlo a Windows attraverso l'invio di una serie di opportune informazioni.
Il primo passo da compiere consiste nel definire tutte le caratteristiche grafiche del font che vogliamo selezionare; a tale proposito dobbiamo riempire i campi di una struttura di tipo LOGFONT. Nel file windows.inc troviamo la seguente dichiarazione di questa struttura: Il campo lfHeight indica l'altezza del font.
Il campo lfWidth indica la larghezza media del font; si tratta di un valore medio in quanto esistono caratteri (come la 'm') che sono molto più larghi di altri (come la 'i'). Specificando il valore zero in questo campo, lasciamo che la larghezza più adatta venga calcolata da Windows.
Il campo lfEscapement indica l'angolo che la stringa deve formare rispetto alla linea orizzontale che rappresenta la base dello schermo; questo angolo è espresso in gradi e deve essere moltiplicato per 10. Per richiedere ad esempio un'inclinazione del testo di 45 gradi, dobbiamo caricare nel campo lfEscapement il valore 450.
Il campo lfOrientation indica l'angolo che ogni carattere della stringa deve formare rispetto alla linea orizzontale che rappresenta la base dello schermo; anche questo valore deve essere moltiplicato per 10. Attualmente questa caratteristica non è ancora supportata da Windows.
Il campo lfWeight indica il peso del font, cioè la sua consistenza; il peso deve essere un valore compreso tra 0 e 1000 e può variare solo a salti di 100 unità. Il valore 400 viene indicato come normal (testo normale); il valore 700 viene indicato come bold (testo in grassetto).
Il campo lfItalic è un valore booleano che indica se il testo deve essere stampato in stile italic (inclinato verso destra); se questo campo vale TRUE (non-zero), verrà attivato lo stile italic.
Il campo lfUnderline è un valore booleano che indica se il testo deve essere sottolineato; se questo campo vale TRUE (non-zero), il testo verrà sottolineato.
Il campo lfStrikeOut è un valore booleano che indica se il testo deve essere sbarrato; se questo campo vale TRUE (non-zero), il testo verrà sbarrato.
Il campo lfCharSet indica la nazionalità del set di caratteri da utilizzare; generalmente questo campo vale zero per indicare il set di caratteri ANSI (si può usare anche la costante simbolica ANSI_CHARSET).
Il campo lfOutPrecision indica con quale precisione Windows rispetterà le richieste del programmatore relative ai campi lfHeight, lfWidth, lfEscapement e lfOrientation; il valore di questo campo indica anche a Windows a quale famiglia deve appartenere il font di riserva da utilizzare se il font che abbiamo richiesto non è disponibile. La famiglia di appartenenza del font viene specificata dal campo lfPitchAndFamily; per obbligare ad esempio Windows ad usare solo font True Type (ad alta qualità), dobbiamo specificare nel campo lfOutPrecision il valore rappresentato dalla costante simbolica OUT_TT_ONLY_PRECIS.
Il campo lfClipPrecision indica con quale precisione Windows "taglierà" la porzione di testo che sconfina dalla client area o che viene coperta da un'altra finestra; il taglio delle porzioni di finestra non visibili, viene chiamato tecnicamente clipping. Generalmente per questo campo si può utilizzare la costante simbolica CLIP_DEFAULT_PRECIS.
Il campo lfQuality indica la qualità generale dell'output; possiamo utilizzare ad esempio le costanti simboliche DEFAULT_QUALITY, DRAFT_QUALITY, PROOF_QUALITY, etc.
Il campo lfPitchAndFamily indica il tipo e la famiglia di appartenenza del font che vogliamo selezionare; come è stato detto in precedenza, Windows utilizza queste informazioni per poter reperire eventuali sostituti del font che abbiamo richiesto. I primi 4 bit di questo campo indicano il tipo di font (fisso o variabile); i successivi bit indicano la famiglia di appartenenza (modern, roman, swiss, etc). Come al solito, per combinare tra loro il tipo e la famiglia di un font, si può utilizzare l'operatore OR dell'Assembly; volendo utilizzare ad esempio un font di tipo variabile della famiglia roman, dobbiamo caricare nel campo lfPitchAndFamily il valore:
VARIABLE_PITCH OR FF_ROMAN
Il campo lfFaceName deve contenere il nome utilizzato da Windows per identificare il font da selezionare; tra i vari nomi si possono citare ad esempio Times New Roman, Arial, Courier New, etc (la distinzione tra maiuscole e minuscole non è importante). Come si può notare, questo campo è un vettore formato da LF_FACESIZE byte (TCHAR = BYTE). La costante LF_FACESIZE vale 32, e questo significa che il nome del font non può superare i 32 byte di lunghezza; in questi 32 byte deve trovare posto anche lo zero finale in quanto il nome di un font deve essere espresso sotto forma di stringa C.

Per conoscere l'enorme numero di costanti simboliche utilizzabili con questi campi, si consiglia di consultare il Win32 Programmer's Reference o l'include file windows.inc; è importante che il programmatore utilizzi queste costanti simboliche per sperimentare in pratica l'effetto che esse producono.

Una volta che abbiamo riempito i vari campi della struttura di tipo LOGFONT, dobbiamo procedere con la creazione del font avente le caratteristiche richieste; a tale proposito, viene utilizzata la procedura CreateFontIndirect. Questa procedura viene definita nella libreria GDI32, e presenta il seguente prototipo:
HFONT CreateFontIndirect(CONST LPLOGFONT lplf);
Il parametro lplf è una DWORD contenente l'indirizzo di una struttura di tipo LOGFONT; naturalmente questa struttura deve essere stata già riempita con tutte le informazioni descritte in precedenza.
Se CreateFontIndirect termina con successo, restituisce in EAX l'handle del font (tipo HFONT) appena installato; se CreateFontIndirect fallisce, restituisce in EAX il valore NULL.
Un concetto molto importante in Windows è rappresentato dal fatto che i vari oggetti grafici come i font, le icone, i cursori, le bitmap, etc, non sono disponibili in numero illimitato; non bisogna dimenticare inoltre che in un SO multitasking come Windows, ci possono essere più programmi contemporaneamente in esecuzione, ciascuno dei quali può aver bisogno di un certo numero di oggetti grafici. Tutto ciò significa che le applicazioni Windows devono comportarsi in modo responsabile, evitando di sprecare inutilmente queste importanti risorse; in sostanza, ogni applicazione deve richiedere un determinato oggetto grafico nel momento in cui ne ha effettivamente bisogno, e deve poi restituirlo quando non gli serve più.
Vediamo allora come ci si deve comportare nel caso della risorsa di tipo font; prima di tutto definiamo una variabile globale destinata a contenere l'handle di un font. In Figura 6.2 notiamo appunto la presenza di una variabile chiamata fontHandle; questa variabile viene inizializzata con il valore zero per indicare che inizialmente non contiene nessun handle. Ogni volta che vogliamo utilizzare un nuovo font, dobbiamo restituire a Windows l'eventuale handle del font precedentemente utilizzato; a tale proposito possiamo servirci della procedura DeleteObject che provvede a liberare tutte le risorse associate ad un determinato handle. Questa procedura viene definita nella libreria GDI32, e presenta il seguente prototipo:
BOOL DeleteObject(HGDIOBJ hObject);
Il parametro hObject rappresenta l'handle di una risorsa grafica che può essere un font, una bitmap, un pennello, etc; la procedura DeleteObject restituisce in EAX un valore non nullo quando termina con successo.
Definiamo poi nel segmento dati non inizializzati (_BSS) una struttura di tipo LOGFONT, che nel caso del nostro esempio viene chiamata fontStruct (Figura 6.2); questa struttura naturalmente deve essere riempita con le necessarie informazioni. A questo punto, per creare un nuovo font avente le caratteristiche che abbiamo richiesto, possiamo scrivere (versione MASM): In questo modo otteniamo nella variabile fontHandle, l'handle del nuovo font che abbiamo appena creato; prima di poter utilizzare fisicamente questo font, dobbiamo selezionarlo attraverso la procedura SelectObject. Il compito di SelectObject è quello di dire al nostro device context che il font corrente verrà sostituito da un nuovo font; la procedura SelectObject viene definita nella libreria GDI32, e presenta il seguente prototipo:
HGDIOBJ SelectObject(HDC hdc,	HGDIOBJ hgdiobj);
Il parametro hdc è l'handle del device context che vogliamo utilizzare per l'output.
Il parametro hgdiobj è l'handle dell'oggetto che vogliamo selezionare; nel nostro caso si tratta dell'handle di un font (fontHandle).
Se la procedura SelectObject fallisce, termina restituendo in EAX il valore NULL; se la procedura SelectObject termina con successo, restituisce in EAX l'handle dell'oggetto appena sostituito (nel nostro caso si tratta dell'handle del precedente font). È fondamentale che l'handle restituito in EAX da SelectObject, venga salvato da qualche parte; infatti, quando abbiamo finito il nostro lavoro, dobbiamo chiamare nuovamente SelectObject per ripristinare il font precedente.

Supponiamo ad esempio di voler utilizzare TextOut per visualizzare la stringa strOut1 con il nuovo font che abbiamo creato; prima di tutto definiamo una variabile locale chiamata ad esempio oldFont e destinata a contenere l'handle del font corrente che dovremo poi ripristinare. A questo punto possiamo scrivere (versione MASM): Come si può notare, prima di tutto selezioniamo il nuovo font con SelectObject; l'handle del vecchio font, restituitoci da SelectObject in EAX, viene salvato nella variabile locale oldFont. In seguito viene chiamata TextOut che stampa la stringa strOut1 con il nuovo font; al termine di questa fase, procediamo a ripristinare il vecchio font attraverso una nuova chiamata a SelectObject.

6.9 Implementazione di FONTWIN.ASM

Dopo aver illustrato gli aspetti teorici relativi alla gestione del testo in Windows, possiamo passare all'implementazione pratica di questi concetti nell'applicazione FONTWIN; il programma può essere suddiviso in tre blocchi fondamentali che gestiscono le fasi di inizializzazione, invio dell'output alla client area e deinizializzazione.
È chiaro che il luogo più adatto nel quale inserire la fase di inizializzazione, è rappresentato dalla procedura FontWin_OnCreate che viene chiamata da WndProc in risposta al messaggio WM_CREATE; in particolare, all'interno di questa procedura possiamo inserire anche l'inizializzazione della struttura di tipo LOGFONT. Nel blocco dati non inizializzati _BSS, definiamo quindi la struttura fontStruct; bisogna tenere presente che non è obbligatorio riempire tutti i campi di questa struttura (come ad esempio lfWidth). È importante però che tutti i campi non inizializzati, vengano riempiti con degli zeri; in questo modo Windows può inizializzare questi campi con dei valori predefiniti. Il procedimento più sicuro consiste allora nell'inizializzare tutta la struttura fontStruct riempiendo tutti i suoi campi con degli zeri; a tale proposito possiamo scrivere un'apposita procedura MemSet che assume il seguente aspetto: Il parametro ptrMem rappresenta l'indirizzo del blocco di memoria da inizializzare; il parametro fillValue rappresenta il valore di inizializzazione, mentre il parametro fillSize indica la dimensione in byte del blocco di memoria da riempire. Come si può notare, MemSet utilizza l'istruzione STOSB; in modalità protetta a 32 bit, questa istruzione utilizza AL come operando sorgente, e [EDI] come operando destinazione. MemSet preserva il contenuto originale di EDI in quanto, come è stato detto in un precedente capitolo, in win32, le procedure di callback devono sempre preservare il contenuto dei registri EBX, ESI e EDI; osserviamo che (come viene mostrato in seguito), MemSet viene chiamata da FontWin_OnCreate che a sua volta viene chiamata da WndProc che è una procedura di callback. I parametri di MemSet sono tutti di tipo DWORD perché, come già sappiamo, in win32 le istruzioni PUSH e POP devono lavorare esclusivamente con operandi a 32 bit; naturalmente, nel caso del parametro fillValue viene sfruttato solo il suo byte meno significativo. La procedura MemSet può essere utilizzata per inizializzare qualunque blocco di memoria; è consigliabile inizializzare con MemSet tutte le strutture utilizzate da una applicazione (come ad esempio la struttura di tipo WNDCLASSEX).
Una volta definiti questi aspetti, possiamo procedere con l'implementazione della procedura FontWin_OnCreate; l'aspetto assunto da questa procedura è il seguente (versione MASM): Come è stato detto in questo capitolo, il campo lfFaceName della struttura fontStruct, è un vettore di 32 byte che deve contenere sotto forma di stringa C, il nome del font che vogliamo creare; nel caso del nostro esempio, viene definita un'apposita variabile fontName che punta alla stringa:
'Times New Roman', 0
(Figura 6.2). Questa stringa C deve essere copiata nel vettore lfFaceName; a tale proposito, possiamo scriverci un'apposita procedura StrCpy che assume il seguente aspetto: Il parametro strFrom rappresenta l'indirizzo della stringa sorgente; il parametro strTo rappresenta l'indirizzo della stringa destinazione. Ricordiamo che in modalità protetta a 32 bit, qualunque registro generale può essere utilizzato come registro puntatore; il test di fine stringa viene effettuato in fondo al loop in modo che venga copiato in strTo anche lo zero di fine stringa. È chiaro che il vettore destinazione deve essere in grado di contenere il vettore sorgente; nel nostro caso, la stringa C che rappresenta il nome del font non deve superare i 32 caratteri (compreso lo zero finale).
Dopo aver inizializzato fontStruct, la procedura FontWin_OnCreate chiama una MessageBox per informare l'utente che le inizializzazioni sono state effettuate; questa MessageBox viene mostrata prima della creazione della main window di FONTWIN, e quindi deve utilizzare l'handle hwnd passato da CreateWindowEx a WndProc che a sua volta lo passa a FontWin_OnCreate. La procedura FontWin_OnCreate termina restituendo in EAX il valore simbolico TRUE, che generalmente viene ignorato da Windows.

Terminata l'elaborazione del messaggio WM_CREATE, la main window di FONTWIN (se tutto procede bene) viene creata da CreateWindowEx, visualizzata da ShowWindow e aggiornata da UpdateWindow; in particolare, la procedura UpdateWindow invia il messaggio WM_PAINT che viene intercettato ed elaborato dal nostro programma.
L'elaborazione del messaggio WM_PAINT si svolge all'interno della procedura FontWin_OnPaint; osservando il sorgente dell'applicazione FONTWIN, si nota che questa procedura definisce le seguenti variabili locali: Queste variabili vengono dichiarate localmente in quanto sono necessarie solamente all'interno di FontWin_OnPaint; come è stato spiegato in precedenza, il primo passo che questa procedura deve compiere consiste nel procurarsi un device context che verrà utilizzato per scrivere nella client area. Il codice necessario è il seguente: In possesso di paintDC, la procedura FontWin_OnPaint può stampare la prima stringa strOut1 con il seguente codice: Come si può notare, viene selezionata con SetBkMode la modalità trasparente che non altera lo sfondo dell'output; questa modalità vale per tutto l'output prodotto da FontWin_OnPaint. Siccome non abbiamo ancora selezionato nessun font particolare, TextOut utilizza il font di sistema di Windows.
Il prossimo passo compiuto da FontWin_OnPaint consiste nel selezionare un font personalizzato che verrà usato per stampare 8 volte la stringa strOut2 con 8 valori differenti per il campo lfEscapement. È perfettamente inutile limitarsi a modificare solo questo campo; la modifica infatti non avrà effetto finché non verrà creato un nuovo font con CreateFontIndirect. A tale proposito, possiamo scrivere la seguente procedura: Il procedimento seguito da SetFont è molto importante; prima di tutto viene usato il parametro angolazione per modificare il campo lfEscapement. Subito dopo viene verificato il contenuto di fontHandle; se fontHandle è diverso da zero, vuol dire che "punta" ad una risorsa font che dobbiamo restituire a Windows attraverso DeleteObject. Terminata questa fase, possiamo chiamare CreateFontIndirect; infine, l'handle del nuovo font, restituito da questa procedura, viene caricato in fontHandle.
A questo punto, all'interno di FontWin_OnPaint, possiamo scrivere il loop che stampa 8 volte la stringa strOut2: All'interno del loop vengono compiute due operazioni molto importanti; prima di chiamare TextOut, viene selezionato il nuovo font attraverso SelectObject. L'handle del vecchio font restituito da SelectObject viene memorizzato in oldFont; al termine di TextOut, viene chiamata nuovamente SelectObject per riselezionare il vecchio font.
Subito dopo il loop, FontWin_OnPaint chiama DrawText per visualizzare la stringa strOut3 al centro della client area; il codice è il seguente: Siccome strOut3 è una stringa C, passiamo l'argomento -1 per delegare a DrawText il calcolo della lunghezza della stringa; l'ultima fase svolta da FontWin_OnCreate consiste nel segnalare a Windows la fine delle operazioni di disegno:
invoke EndPaint, hwnd, paintStruct
Ogni volta che la main window di FONTWIN viene sporcata, il SO invia il messaggio WM_PAINT; intercettando questo messaggio abbiamo quindi la possibilità di ripristinare costantemente il contenuto della nostra finestra.

Non appena si prova a chiudere la main window di FONTWIN, il SO invia il messaggio WM_CLOSE; ovviamente, nel caso di FONTWIN, chiudere la main window significa chiudere l'intera applicazione. Il messaggio WM_CLOSE viene intercettato da WndProc ed elaborato da FontWin_OnClose; il codice di questa procedura è il seguente: Come si può notare, FontWin_OnClose si limita a chiedere all'utente la conferma della richiesta di chiusura della main window; se la richiesta viene confermata, il messaggio WM_CLOSE viene passato a DefWindowProc che provvede ad eseguire una serie di deinizializzazioni predefinite inviando poi il messaggio WM_DESTROY. Naturalmente, la window procedure WndProc di FONTWIN intercetta questo messaggio e lo passa per l'elaborazione alla procedura FontWin_OnDestroy; dal punto di vista dei linguaggi di programmazione ad oggetti, questa procedura rappresenta il distruttore della classe finestra FontWin. All'interno di FontWin_OnDestroy effettuiamo quindi tutte le necessarie deinizializzazioni; il codice di questa procedura è il seguente: Chiamando DeleteObject con l'argomento fontHandle, permettiamo a Windows di liberare la risorsa font associata a questo handle; in questo modo, questa risorsa viene messa a disposizione di altre applicazioni. Naturalmente, FontWin_OnDestroy prima di terminare, chiama PostQuitMessage che provvede ad inviare alla nostra applicazione il messaggio WM_QUIT; questo messaggio come sappiamo determina l'uscita dal loop dei messaggi e la terminazione del nostro programma.

Codice sorgente per MASM: Esempi capitolo 6 MASM