Introduzione alle API Win32

Capitolo 40: output per il debug

I programmatori che affrontano la programmazione in ambienti grafici (come Win32) provenendo da precedenti esperienze in ambienti orientati al testo (come DOS) sentono spesso la mancanza del buon vecchio idioma "printf di debug". Vi sono, certo, debugger potenti e sofisticati, ma, qualche volta, sarebbe bello poter ottenere un semplice quadro d'insieme dell'esecuzione del nostro programma, esaminando una serie di "stampe" piazzate strategicamente nei punti opportuni.

Naturalmente, è possibile fare queste scritture su di un file di testo, e questo ha i suoi separati vantaggi (permanenza del "log" di esecuzione) e problemi (il file va chiuso, e riaperto in append, ad ogni scrittura, se no un crash del nostro programma lascerebbe una traccia incompleta di esecuzione proprio quando più ne avremmo bisogno per capire le ragioni e la meccanica del crash stesso!).

Le API di Win32 offrono anche una chiamata apposita:

VOID OutputDebugString( LPCTSTR lpOutputString // la stringa da mostrare ); che spedisce la stringa a qualsiasi eventuale debugger sia "in ascolto" (specificamente sul nostro processo, o come "debugger di sistema"). Questo ha molti vantaggi, ma presenta altri problemi (il nostro output di debug si mescola con quello di qualsiasi altra libreria stia usando questa stessa funzionalità di sistema, e non c'è in generale modo di sceverare i diversi "flussi logici" di messaggi di debug in maniera semplice).

(Naturalmente, non è un problema che questa API accetti una stringa, invece di un formato e un elenco variabile di argomenti alla "printf"; è banale, infatti, fare prima la formattazione da formato+argomenti a stringa -- con sprintf, o con l'analoga API wsprint, o magari con la ricca e complessa API FormatMessage -- e poi emettere la stringa risultante, ad esempio con OutputDebugString; il meccanismo "stdarg" del C offre un modo semplice di impacchettare le due cose in una, e quello C++ degli "stream" un modo anche più semplice, elegante, e generale).

Tutto sommato, dunque, c'è ancora posto per un semplice e artigianale "output di debugging fatto in casa", e il controllo listbox offre un ottimo e semplicissimo approccio per implementarlo.

A questo scopo, basta conoscere il seguente minuscolo sottoinsieme delle ricche e varie funzionalità delle listbox (che presenteremo poi in dettaglio in un capitolo successivo):

Questi elementi ci permettono di accennare già una prima bozza di soluzione. Facciamo un piccolo dialogo, con un file RC simile a:

IDD_DIALOG1 DIALOG DISCARDABLE 0, 0, 227, 68 STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "Dialog" FONT 8, "MS Sans Serif" BEGIN DEFPUSHBUTTON "OK",IDOK,175,5,50,14 PUSHBUTTON "Cancel",IDCANCEL,120,5,50,14 LISTBOX IDC_LIST1,10,5,105,60,NOT LBS_NOTIFY | LBS_NOSEL | WS_VSCROLL | WS_HSCROLL EDITTEXT IDC_EDIT1,120,25,105,20,ES_AUTOHSCROLL END dove l'unica novità, naturalmente, è l'istruzione LISTBOX, il cui scopo dovrebbe però essere, spero, abbastanza ovvio (la necessità di specificare esplicitamente NOT LBS_NOTIFY fra i bit di stile, per evitare che la listbox ci "avverta" di eventi che non ci interessano, deriva da un gioco di "default", e non è qui molto importante).

La semplicissima dialog procedure da connettere a questo template di dialogo sarà del tipo (omettendo i vari casi già visti, come il WM_INITDIALOG per il quale non abbiamo nulla da fare, il WM_CLOSE per cui dovremo fare la solita EndDialog, ecc, ed evidenziando invece il solo frammento d'interesse):

case WM_COMMAND: { int id = LOWORD(wParam); switch(id) { case IDOK: { char buf[256]; GetDlgItemText(hwndDlg, IDC_EDIT1, buf, 256); SendDlgItemMessage(hwndDlg, IDC_LIST1, LB_ADDSTRING, 0, (LPARAM)buf); } break;

In pratica, al clic sul bottone "OK" (o alla pressione del tasto di Invio, visto che il bottone stesso è quello di default), preleviamo la stringa che l'utente ha immesso nel controllo di edit, e la spediamo al controllo listbox, dove viene accodata alle altre già presenti.

Completando questo programmillo, e giocandoci un poco, diventa evidente che, mentre la funzionalità di base è soddisfacente, vi sono un paio di cosette antipatiche:

Questi due problemi richiedono un'interazione appena un poco più sofisticata con il controllo listbox, e quindi li riprenderemo in esame più avanti, dopo che avremo dato tutti i necessari dettagli sul controllo stesso.

Un altro problema, più grave e strutturale, non ha invece nulla a che vedere coi listbox: il problema in questione è che questo approccio all'output di debugging, come l'abbiamo ora presentato, è invasivo rispetto ai programmi che vogliono farne uso. Per la precisione, un tale programma deve riservare una certa area sul suo dialogo (deve avere un dialogo, il che è pure un limite, perchè, anche se ancora non abbiamo preso in esame questa problematica, si possono avere tante varietà di applicazioni per Windows che hanno invece un'impostazione assai diversa!) al solo scopo di mostrare il proprio output di debugging. Ma questo output tipicamente non interessa all'utente finale del programma, bensì solo al programmatore che lo sviluppa; le due versioni del programma, quella "da debug" e quella "per release", dovrebbero dunque far uso di diversi layout per il dialogo principale, il che dà svariati tipi di problemi.

Per risolvere questo problema strutturale, bisogna insomma che la listbox di output sia in un dialogo diverso da quello principale, e da esso separato. Non abbiamo però ancora visto come questa separazione sia possibile, e di questo tema ci occuperemo dunque al prossimo capitolo.


Capitolo 39: Indies: l'implementazione
Capitolo 41: dialoghi non modali
Elenco dei capitoli