Le API WIN32 rappresentano l'insieme di interfacce di programmazione nei sistemi operativi di Microsoft, esse sono le funzioni di più
basso livello nel sistema operativo. Un programma che gira sotto Windows funziona in maniera un pò diversa di (come invece funzionava quando ancora il sistema
operativo di Windows non esisteva) un programma che girava sotto Dos. Il sistema operativo di Windows tiene sotto controllo stretto un programma che sta
girando, nel senso che ogni azione dell'utilizzatore del programma che sta girando viene notificata al programma stesso da Windows attraverso dei messaggi
inseriti in una coda dei messaggi, è poi compito dell'applicazione come vedremo in seguito prelevare dalla coda i messaggi ed elaborarli (oppure lasciarli
cadere nel dimenticatoio rappresentato da una funzione di Windows) con questo voglio dire che un programma da noi scritto che gira sotto Windows riceve dal
sistema operativo stesso dei messaggi ad esempio quando deve disegnare o aggiornare la finestra, oppure quando l'utente ultilizzatore del programma
ridimensiona, ingrandisce o riduce a icona la finestra, oppure quando clicca con il mouse sulla crocetta in alto a destra per terminare il programma. Ma il
programma riceve anche dei messaggi da Windows quando si fa clic su un pulsante inserito all'interno della finestra principale, o si scrive qualcosa in una
TextBox (controllo che fa parte di un insieme di altri controlli Windows).
Vediamo allora adesso quali sono le azioni che un programma minimo di Windows deve intraprendere affinchè sullo schermo appaia una
finestra di Windows, che può essere ridimensionata, ingrandita ridotta a icona o chiusa, ma niente altro ( trattandosi del minimo codice richiesto niente altro
deve fare).
Come tutti i programmi scritti nel linguaggio C o C++ (in questo caso useremo il C) la prima funzione da inserire nel codice si dovrà
chiamare obbligatoriamente WinMain in quanto rappresenta il punto d'entrata (per chi non lo sapesse ricordo che nei programmi scritti sotto Dos cioè quando ancora il sistema operativo Windows non
esisteva questa funzione si chiamava Main).
La funzione principale WinMain si occuperà prima di tutto di registrare una Window Class
rappresentata da una struttura che contienerà tutti i dati necessari per la finestra, quindi creerà una finestra che si baserà sui dati appena inseriti nella
struttura, successivamente visualizzerà la finestra sullo schermo ed infine entrerà in un ciclo chiamato ciclo dei messaggi che si occuperà di
prelevare i messaggi inseriti nella coda dei messaggi e li passerà a un'altra funzione chiamata procedura finestra che si occuperà di
processare i messaggi, quando nella coda dei messaggi non ce ne saranno il ciclo dei messaggi resterà in attesa. Questo è un ciclo che terminerà solamente alla
chiusura dell'App.
Allora vediamo adesso quì sotto il codice relativo alla funzione WinMain che rappresenta il punto di ingresso per
l'esecuzione del programma.
#include <windows.h>
const char g_szClassName[] = "myWindowClass";
int WINAPI WinMain(_In_ HINSTANCE hInstance, _In_opt_
HINSTANCE hPrevInstance, _In_ LPSTR lpCmdLine, _In_ int nCmdShow) { WNDCLASSEX wc; HWND hwnd; MSG Msg;
//Step 1: Registrazione della
Window Class wc.cbSize = sizeof(WNDCLASSEX); wc.style = 0; wc.lpfnWndProc = WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance =
hInstance; wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wc.lpszMenuName = NULL; wc.lpszClassName = g_szClassName; wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
if (!RegisterClassEx(&wc)) {
MessageBox(NULL, "Registrazione della Finestra Fallita!", "Errore!", MB_ICONEXCLAMATION | MB_OK); return 0; }
// Step 2: Creazione della
finestra hwnd = CreateWindowEx( WS_EX_CLIENTEDGE, g_szClassName, "Titolo della mia finestra", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT,
CW_USEDEFAULT, 400, 200, NULL, NULL, hInstance, NULL);
if (hwnd == NULL) { MessageBox(NULL, "Creazione della Finestra Fallita!", "Errore!",
MB_ICONEXCLAMATION | MB_OK); return 0; }
ShowWindow(hwnd, nCmdShow); UpdateWindow(hwnd); // Step 3: Il ciclo dei messaggi while
(GetMessage(&Msg, NULL, 0, 0) > 0) { TranslateMessage(&Msg); DispatchMessage(&Msg); } return Msg.wParam; }
La prima riga #include <window.h> include il file che contiene le intestazioni per i tipi di dato, macro, strutture e
tipi ridefiniti. Ovviamente sarà necessario inserirlo. Segue la dichiarazione di una stringa di tipo const (cioè non modificabile) che contiene il nome che
ci servirà per riempire il campo wc.lpszClassName della struttura o Window Class che dovremo registrare per creare la finestra.
La funzione di ingresso WinMain prende quattro parametri. Questi parametri vengono caricati automaticamente nella funzione dal sistema
operativo Windows al momento dell'avvio del programma. HINSTANCE hInstance: handle del modulo
eseguibile, per capirci meglio, il file .exe in memoria. HINSTANCE hPrevInstance: sempre NULL per i programmi win32.
LPSTR lpCmdLine: la riga di comando in un'unica stringa senza il nome del programma.
int nCmdShow: un valore intero che può essere passato a ShowWindow().
Segue la dichiarazione di tre tipi di dato struttura. WNDCLASSEX wc: struttura che contienerà tutti i dati della
finestra. HWND hwnd: handle per la finestra che verrà creata.
MSG Msg: struttura che contienerà i dati relativi ai messaggi ricevuti dalla procedura che sarà incaricata di elaborarli.
Adesso è venuto il momento di riempire la struttura wc di tipo WNDCLASSEX con i dati necessari alla
creazione della finestra, in tutto 12 campi da riempire.
wc.cbSize = sizeof(WNDCLASSEX); |
dimensioni della struttura che si ottiene con l'operatore sizeof. |
wc.style = 0; |
specifica lo stile della classe rappresentato da una serie di valori predeterminati che possono essere combinati tramite l'operatore
or(|), ma in questo caso possiamo tranquillamente inserire lo zero. |
wc.lpfnWndProc = WndProc; |
puntatore alla procedura che sarà
incaricata di elaborare i messaggi ricevuti per la finestra creata e che in questo caso chiameremo WndProc, ma in pratica potrà essere chiamata con un qualsiasi
nome a piacere. |
wc.cbClsExtra = 0; |
a questo campo può essere passato il valore zero. |
wc.cbWndExtra = 0; |
anche a questo. |
wc.hInstance = hInstance; |
questo valore è l'istanza dell'applicazione ed è il primo parametro della funzione WinMain. |
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); |
identifica la classe icon. |
wc.hCursor = LoadCursor(NULL, IDC_ARROW); |
identifica la classe cursore. |
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); |
identifica la classe brush. sarà il colore dello sfondo
della finestra. |
wc.lpszMenuName = NULL; |
siccome in questo esempio non è previsto un menù il suo valore sarà NULL. |
wc.lpszClassName = g_szClassName; |
ed ecco quì che ho inserito in questo campo il nome per la classe, cioè la stringa di tipo const che si trova alla
seconda riga del listato sopra. |
wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION); |
gestisce una piccola icona associata alla classe della
finestra. |
E' venuto il momento di registrare la finestra e questo si ottiene utilizzando la funzione RegisterClassEx, vediamo il
codice quì sotto.
if (!RegisterClassEx(&wc)) { MessageBox(NULL, "Registrazione della Finestra Fallita!", "Errore!", MB_ICONEXCLAMATION | MB_OK);
return 0; }
Se la funzione registra la classe và bene, se invece si verifica un errore nella registrazione l'utente viene avvisato con un messaggio
che avvisa che la registrazione è fallita e l'esecuzione del programma viene terminata dal comando return 0;
A questo punto se tutto è andato liscio si può creare la finestra con la funzione CreateWindowEx, anche questa funzione
necessita di 12 parametri. vediamo una descrizione quì sotto.
hwnd = CreateWindowEx |
|
WS_EX_CLIENTEDGE |
specifica lo
stile esteso per la finestra con una serie di valori predeterminati che possono essere combinati tramite l'operatore or(|), in questo caso ne ho inserito solo
uno che specifica che la finestra avrà un bordo incassato. |
g_szClassName |
puntatore al nome della classe, lo stesso che ho inserito prima nel campo wc.lpszClassName della struttura WNDCLASSEX wc. |
"Titolo della mia finestra" |
puntatore ad una stringa che rappresenta il titolo della finestra, cioè quello che
si vedrà sulla finestra in alto a sinistra. |
WS_OVERLAPPEDWINDOW |
specifica uno stile (questa volta non esteso a
differenza del primo parametro) per la finestra rappresentato da una serie di valori predeterminati che possono essere combinati con l'operatore or (|), in
questo caso ne ho inserito uno. |
CW_USEDEFAULT |
quì
và inserito il valore per l'asse x del punto in cui la finestra sarà visualizzata sullo schermo (asse x in alto a sinistra) se si usa CW_USEDEFAULT come ho
fatto sarà incaricato di stabilirlo Windows. |
CW_USEDEFAULT |
quì
và inserito il valore per l'asse y del punto in cui la finestra sarà visualizzata sullo schermo (asse y in alto a sinistra) se si usa CW_USEDEFAULT come ho
fatto sarà incaricato di stabilirlo Windows. |
500 |
questo valore rappresenta la larghezza della finestra. |
250 |
questo valore rappresenta la l'altezza della finestra. |
NULL |
questo valore identifica la finestra genitore, in questo caso siccome che la finestra che sto creando è la principale passando il
valore NULL sto dicendo a Windows che la finestra genitore è il Desktop. |
NULL |
quì si identifica un menù oppure una finestra figlia, in questo caso si può passare il valore NULL. |
hInstance |
quì inseriamo l'istanza dell'applicazione (file .exe in memoria) primo parametro di WinMain. |
NULL |
quì bisigna passare un valore a Windows che serve quando si deve creare una applicazione MDI (multiple document interface), non è
il nostro caso quindi basta passre NULL. |
Come si può notare, quando la funzione CreateWindowEx viene eseguita restituisce un valore che in questo caso viene
assegnato alla variabile hwnd che ho dichiarato all'inizio della funzione WinMain. questo rappresenta l'handle alla finestra
appena creata (identifica la finestra creata), molte delle funzioni di Windows richiedono per poter eseguire determinati compiti sulla finestra questo valore.
Subito dopo la creazione della finestra possiamo vedere che il contenuto della variabile hwnd di tipo HWND viene controllato e se è uguale a NULL significa che
la finestra non è stata creata, quindi il programma provvede ad avvisare l'utente con un messaggio a video e poi termina, come precedentemente ha fatto nella
fase di registrazione della finestra. Vediamo il codice quì sotto.
if (hwnd == NULL) { MessageBox(NULL, "Creazione della Finestra Fallita!", "Errore!", MB_ICONEXCLAMATION | MB_OK); return 0; }
A questo punto non rimane altro che visualizzare la finestra quindi per questo viene usata la funzione ShowWindow che
fortunatamente prende solo 2 parametri.
ShowWindow(hwnd, nCmdShow); UpdateWindow(hwnd);
Il primo parametro è l'handle appena restituito dalla funzione CreateWindowEx, invece il secondo parametro è
rappresentato dall'ultimo parametro di WinMain. Dopo che la finestra è stata visualizzata sulo schermo viene eseguita la funzione
UpdateWindow che prende come parametro solo l'handle della finestra.
L'ultima parte rimasta per la funzione WinMain è il ciclo dei messaggi quì sotto.
while
(GetMessage(&Msg, NULL, 0, 0) > 0) { TranslateMessage(&Msg); DispatchMessage(&Msg); } return Msg.wParam;
Da questo ciclo il programma uscirà solamente quando il programma terminerà. Quando si fà qualcosa nell'applicazione, ad esempio si
batte un tasto sulla tastiera oppure si fà clic con il mouse su un menù o semplicemente all'interno della finestra o ancora quando si sposta la finestra sul
desktop il sistema operativo Windows mette un opportuno messaggio nella coda dei messaggi. La funzione GetMessage(&Msg, NULL, 0, 0)
controlla all'interno della coda dei messaggi e se non ce ne sono si blocca e resta in attesa, nel momento in cui Windows inserisce un messaggio lo prende e
inserisce il messaggio nella struttura Msg, quindi restituisce un valore positivo. A questo punto il ciclo viene eseguito, pertanto viene
eseguita la funzione TranslateMessage(&Msg) il quale ha come parametro la struttura Msg che adesso contiene tutti i dati del
messaggio, questa funzione processa ulteriormente il messaggio, trasforma i messaggi generati da caratteri virtuali (ins, canc, ecc..) in messaggi generati da
caratteri, solitamente non inserendo questa funzione funziona ugualmente tutto, ma in alcuni casi potrebbero sorgere dei problemi, quindi è meglio sempre
inserirla. Dopodichè viene eseguita la funzione DispatchMessage(&Msg) che prende sempre come parametro Msg struttura che
contiene l'hwnd della finestra interessata, un valore di intero senza segno che rappresenta il messaggio, altri due valori di tipo
WPARAM e LPARAM che si chiamano rispettivamente wParam e lParam che sono riempiti con dei dati a
seconda del messaggio ricevuto, ed infine ci sono altri due dati quasi mai usati che adesso non sto quì a commentare, quindi ricava la Window Procedure
e la esegue passandole tutti questi dati. Terminata l'esecuzione della Window Procedure il controllo ritorna, a sua volta ritorna anche
DispatchMessage(&Msg) e il ciclo ricomincia dall'inizio.
Adesso non ci resta che esaminare e commentare la Window Procedure del quale vediamo il codice quì sotto, seguono i
commenti.
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch (msg) { case WM_CLOSE:
DestroyWindow(hwnd); break; case WM_DESTROY: PostQuitMessage(0); break; default: return DefWindowProc(hwnd, msg, wParam, lParam); }
return 0; }
Questa Window Procedure per i messaggi che gestisce permette solo di ridimensionare la finestra, ingrandirla, ridurla a icona e chiuderla
terminando l'applicazione. Diciamo subito che l'azione di chiusura della finestra e terminazione del programma viene gestita dai messaggi WM_CLOSE
e WM_DESTROY che ho inserito io. Quando si clicca sulla crocetta in alto a destra della finestra, Window mette un messaggio WM_CLOSE
nella coda dei messaggi, il ciclo dei messaggi lo preleva e manda in esecuzione la Window Procedure passandogli tutti i dati, quindi
esegue DestroyWindow(hwnd) che elimina la finestra e poi mette il messaggio WM_DESTROY nella coda dei messaggi, quando viene
eseguita PostQuitMessage(0) viene fatta a Window una richiesta di terminazione del thread e quindi il programma termina. Invece l'atto di
ridimensionare, ingrandire e ridurre a icona la finestra viene automaticamente eseguita dal sistema operativo di Windows nel momento in cui viene eseguita la
funzione DefWindowProc(hwnd, msg, wParam, lParam) che si trova sotto la voce default: in pratica questa parte di codice
viene eseguita quando nessun'altro case è stato elaborato.
Quì sotto il listato completo.
Listato completo |
|
|
Non resta che avviare il proprio compilatore, io uso Microsoft Visual Studio 2019 Community, selezionare un
nuovo progetto vuoto console in linguaggio C++, quando è tutto pronto in alto a sinistra in esplora soluzioni cliccare con il tasto destro del
mouse sul nome del progetto quindi selezionare proprietà. Nella finestra che si apre sul menù a sinistra selezionare linker->sistema, quindi a
destra alla voce sottosistema selezionare Windows (/SUBSYSTEM:WINDOWS) come possiamo vedere nell'immagine sotto.
Cliccare su OK, la finestra si chiude, Adesso ricliccare nuovamente sul nome del progetto in
esplora soluzioni, ma questa volta sciegliere Aggiungi->Nuovo Elemento e dalla finestra che si apre sciegliere File C++, nella
casella del nome del file mettere pure un nome a piacere, ma con estensione c invece che cpp, in questo modo il compilatore
capisce che deve compilare un File nel linguaggio C.
Copiare il codice del listato completo presentato sopra ed incollarlo nel file appena creato nel compilatore
quindi compilare e mandare in esecuzione.
|