programmazione

API (APPLICATION PROGRAMMING INTERFACE) OVVERO INSIEME DELLE INTERFACCE DI PROGRAMMAZIONE NEI SISTEMI OPERATIVI WINDOWS DI MICROSOFT.
In questa pagina viene presentato e descritto il codice minimo necessario per una App Desktop che utilizza le API di Windows.

home page
 

APP DESKTOP con WIN32 e linguaggio C

Pubblicato il: 19-10-2019

Ultima Modifica: 7-1-2024

Win32Le 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 copia
 

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.