Introduzione alle API Win32

Capitolo 11: macro e API "di comodo"

Abbiamo già dedicato parecchia energia, al Capitolo 9, ad esaminare come "smistare" i messaggi, portandoli tutti alla "destinazione" di un'apposita funzione (invece di gestirli tutti, come potrebbe venire spontaneo ma sarebbe poco furbo fare, nella Dialog Procedure, entro un grosso switch o altro costrutto equivalente). Ma ci sono alcuni altri problemi sull'uso dei messaggi, sia in spedizione, sia in ricezione; in questo capitolo, e nel successivo, accenneremo ad alcuni aiuti che gli header di sistema di Windows ci possono offrire in merito.

Anzitutto, nei meccanismi "di fondo" e "unificanti" dei messaggi di Windows, c'è un problema di tipi. Ogni messaggio si porta dietro "due token di 32 bit ciascuno"; ciascuno può "trasportare" un puntatore, una handle, un intero, magari due, ma "intrinsecamente" non ha davvero in sè e per sè alcun tipo. In pratica, per preparare e per usare questi messaggi abbiamo quasi sempre bisogno di fare dei "cast" C (prassi già di per sè assai spiacevole!), e, non di rado, anche di impacchettare o spacchettare "parole" ("word", interi di 16 bit). Questo è un livello di accesso molto basso, cioè vicinissimo alla macchina e lontanissimo dai problemi che è nostro interesse risolvere; lo indica molto chiaramente la continua violazione del sistema dei tipi che i "cast" implicano.

Fortunatamente, c'è una semplice via d'uscita.

Accettiamo pure il fatto che ogni messaggio viene, in realtà, sempre "spedito" da SendMessage [in realtà, c'è un'importante alternativa, la API PostMessage, che esamineremo più avanti], con due parametri di tipo WPARAM e LPARAM (ciascuno di 32 bit) e un valore di ritorno di tipo LRESULT (idem); accettiamo pure che esso viene "ricevuto" da una Dialog Procedure negli stessi termini (in realtà, una Dialog Procedure ritorna BOOL, cioè TRUE o FALSE, non LRESULT, ma vedremo più avanti, al prossimo capitolo, come essa possa in realtà "ritornare" il suo vero risultato) -- questi sono fra gli aspetti più fondamentali di Windows.

Ma nessuno ci obbliga, una volta presone atto, e ben acquisita questa conoscenza, a restarne "sempre" pienamente coscienti... e a subire tutti i possibili errori che un "cast" può mascherare al nostro compilatore! Perchè non "nascondere" questa "realtà" dei messaggi, e dei tipi ad essi connessi (o meglio, della mancanza di veri e propri tipi, da parte dei "veri" parametri che un messaggio porta con sè), dietro un semplice "schermo" di costo basso o nullo...?

Per esempio, abbiamo accennato che, per cambiare lo stato "scelto/non scelto" di un BUTTON stile checkbox o radiobutton, gli si spedisce il messaggio BM_SETCHECK, con WPARAM pari rigorosamente a 0 (per togliere la "spuntatura", o "pallino" che sia) o a 1 (per metterla), e LPARAM pari a 0. Allora, perchè non "incapsulare" tutto questo in macro del tipo...:

#define SetCheck(hWnd,OnOff) \ SendMessage(hWnd,BM_SETCHECK,!!OnOff,0)

Non potremmo scriverci una "batteria" di simili macro, compresi tutti i cast necessari, ficcarla in un qualche nostro header file, e poi "scordarci" che sono macro, e "chiamarle" come e quando occorra...?

Potremmo, ma non serve: qualcuno altro l'ha già fatto per noi! Precisamente, quel qualcuno è la Microsoft, che, nell'header file <windowsx.h> incluso nell'SDK, offre appunto, fra l'altro, una piccola batteria di macro del genere, per spedire messaggi "mirati" ai vari controlli elementari. Le macro in questione presentano al programmatore una sintassi, e in certa misura gli permettono di usare dei tipi di parametri, meno "ostici" di quanto i bassi livelli delle API Windows non richiedano.

Ad esempio,

#define Button_SetCheck(hwndCtl, check) \ ((void)SendMessage((hwndCtl), BM_SETCHECK, \ (WPARAM)(int)(check), 0L)) non è magari esattamente l'insieme di cast e trasformazioni che avremmo scelto (ad esempio, questa macro non garantisce di mandare un wParam pari sempre a 0 o a 1 -- e questo, per ragioni di generalità relativamente al suo uso con i button "3-state"), ma il vantaggio è che tutte queste macro sono già fatte nel file di header di sistema <windowsx.h>, pronte per essere riusate nei nostri programmi senza "reinventare l'acqua calda"!

Altri simili "rivestimenti" di messaggi, con "costo" di uso molto basso (sia pure non lo "zero assoluto" implicito in una macro di preprocessore!) li ha fatti sempre la Microsoft, e li offre come parte delle API di Win32. Ad esempio, abbiamo visto che, per cambiare il testo di una finestra, le si invia il messaggio WM_SETTEXT; ebbene, c'è un'alternativa praticamente equivalente: chiamare invece la API...:

BOOL SetWindowText( HWND hWnd, // finestra destinazione LPCTSTR lpString // testo da impostare );

Questo tipo di "funzioni di sottile rivestimento", rispetto alla semplice spedizione di messaggi, ha, come dicevamo, un minimo di costo (è pur sempre una funzione, quindi qualche istruzione di macchina -- qualche nanosecondo -- è speso per chiamata-e-ritorno), ma anche un qualche piccolo vantaggio; ad esempio, SetWindowText "armonizza" i valori di ritorno in caso di errore (che potrebbero essere diversi per WM_SETTEXT spedita a diverse classi di controllo o finestra), tornando sempre FALSE per qualsiasi errore (e, naturalmente, TRUE se ha successo). La "sicurezza dei tipi" è, inoltre, salvaguardata meglio dall'uso di queste funzioni, rispetto a quello di macro equivalenti.

In generale, è meglio usare questi "rivestimenti", macro o funzioni che siano, che non ricorrere alla pura e semplice SendMessage (per quei messaggi, naturalmente, per cui un tale "rivestimento" È previsto!). I pochi nanosecondi da "pagare" costituiscono infatti, tutto sommato, un prezzo estremamente modesto.

A scopi didattici, quali sono appunto quelli di questo tutorial, potremo, tuttavia, continuare ad indicare i messaggi come substrato fondamentale della comunicazione con i controlli, lasciando alla diligenza del lettore la ricerca delle API direttamente equivalenti, quando esse esistano, ovvero delle relative macro di ricoprimento, rispettivamente sui manuali di riferimento delle API (ad esempio, l'MSDN) e il già nominato header file windowsx.h.


Capitolo 10: controlli: i BUTTON
Capitolo 12: ID e HWND
Elenco dei capitoli