Win32 Assembly

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.
Come al solito, per seguire meglio le spiegazioni teoriche, conviene scaricare il codice sorgente e visualizzarlo con l'editor preferito; il codice sorgente e' disponibile nei seguenti files zippati:
Download di tutti gli esempi del capitolo (versione MASM)
Download di tutti gli esempi del capitolo (versione TASM)
Analizzando il contenuto del file FONTWIN.ASM, si puo' subito notare che, come era stato anticipato nel precedente capitolo, e' 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 e' 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:
LoadIconA   PROTO    :DWORD, :DWORD
LoadIcon    equ      < LoadIconA >
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 verra' sempre omessa.

Il blocco dati inizializzati (_DATA) e il blocco dati non inizializzati (_BSS) di FONTWIN.ASM, assumono l'aspetto mostrato in Figura 1.
Figura 1 - Segmento dati di FONTWIN.ASM
_DATA       SEGMENT  DWORD PUBLIC USE32 'DATA'

className      db       'FontWin', 0
winTitle       db       'Win32 Assembly - Applicazione FontWin', 0

msgTitolo      db       'Win 32 Assembly', 0
msgCreate      db       'Procedura FontWin_OnCreate.', 10
               db       'Messaggio WM_CREATE ricevuto!', 10
               db       'Inizializzazioni effettuate.', 0

msgClose       db       'Procedura FontWin_OnClose.', 10       
               db       'Messaggio WM_CLOSE ricevuto!', 10
               db       'Chiudere FONTWIN.EXE ?', 0

strOut1        db       'Output di stringhe di testo in Win32'
LEN_STROUT1    =        $ - offset strOut1
strOut2        db       'True Type'
LEN_STROUT2    =        $ - offset strOut2
strOut3        db       'Testo centrato nella finestra', 0

fontName       db       'Times New Roman', 0
fontHandle     dd       0

_DATA       ENDS

_BSS        SEGMENT  DWORD PUBLIC USE32 'BSS'

hInstance      dd       ?                 ; handle dell'applicazione
commandLine    dd       ?                 ; puntatore alla command line
hWindow        dd       ?                 ; handle della main window

fontStruct     LOGFONT                 ; struttura LOGFONT

_BSS        ENDS
Come si puo' notare, per le variabili principali come className, winTitle, etc, si e' 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 gia' 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 e' 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 piu' 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 e' una DWORD che contiene un codice di tipo HBRUSH. Questo codice rappresenta l'handle del pennello (brush = spazzola, pennello) che verra' 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 Proprieta'; 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, e' necessario illustrare il metodo che viene impiegato da Windows per la gestione dei colori.
Come si sa dalla fisica ottica, e' 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 tonalita' di rosso, la tonalita' di verde e la tonalita' 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 e' una DWORD che assume la struttura mostrata dalla Figura 2.

Come possiamo notare, i bit da 0 a 7 contengono la tonalita' di rosso, i bit da 8 a 15 contengono la tonalita' di verde, i bit da 16 a 23 contengono la tonalita' 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 tonalita' 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, sara' 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 e' 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 piu' somigliante a quello richiesto; e' chiaro quindi che tanto maggiore e' il numero di colori supportato dalla nostra scheda video, tanto migliore sara' la qualita' delle immagini gestite attraverso Windows.
Per creare facilmente un dato di tipo COLORREF, possiamo scrivere ad esempio la seguente macro:
MAKE_COLORREF MACRO intRed, intGreen, intBlue

   xor      eax, eax
   mov      ah, intBlue
   shl      eax, 8
   mov      ah, intGreen
   mov      al, intRed

ENDM
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:

Le terne RGB di tipo (r, 0, 0), con r compreso tra 0 e 255, ci forniscono 256 possibili tonalita' di rosso.
Le terne RGB di tipo (0, g, 0), con g compreso tra 0 e 255, ci forniscono 256 possibili tonalita' di verde.
Le terne RGB di tipo (0, 0, b), con b compreso tra 0 e 255, ci forniscono 256 possibili tonalita' di blu.
Le terne RGB di tipo (r, g, b), con r = g = b compresi tra 0 e 255, ci forniscono 256 possibili tonalita' di grigio, dal nero (0, 0, 0) al bianco (255, 255, 255).

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 e':
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):
MAKE_COLORREF 0, 150, 150           ; crea una terna RGB
invoke   CreateSolidBrush, eax      ; e la converte in HBRUSH
mov      wc.hbrBackground, eax      ; colore di sfondo della finestra
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 curiosita' possiamo fare degli esperimenti anche con la procedura CreateHatchBrush definita sempre nella libreria GDI32; il prototipo di questa procedura e':
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 e' una DWORD che codifica appunto lo stile di retinatura. Per conoscere i vari stili disponibili possiamo consultare il Win32 Programmer's Reference o gli include files windows.inc e win32.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 piu' sofisticati.

Nota importante.
A rigore, per poter utilizzare un pennello non basta ottenere il suo handle; come vedremo piu' avanti, una volta ottenuto un HBRUSH da CreateSolidBrush, dobbiamo abilitare il corrispondente pennello e dobbiamo dire a Windows in quale contesto vogliamo utilizzare questa risorsa. Una volta che abbiamo finito di utilizzare il pennello, dobbiamo restituirlo a Windows in modo che questa risorsa venga resa disponibile anche ad altri programmi in esecuzione; nel caso pero' del pennello utilizzato per definire il colore di sfondo di una client area, il programmatore deve fornire solo un HBRUSH in quanto tutti gli altri dettagli vengono gestiti da Windows.

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 puo' 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 piu' opportuni in base alla risoluzione grafica dello schermo.
Come e' 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 piu' avanti.

6.1 Modifiche apportate alla Window Procedure.

Nel precedente capitolo abbiamo visto che il cuore di un'applicazione Windows e' rappresentato sicuramente dalla procedura di finestra (window procedure); ogni finestra di un programma e' 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, e' quello di decidere quali messaggi accettare e quali invece rifiutare restituendoli al mittente. Restituire un messaggio al mittente significa passarlo alla procedura DefWindowProc che e' 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 e' 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 puo' essere inserito all'interno della window procedure; nel caso dell'esempio MAINWIN.ASM e' stata seguita proprio questa strada. Quando pero' l'applicazione che stiamo scrivendo comincia ad assumere una certa complessita', conviene decisamente cambiare tattica scrivendo una apposita procedura per ogni messaggio che intendiamo elaborare; questo e' proprio il metodo seguito nell'esempio FONTWIN.ASM. Per chiarire la situazione, vediamo in Figura 3 l'aspetto assunto dalla window procedure WndProc dell'applicazione FONTWIN (versione TASM).
Figura 3 - Window procedure WndProc
WndProc proc hWnd :DWORD, uMsg :DWORD, wParam :DWORD, lParam :DWORD

messaggio_WM_CREATE:
   cmp      uMsg, WM_CREATE            ; (uMsg == WM_CREATE) ?
   jne      messaggio_WM_PAINT
   call     FontWin_OnCreate, hWnd, NULL
   jmp      messaggio_restituito       ; passa WM_CREATE a DefWindowProc
messaggio_WM_PAINT:
   cmp      uMsg, WM_PAINT             ; (uMsg == WM_PAINT) ?
   jne      messaggio_WM_CLOSE
   call     FontWin_OnPaint, hWnd
   jmp      exitWndProc
messaggio_WM_CLOSE:
   cmp      uMsg, WM_CLOSE             ; (uMsg == WM_CLOSE) ?
   jne      messaggio_WM_DESTROY
   call     FontWin_OnClose, hWnd
   cmp      eax, IDYES
   jne      exitWndProc
   jmp      messaggio_restituito       ; passa WM_CLOSE a DefWindowProc
messaggio_WM_DESTROY:
   cmp      uMsg, WM_DESTROY           ; (uMsg == WM_DESTROY) ?
   jne      messaggio_restituito
   call     FontWin_OnDestroy, hWnd
   jmp      exitWndProc
messaggio_restituito:
   call     DefWindowProc, hWnd, uMsg, wParam, lParam
   ret
exitWndProc:

   xor      eax, eax
   ret

WndProc endp
Come si puo' notare, l'applicazione FONTWIN elabora i 4 messaggi WM_CREATE, WM_PAINT, WM_CLOSE e WM_DESTROY; osserviamo anche che come e' 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 3).
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 Win32 Assembly, viene seguito proprio questo procedimento (vedi Figura 3).
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 puo' dare e' quello di procurarsi una copia dell'header file windowsx.h, distribuito insieme ai compilatori C/C++ per Windows. Questo header file e' 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 3, nel caso di un programma C/C++ puo' essere semplificato come si vede in Figura 4.
Figura 4 - Struttura di WndProc in C/C++
LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
   switch (uMsg)
   {
      HANDLE_MSG(hWnd, WM_CREATE, FontWin_OnCreate);
      HANDLE_MSG(hWnd, WM_PAINT, FontWin_OnPaint);
      HANDLE_MSG(hWnd, WM_CLOSE, FontWin_OnClose);
      HANDLE_MSG(hWnd, WM_DESTROY, FontWin_OnDestroy);
      default:
         return DefWindowProc(hWnd, uMsg, wParam, lParam);
   }
}
Come si puo' 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 e' presente la dichiarazione:
#define  HANDLE_MSG(hwnd, message, fn) \
         case (message): return HANDLE_##message((hwnd), (wParam), (lParam), (fn))
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:
/* BOOL Cls_OnCreate(HWND hwnd, LPCREATESTRUCT lpCreateStruct) */

#define  HANDLE_WM_CREATE(hwnd, wParam, lParam, fn) \
         ((fn)((hwnd), (LPCREATESTRUCT)(lParam)) ? 0L : (LRESULT)-1L)
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 3); 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 e' stata richiesta la creazione, mentre il parametro lpCreateStruct e' 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 3). 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 verra' 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 necessita' 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 possibilita' 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 possibilita' di confermare o meno la richiesta di chiusura dell'applicazione. Nel caso in cui la chiusura venga confermata, e' importantissimo passare il messaggio WM_CLOSE anche alla window procedure predefinita DefWindowProc; in questo modo il SO puo' effettuare le necessarie deinizializzazioni e puo' 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 e' 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 puo' 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 gia' 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, e' meglio svolgere questa fase in modo esplicito.
Dalle considerazioni appena esposte, risulta evidente il ruolo svolto dalle due procedure Cls_OnCreate e Cls_OnDestroy; cosi' 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 gia' 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 messagio 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 puo' 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 e' 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'opportunita' 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 5. 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 puo' accedere direttamente all'hardware della scheda video sfruttandone tutte le caratteristiche; il Device Driver permette quindi a Windows di selezionare le varie modalita' 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 cosi' 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 puo' essere lo schermo, la stampante, il plotter, etc; in questo modo si ottiene una enorme semplificazione nella scrittura delle applicazioni. L'aspetto negativo e' 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. E' fondamentale che in relazione al messaggio WM_PAINT venga utilizzata esclusivamente la procedura BeginPaint; questa procedura infatti e' stata concepita esplicitamente per questo particolare messaggio. La procedura BeginPaint e' definita nella libreria USER32; il suo prototipo e' 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 contenete l'indirizzo di una struttura di tipo PAINTSTRUCT i cui campi verranno riempiti da BeginPaint. Nell'include file windows.inc o win32.inc, troviamo la seguente dichiarazione della struttura PAINTSTRUCT:
PAINTSTRUCT    STRUC

   hdc            HDC         ?        ; handle del DC da usare
   fErase         BOOL        ?        ; flag aggiornamento sfondo
   rcPaint        RECT        < ? >    ; area di output
   fRestore       BOOL        ?        ; riservato a Win32
   fIncUpdate     BOOL        ?        ; riservato a Win32
   rgbReserved    BYTE 32 dup (?)      ; riservato a Win32
   
PAINTSTRUCT    ENDS
Il campo hdc e' 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 e' un valore booleano che indica se deve essere aggiornato anche lo sfondo della finestra; se fErase vale TRUE, verra' aggiornato anche lo sfondo.
Il campo rcPaint e' una struttura di tipo RECT (rettangolo) che indica il rettangolo della client area interessato all'aggiornamento; in questo modo Windows ha la possibilita' di migliorare le prestazioni del programma in quanto viene aggiornata solo la porzione della finestra che e' stata sporcata.
La struttura RECT viene dichiarata nel file windows.inc o win32.inc nel seguente modo:
RECT     STRUC

   left        LONG     ?  ; ascissa P1
   top         LONG     ?  ; ordinata P1
   right       LONG     ?  ; ascissa P2
   bottom      LONG     ?  ; ordinata P2

RECT     ENDS
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.

Nota importante.
Come al solito, gli utilizzatori del TASM devono affrontare il problema della visibilita' globale dei nomi usati per i vari campi delle strutture; in sostanza, se ad esempio nel nostro programma definiamo una variabile chiamata left, questo nome entra in conflitto con il campo left della struttura RECT. Per evitare questo problema, bisogna modificare i nomi dei campi delle varie strutture predefinite di Windows; se si osserva il file win32.inc si nota che ad esempio, nel caso della struttura RECT, i vari campi sono stati rinominati in rc_left, rc_top, rc_right, rc_bottom.
Naturalmente, le stesse considerazioni valgono per la struttura PAINTSTRUCT e per molte altre strutture predefinite di Windows; il programmatore e' libero di modificare a sua volta i nomi di questi campi adattandoli alle proprie esigenze o ai propri gusti.

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 e' terminata, dobbiamo utilizzare la procedura EndPaint definita anch'essa nella libreria USER32; il prototipo di questa procedura e' il seguente:
BOOL EndPaint(HWND hwnd, CONST LPPAINTSTRUCT lpPaint);
Il parametro hwnd indica l'handle della finestra appena aggiornata; il parametro lpPaint e' 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 puo' 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):
Cls_OnPaint proc hwnd :HWND

   LOCAL    paintStruct :DWORD
   LOCAL    paintDC     :DWORD

   invoke   BeginPaint, hwnd, addr paintStruct
   mov      paintDC, eax

   ; elaborazione di WM_PAINT

   invoke   EndPaint, hwnd, addr paintStruct

   ret

Cls_OnPaint endp

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 e' una DWORD che rappresenta l'ascissa del punto della client area dal quale inizia l'output.
Il parametro nYStart e' una DWORD che rappresenta l'ordinata del punto della client area dal quale inizia l'output.
Il parametro lpString e' una DWORD che rappresenta l'indrizzo di una stringa ASCII, non necessariamente terminata da uno zero; questa e' ovviamente la stringa che verra' stampata sullo schermo.
Il parametro cbString e' 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 1; approfittando del fatto che questa stringa viene definita staticamente, possiamo determinare facilmente la sua lunghezza con il solito metodo del location counter.
strOut1        db       'Output di stringhe di testo in Win32'
LEN_STROUT1    =        $ - offset strOut1
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. E' 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 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, restituiscein EAX la costante simbolica CLR_INVALID.
Volendo definire ad esempio uno sfondo nero per l'output, possiamo scrivere:
MAKE_COLORREF 0, 0, 0
invoke   SetBkColor, paintDC, eax
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 e' 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, restituiscein EAX la costante simbolica CLR_INVALID.
Volendo usare ad esempio un colore rosso intenso per il testo, possiamo scrivere:
MAKE_COLORREF 200, 0, 0
invoke   SetTextColor, paintDC, eax
Windows ci permette anche di definire con precisione la modalita' 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 e' il solito handle del device context che stiamo utilizzando per scrivere nella client area della nostra finestra.
Il parametro iBkMode e' una DWORD che codifica la modalita' di modifica dello sfondo; le due modalita' piu' utilizzate sono espresse dalle due costanti simboliche OPAQUE e TRANSPARENT. La modalita' OPAQUE indica che allo sfondo verra' assegnato il colore predefinito di Windows o il colore che eventualmente abbiamo scelto con SetBkColor; la modalita' TRANSPARENT indica che l'output non provochera' nessuna modifica dello sfondo (output trasparente).
Se SetBkMode termina con successo, restituisce in EAX il codice della precedente modalita' di modifica dello sfondo; se SetBkMode fallisce, restituisce in EAX il valore zero.
Osserviamo che utilizzando SetBkMode con la costante TRANSPARENT, non abbiamo la necessita' 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):
invoke   SetBkMode, paintDC, TRANSPARENT
MAKE_COLORREF 0, 250, 0
invoke   SetTextColor, paintDC, eax
invoke   TextOut, paintDC, 10, 10, offset strOut1, LEN_STROUT1


6.7 La procedura DrawText.

Se abbiamo bisogno di una gestione piu' 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 e' l'handle del device context che vogliamo utilizzare per l'output.
Il parametro lpString e' una DWORD contenete l'indirizzo di una stringa ASCII, non necessariamente terminata da uno zero; questa e' la stringa che verra' stampata sullo schermo da DrawText.
Il parametro nCount e' 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 e' una DWORD contenente l'indirizzo di una struttura di tipo RECT; la stringa da stampare, verra' formattata all'interno di questo rettangolo.
Il parametro uFlags, e' una DWORD contenente una serie di flags che indicano il tipo di formattazione che DrawText applichera' 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, verra' stampata una stringa su una sola linea (DT_SINGLELINE); questa stringa verra' 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 1; come si puo' 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 verra' 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 otterra' 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):
invoke   GetClientRect, hwnd, addr paintRect
MAKE_COLORREF 150, 0, 0
invoke   SetTextColor, paintDC, eax
invoke   DrawText, paintDC, addr strOut3, -1, addr paintRect, DT_SINGLELINE OR DT_CENTER OR DT_VCENTER
Come possiamo notare, per quanto riguarda il colore di sfondo, il colore di primo piano e la modalita' di modifica dello sfondo, valgono tutte le considerazioni gia' 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 modalita' di modifica dello sfondo dell'output; un'altro aspetto importante, e' 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 qualita'.
Se non selezioniamo nessun font particolare, Windows utilizza un font predefinito chiamato font di sistema; generalmente il font predefinito e' un tipo di carattere a spaziatura fissa simile a quello utilizzato dal DOS. Se ci serve invece un font piu' 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 o win32.inc troviamo la seguente dichiarazione di questa struttura:
LOGFONT STRUC

   lfHeight             LONG     ?     ; altezza cella carattere
   lfWidth              LONG     ?     ; larghezza cella carattere
   lfEscapement         LONG     ?     ; inclinazione del testo in gradi * 10
   lfOrientation        LONG     ?     ; inclinazione dei char in gradi * 10
   lfWeight             LONG     ?     ; peso del font (0 - 1000)
   lfItalic             BYTE     ?     ; TRUE = font italic
   lfUnderline          BYTE     ?     ; TRUE = font sottolineato
   lfStrikeOut          BYTE     ?     ; TRUE = font sbarrato
   lfCharSet            BYTE     ?     ; set di caratteri
   lfOutPrecision       BYTE     ?     ; precisione nell'output del font
   lfClipPrecision      BYTE     ?     ; precisione nel clipping del font
   lfQuality            BYTE     ?     ; qualita' dell'output
   lfPitchAndFamily     BYTE     ?     ; tipo e famiglia del font
   lfFaceName           TCHAR LF_FACESIZE dup (?)  ; nome del font
   
LOGFONT ENDS
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 piu' larghi di altri (come la 'i'). Specificando il valore zero in questo campo, lasciamo che la larghezza piu' 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 e' 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 e' ancora supportata da Windows.
Il campo lfWeight indica il peso del font, cioe' la sua consistenza; il peso deve essere un valore compreso tra 0 e 1000 e puo' variare solo a salti di 100 unita'. Il valore 400 viene indicato come normal (testo normale); il valore 700 viene indicato come bold (testo in grassetto).
Il campo lfItalic e' un valore booleano che indica se il testo deve essere stampato in stile italic (inclinato verso destra); se questo campo vale TRUE (non-zero), verra' attivato lo stile italic.
Il campo lfUnderline e' un valore booleano che indica se il testo deve essere sottolineato; se questo campo vale TRUE (non-zero), il testo verra' sottolineato.
Il campo lfStrikeOut e' un valore booleano che indica se il testo deve essere sbarrato; se questo campo vale TRUE (non-zero), il testo verra' sbarrato.
Il campo lfCharSet indica la nazionalita' del set di caratteri da utilizzare; generalmente questo campo vale zero per indicare il set di caratteri ANSI (si puo' usare anche la costante simbolica ANSI_CHARSET).
Il campo lfOutPrecision indica con quale precisione Windows rispettera' 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 e' 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 qualita'), dobbiamo specificare nel campo lfOutPrecision il valore rappresentato dalla costante simbolica OUT_TT_ONLY_PRECIS.
Il campo lfClipPrecision indica con quale precisione Windows "tagliera'" 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 puo' utilizzare la costante simbolica CLIP_DEFAULT_PRECIS.
Il campo lfQuality indica la qualita' 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 e' 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 puo' 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 e' importante). Come si puo' notare, questo campo e' un vettore formato da LF_FACESIZE byte (TCHAR = BYTE). La costante LF_FACESIZE vale 32, e questo significa che il nome del font non puo' 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 gli include files windows.inc e win32.inc; e' 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 e' una DWORD contenente l'indirizzo di una struttura di tipo LOGFONT; naturalmente questa struttura deve essere stata gia' 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 e' 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 piu' programmi contemporaneamente in esecuzione, ciascuno dei quali puo' aver bisogno di un certo numero di oggetti grafici. Tutto cio' 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 piu'.
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 1 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 puo' 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 1); 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):
   cmp      fontHandle, 0
   je       short continue
   invoke   DeleteObject, fontHandle
continue:
   invoke   CreateFontIndirect, addr fontStruct
   mov      fontHandle, eax
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 e' quello di dire al nostro device context che il font corrente verra' 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 e' l'handle del device context che vogliamo utilizzare per l'output.
Il parametro hgdiobj e' 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). E' 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):
invoke   SelectObject, paintDC, fontHandle
mov      oldFont, eax
invoke   TextOut, paintDC, 10, 10, addr strOut1, LEN_STROUT1
invoke   SelectObject, paintDC, oldFont
Come si puo' 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 puo' essere suddiviso in tre blocchi fondamentali che gestiscono le fasi di inizializzazione, invio dell'output alla client area e deinizializzazione.
E' chiaro che il luogo piu' adatto nel quale inserire la fase di inizializzazione, e' 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 e' obbligatorio riempire tutti i campi di questa struttura (come ad esempio lfWidth). E' importante pero' che tutti i campi non inizializzati, vengano riempiti con degli zeri; in questo modo Windows puo' inizializzare questi campi con dei valori predefiniti. Il procedimento piu' 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:
MemSet proc ptrMem :DWORD, fillValue :DWORD, fillSize :DWORD

   push     edi            ; salva il contenuto di edi

   mov      edi, ptrMem    ; edi punta al blocco di memoria da inizializzare
   mov      eax, fillValue ; al = byte ptr fillValue[0] (inizializzatore)
   mov      ecx, fillSize  ; ecx = n. byte da riempire (contatore)
   cld                     ; DF = 0 (incremento automatico puntatori)
   rep      stosb          ; riempie [ptrMem] con fillValue byte
   
   pop      edi            ; ripristina il vecchio contenuto di edi
   ret

MemSet endp
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 puo' notare, MemSet utilizza l'istruzione STOSB; in modalita' 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 e' 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 e' una procedura di callback. I parametri di MemSet sono tutti di tipo DWORD perche', come gia' 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 puo' essere utilizzata per inizializzare qualunque blocco di memoria; e' consigliabile inizializzare con MemSet tutte le strutture utilizzate da una applicazione (come ad esempio la struttura di tipo WNDCLASSEX).
Una volta definiti questi aspetti, prossiamo procedere con l'implementazione della procedura FontWin_OnCreate; l'aspetto assunto da questa procedura e' il seguente (versione TASM):
FontWin_OnCreate proc hwnd :DWORD, lpCreateStruct :DWORD

   call     MemSet, offset fontStruct, 0, SIZE LOGFONT

   mov      fontStruct.lfHeight, 50
   mov      fontStruct.lfWeight, 700
   mov      fontStruct.lfEscapement, 0
   mov      fontStruct.lfItalic, TRUE
   mov      fontStruct.lfUnderline, TRUE
   mov      fontStruct.lfCharSet, ANSI_CHARSET
   mov      fontStruct.lfOutPrecision, OUT_TT_ONLY_PRECIS
   mov      fontStruct.lfClipPrecision, CLIP_DEFAULT_PRECIS
   mov      fontStruct.lfQuality, DEFAULT_QUALITY
   mov      fontStruct.lfPitchAndFamily, VARIABLE_PITCH OR FF_ROMAN
   call     StrCpy, offset fontStruct.lfFaceName, offset fontName
   
   call     MessageBox, hwnd, offset msgCreate, offset msgTitolo, MB_OK OR MB_ICONINFORMATION

   mov      eax, TRUE
   ret

FontWin_OnCreate endp
Nel caso del MASM bisogna ricordarsi di utilizzare l'operatore SIZEOF al posto di SIZE; in MASM l'operatore SIZE applicato ad un dato, restituisce un codice numerico che identifica il tipo di dato.
Come e' stato detto in questo capitolo, il campo lfFaceName della struttura fontStruct, e' 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 1)
Questa stringa C deve essere copiata nel vettore lfFaceName; a tale proposito, possiamo scriverci un'apposita procedura StrCpy che assume il seguente aspetto:
StrCpy proc strTo :DWORD, strFrom :DWORD

   mov      ecx, strFrom   ; ecx punta a strFrom
   mov      edx, strTo     ; edx punta a strTo
strcpy_loop:
   mov      al, [ecx]      ; trasferisce un byte
   mov      [edx], al      ; da [ecx] in [edx]
   inc      ecx            ; prossimo byte sorgente
   inc      edx            ; prossimo byte destinaz.
   test     al, al         ; fine stringa ?
   jnz      strcpy_loop

   ret

StrCpy endp
Il parametro strFrom rappresenta l'indirizzo della stringa sorgente; il parametro strTo rappresenta l'indirizzo della stringa destinazione. Ricordiamo che in modalita' protetta a 32 bit, qualunque registro generale puo' 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. E' 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:
LOCAL    paintStruct :PAINTSTRUCT   ; struttura PAINTSTRUCT
LOCAL    paintRect   :RECT          ; area di output
LOCAL    paintDC     :HDC           ; handle del DC
LOCAL    oldFont     :HFONT         ; handle del font
LOCAL    counter     :DWORD         ; contatore
LOCAL    angolo      :DWORD         ; inclinazione stringa
Queste variabili vengono dichiarate localmente in quanto sono necessarie solamente all'interno di FontWin_OnPaint; come e' stato spiegato in precedenza, il primo passo che questa procedura deve compiere consiste nel procurarsi un device context che verra' utilizzato per scrivere nella client area. Il codice necessario e' il seguente:
invoke   BeginPaint, hwnd, addr paintStruct
mov      paintDC, eax
In possesso di paintDC, la procedura FontWin_OnPaint puo' stampare la prima stringa strOut1 con il seguente codice:
invoke   SetBkMode, paintDC, TRANSPARENT
MAKE_COLORREF 0, 250, 0
invoke   SetTextColor, paintDC, eax
invoke   TextOut, paintDC, 10, 10, offset strOut1, LEN_STROUT1
Come si puo' notare, viene selezionata con SetBkMode la modalita' trasparente che non altera lo sfondo dell'output; questa modalita' 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 verra' usato per stampare 8 volte la stringa strOut2 con 8 valori differenti per il campo lfEscapement. E' perfettamente inutile limitarsi a modificare solo questo campo; la modifica infatti non avra' effetto finche' non verra' creato un nuovo font con CreateFontIndirect. A tale proposito, possiamo scrivere la seguente procedura:
SetFont proc angolazione :DWORD

   MOV32    fontStruct.lfEscapement, angolazione

   cmp      fontHandle, 0
   je       short setfont_continue
   invoke   DeleteObject, fontHandle
setfont_continue:
   invoke   CreateFontIndirect, offset fontStruct
   mov      fontHandle, eax

   ret

SetFont endp
Il procedimento seguito da SetFont e' 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 e' 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:
   MAKE_COLORREF 250, 250, 0
   invoke   SetTextColor, paintDC, eax
   mov      counter, 8
   mov      angolo, 0
fontRotateLoop:
   invoke   SetFont, angolo
   invoke   SelectObject, paintDC, fontHandle
   mov      oldFont, eax
   invoke   TextOut, paintDC, 300, 250, offset strOut2, LEN_STROUT2
   invoke   SelectObject, paintDC, oldFont
   add      angolo, 450
   dec      counter
   jnz      fontRotateLoop
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 e' il seguente:
invoke   SetFont, 0
invoke   SelectObject, paintDC, fontHandle
mov      oldFont, eax
invoke   GetClientRect, hwnd, addr paintRect
MAKE_COLORREF 150, 0, 0
invoke   SetTextColor, paintDC, eax
invoke   DrawText, paintDC, addr strOut3, -1, addr paintRect, DT_SINGLELINE OR DT_CENTER OR DT_VCENTER
invoke   SelectObject, paintDC, oldFont
Siccome strOut3 e' 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 possibilita' 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 e' il seguente:
FontWin_OnClose proc hwnd :DWORD

   invoke   MessageBox, hwnd, offset msgClose, offset msgTitolo, MB_YESNO OR MB_ICONQUESTION

   ret            ; eax = return value (IDYES o IDNO)

FontWin_OnClose endp
Come si puo' 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 e' il seguente:
FontWin_OnDestroy proc hwnd :DWORD

   cmp      fontHandle, 0
   je       short ondestroy_continue
   invoke   DeleteObject, fontHandle
ondestroy_continue:
   invoke   PostQuitMessage, NULL

   ret

FontWin_OnDestroy endp
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.
Nota importante per il TASM.
Se si ottengono errori nell'assemblaggio di FONTWIN.ASM con il TASM, vuol dire che probabilmente si sta utilizzando una vecchia versione dell'include file principale win32.inc; in questo caso e' necessario scaricare la versione piu' aggiornata dalla sezione Downloads. Si consiglia di verificare periodicamente la presenza di versioni piu' aggiornate di win32.inc.