Programmare in C su Amiga (24)

8
AMIGA Programmare in C su Amiga di Oario de Judicibus (MC2120) (24) L'emissione di messaggi di errore e l'interazione diretta dell'utente nelle scelte logiche di un programma è un argomento base nel discorso delle tecniche di programmazione. In questa puntata vedremo come implementare tali tecniche nel nostro programma scheletro Esempio di quadro automatico (autorequester). 210 Introduzione Un programma scritto bene deve pre- vedere la possibilità che qualcosa vada storto. Ad esempio che non si possa aprire una libreria, o non si trovi un file a cui deve accedere, o più semplicemente che non ci sia abbastanza memoria libe- ra per portare a termine una qualche operazione. Deve cioè prevedere tutta una serie di controlli a fronte di quelle operazioni che, se non effettuate o svol- te non correttamente, possono avere un influsso negativo sulla continuazione del programma stesso od addirittura sul cor- retto funzionamento del sistema. Ovvia- mente, nel caso che uno di questi con- trolli metta in evidenza una situazione anomala, il programma deve passare il controllo ad una procedura che sia in grado di gestire il problema. In alcuni casi basterà modificare il flusso delle operazioni successive, ignorando di fatto l'evento verificatosi, in altri casi la deci- sione potrà essere più drastica, fino eventualmente al completamento dell'e- secuzione con una indicazione di impos- sibilità a proseguire [abend return codeI Non solo. In alcuni casi il programma sklstdh,sy" skIUSl'~,c sklUSl'h,sy" " potrà prendere le sue decisioni da solo, in altri dovrà chiedere aiuto all'utente, vuoi per effettuare delle operazioni di compensazione, vuoi per determinare direttamente il flusso logico delle opera- zioni successive. Facciamo un paio di esempi. Un editore di testi viene chia- mato passandogli il nome di un file. Il programma si accorge che il file non esiste. Nessun problema: se ne crea uno nuovo con quel nome. Il programma si è limitato ad operare in modo diverso saltando così il problema. A questo pun- to l'editore cerca il file di configurazione Lo trova ma alcuni campi nel file non sono corretti. Il programma usa quelli di default ma avverte l'utente che c'è qual- cosa di sbagliato nel file di configurazio- ne. L'utente modifica il file, gli cambia nome e cerca di salvarlo. Il programma si accorge che esiste già un file con quel nome e non sa che fare. Non può certo decidere lui se è il caso o no di rimpiaz- zarlo. E se l'utente si fosse sbagliato? L'editore allora emette un messaggio chiedendo all'utente di confermare il salvataggio del nuovo file sul preceden- te. A questo punto l'utente chiede al programma di aprire una nuova finestra. Il programma ci prova, ma non c'è abba- stanza memoria. Messaggio di errore: ((Non posso aprire la finestra. Non c'è abbastanza memoria per farlo)). L'utente allora chiude altri lavori concomitanti, ma nel far questo chiude per errore anche un processo collegato all'editore stesso. Chiede di nuovo, quindi, di aprire la seconda finestra, ma a questo punto il programma principale si accorge che non può comunicare con il processo di servizio che utilizzava per aprire e chiu- dere i documenti, emette un messaggio di errore di elevata severità e, non poten- do neppure chiudere il documento prin- cipale, libera tutte le risorse allocate e ritorna il controllo al sistema operativo. In questo scenario abbiamo visto un po' tutte le varie possibiità che si posso- no presentare. In pratica un buon pro- gramma deve poter comunicare con l'utente e ricevere da questi delle diretti- ve, anche quando il modo normale di MCmicrocomputer n. 98 - luglio/agosto 1990

Transcript of Programmare in C su Amiga (24)

Page 1: Programmare in C su Amiga (24)

AMIGA

Programmare in C su Amigadi Oario de Judicibus

(MC2120)

(24)

L'emissione di messaggi dierrore e l'interazione direttadell'utente nelle sceltelogiche di un programma è unargomento base nel discorsodelle tecniche diprogrammazione. In questapuntata vedremo comeimplementare tali tecniche nelnostro programma scheletro

Esempio diquadro automatico

(autorequester).

210

Introduzione

Un programma scritto bene deve pre-vedere la possibilità che qualcosa vadastorto. Ad esempio che non si possaaprire una libreria, o non si trovi un file acui deve accedere, o più semplicementeche non ci sia abbastanza memoria libe-ra per portare a termine una qualcheoperazione. Deve cioè prevedere tuttauna serie di controlli a fronte di quelleoperazioni che, se non effettuate o svol-te non correttamente, possono avere uninflusso negativo sulla continuazione delprogramma stesso od addirittura sul cor-retto funzionamento del sistema. Ovvia-mente, nel caso che uno di questi con-trolli metta in evidenza una situazioneanomala, il programma deve passare ilcontrollo ad una procedura che sia ingrado di gestire il problema. In alcunicasi basterà modificare il flusso delleoperazioni successive, ignorando di fattol'evento verificatosi, in altri casi la deci-sione potrà essere più drastica, finoeventualmente al completamento dell'e-secuzione con una indicazione di impos-sibilità a proseguire [abend return codeINon solo. In alcuni casi il programma

sklstdh,sy"skIUSl'~,csklUSl'h,sy"

"

potrà prendere le sue decisioni da solo,in altri dovrà chiedere aiuto all'utente,vuoi per effettuare delle operazioni dicompensazione, vuoi per determinaredirettamente il flusso logico delle opera-zioni successive. Facciamo un paio diesempi. Un editore di testi viene chia-mato passandogli il nome di un file. Ilprogramma si accorge che il file nonesiste. Nessun problema: se ne creauno nuovo con quel nome. Il programmasi è limitato ad operare in modo diversosaltando così il problema. A questo pun-to l'editore cerca il file di configurazioneLo trova ma alcuni campi nel file nonsono corretti. Il programma usa quelli didefault ma avverte l'utente che c'è qual-cosa di sbagliato nel file di configurazio-ne. L'utente modifica il file, gli cambianome e cerca di salvarlo. Il programma siaccorge che esiste già un file con quelnome e non sa che fare. Non può certodecidere lui se è il caso o no di rimpiaz-zarlo. E se l'utente si fosse sbagliato?L'editore allora emette un messaggiochiedendo all'utente di confermare ilsalvataggio del nuovo file sul preceden-te. A questo punto l'utente chiede alprogramma di aprire una nuova finestra.Il programma ci prova, ma non c'è abba-stanza memoria. Messaggio di errore:((Non posso aprire la finestra. Non c'èabbastanza memoria per farlo)). L'utenteallora chiude altri lavori concomitanti, manel far questo chiude per errore ancheun processo collegato all'editore stesso.Chiede di nuovo, quindi, di aprire laseconda finestra, ma a questo punto ilprogramma principale si accorge chenon può comunicare con il processo diservizio che utilizzava per aprire e chiu-dere i documenti, emette un messaggiodi errore di elevata severità e, non poten-do neppure chiudere il documento prin-cipale, libera tutte le risorse allocate eritorna il controllo al sistema operativo.

In questo scenario abbiamo visto unpo' tutte le varie possibiità che si posso-no presentare. In pratica un buon pro-gramma deve poter comunicare conl'utente e ricevere da questi delle diretti-ve, anche quando il modo normale di

MCmicrocomputer n. 98 - luglio/agosto 1990

Page 2: Programmare in C su Amiga (24)

1ft la finestra a cui il quadro è associato o NUlL *//* il testo infofllèltivo sopra i pulsanti *11* il testo del pulsante positivo (a sinistra) *//* il testo del pulsante negativo (a destra) */1* il segnalatore relativo agli event; "positivi" *//* il segnalatore relativo agli eventi "negativi" */1* la larghezza del quadro * I1* l'altezza del quadro */

operare non prevede esplicitamente unainterazione diretta. I messaggi che unprogramma può emettere nei confrontidell'utente sono in sostanza di tre tipi:Informativicon i quali il programma avverte che uncerto evento, in genere previsto, si èpuntualmente verificato. Ad esempio, aseguito della richiesta di salvare un do-cumento, il programma effettua l'opera-zione e quindi emette a video il messag-gio «Documento salvato)).Di Avvertimentocon i quali il programma denuncia unasituazione anomala ma non tale da impe-dire di effettuare l'operazione. A secon-da della situazione il programma puòrichiedere o meno l'intervento dell'uten-te. Un esempio è quello del salvataggiodi un documento su uno già esistente, ol'identificazione di campi errati nel file diconfigurazione.Di Errorecon i quali il programma denuncia ilverificarsi di una situazione che rendeimpossibile il corretto svolgimento diuna determinata operazione. Anche in

SINTASSI:

long AutoRequest(

struct Window "", struct lntuiText "", struct IntuiText *, struct IntuiText *, 10"g,long, long, long);

ESEMPIO:

questo caso l'utente può essere chiama-to ad intervenire, infilando, ad esempio,il dischetto che contiene il documentoda caricare nell'unità disco. Se l'erroretuttavia è grave, il programma può anchedover sospendere l'esecuzione se nonaddirittura terminarla del tutto. Ad esem-pio, se si è cercato di stampare ma nonrisulta esserci alcuna stampante collega-ta al computer, il sistema avvertirà l'u-tente della cosa e riprenderà quindi ilnormale corso delle operazioni. Se vice-versa un programma si accorge che nonpuò aprire, diciamo, in intuition.library,il flusso logico salterà direttamente allaprocedura di chiusura del programmastesso.

In tutti i casi l'utente dovrebbe essereavvertito di cosa è successo o sta succe-dendo, anche se non è in grado in quelmomento di intervenire. Se ad esempiomanca una libreria di sistema, il pro-gramma, prima di terminare, dovrebbeavvertire l'utente di quale libreria si trat-ta, dandogli così la possibilità di installar-la prima di lanciare di nuovo il pro-gramma.

.•• Figura lAutoRequestO.

Figura 2 - stdarg.h.T

AMIGA

Nei sistemi mono-tasking bastavaemettere un messaggio a console. Inquelli multi-tasking basati su una inter-faccia a finestre, è spesso necessarioaprire una finestrella contenente il mes-saggio emesso e, possibilmente, un si-stema di controllo interattivo che per-metta all'utente di dire al programma:IIOK. Ho letto. Adesso fai così ...».

In questa puntata vedremo come sipuò implementare un meccanismo delgenere nel nostro programma scheletro.

Prima di passare alle modifiche effet-tuate, è necessario introdurre due nuovetecniche, una sempre relativa ad Intui-tion, l'altra propria di tutti i compilatori Cconformi allo standard ANSI.

1/ quadro automaticoCertamente vi sarà capitato più di una

volta di veder apparire nell'angolo in alto asinistra dello schermo del WorkBenchuna finestrella rettangolare con la richie-sta di reinserire un determinato disco o diattivare la stampante. A volte è bastatoeffettuare l'operazione richiesta per farlascomparire e continuare a lavorare senzaulteriori interruzioni, a volte avete dovutoselezionare il rettangolino con la scrittaretry o quello con la scritta cancel per po-ter andare avanti. La finestra in questionesi chiama quadro di sistema [system re-quester}, ed è uno dei più semplici quadriche l'Amiga vi permette di costruire.

Un quadro è in sostanza una finestrapensata come supporto a tutta una seriedi elementi il cui scopo è quello diricevere e fornire informazioni al sistemao ad un programma che in esso gira. Ad

long fatto = Ol;

struct IntuiText *testoBase •

9,1,JAH1,SB,5 ,NUll, "Rilletti il dischetto!" ,NUll};struct IntuiText *testoPositivo .•

8,l,JAM1,7,3,NUll,"Va bene",NUll};struct lntuiText *testoNegativo ""{

8,1 ,JAM1, 7,3, NUll, "Cancella" ,NUll};

long eventi Positivi = OISKINSERTEOjlong eventiNegativi .• NUll;

long larghezza" ZS8jlong altezza = lZOj

fatto :z AutoRequest (w, testoBase, testoPos iti vo, testoNegati vo,event i Pos it ivi , event i Negat i vi ,larghezza, a l tezza) j

MCmicrocomputer n. 98 - luglio/agosto 1990

Nifndef _STOARGMNdefine _STOARGM l

Nifndef _VA_LISTNdefine _VA_LIST ltypedef char *va_list;Nendif

Ndefine va start (a,b) a:{ehar ')(&b+l)Ndefine va=arg(a,b) '((b ')( (a+=sizeof{bll-sizeof{b) Il#define va_end(a) /* end of varg a */

fU

• Define NULL if H's not already defined

• fNi fndef NULLNdefine NULL {void ')9Nendif

Nendif

211

Page 3: Programmare in C su Amiga (24)

AMIGA

void curva (tempo)struct t. ·telllpOi

(va_list vl; 1* definisci una lista di p~ra.etri il nUlero variabile "Yl" *'

printf("INHRO %ld\n",1Z34L); /* st •• ,. INTEU 1Z34,rintf("ESAOEC %IX\n", 1234L); /* st..,. ESAIEC I189t4<lZ

*/*/

Figura 5 - Funzioni della famiglia vprintfll.

extern int vprintf(char *, va list);extern int vfprintf(FILE *, chAr *, VAlist);extern int vsprintf(char *, chilr *, VA=liSt);

nire ma, proprio per questo, poco flessi-bile e di limitato utilizzo. Il quadro auto-matico (vedi schermo pubblicato nellaprima pagina dell'articolo) è un rettango-lo di altezza e larghezza definibili cheviene visualizzato sempre nell'angolo inalto a sinistra dello schermo, sebbeneuna volta emesso, possa essere sposta-to dall'utente essendo dotato di barra dispostamento. Il quadro contiene tre ele-menti, un testo e due controlli a pulsan-te, ognuno contenente a sua volta untesto. Il testo posizionato sopra i pulsantiserve a fornire informazioni e, in genere,a richiedere all'utente di effettuare unadeterminata operazione. Questa può es-sere da sola sufficiente a «soddisfare» ilquadro o può richiedere la selezioneesplicita di uno dei due pulsanti. È anchepossibile che, data la peculiarità di unadeterminata richiesta, sia disponibile unsolo pulsante, invece di due.

Facendo riferimento alla figura 1, ve-diamo ora come si invoca un quadroautomatico e quali sono i parametri dapassare alla funzione apposita.

La funzione si chiama AutoRequestO.Il suo compito è quello di creare automa-ticamente il quadro, mettersi in attesa diuno o più eventi che soddisfino la richie-sta effettuata, e quindi di cancellare ilquadro deallocando la memoria utilizzataritornando al programma il tipo di rispo-

sta ottenuta. Questa può essere solo didue tipi: positiva o negativa E positivase l'utente ha selezionato il pulsantepositivo, posto a sinistra, oppure se ilsistema ha segnalato uno degli eventidefiniti dal programmatore come «positi-vi». È negativa se viceversa l'utente haselezionato il pulsante negativo, posto adestra, oppure se il sistema ha segnala-to uno degli eventi definiti dal program-matore come «negativi». Nel primo casola funzione ritorna il valore TRUE, nelsecondo caso il valore FALSE. In figura èanche mostrato un esempio. Nel nostrocaso è stato definito l'evento relativoall'inserzione di un dischetto in una unitàdisco come evento positivo, atto cioè asoddisfare il quadro. Grazie a questapossibilità l'utente deve solo inserire ildischetto richiesto nel computer, senzanecessariamente dover selezionare an-che il pulsante di sinistra, cosa cherende molto più flessibile l'utilizzo deiquadri di sistema.

Il quadro può essere associato ad unafinestra oppure, se il primo parametropassato è nullo, la funzione creerà unafinestra ad hoc a cui associare il quadro.Inoltre un quadro automatico deve con-tenere sempre almeno il pulsante nega-tivo. Quello positivo può essere omessopassando NULL al posto della strutturaIntuiText relativa al pulsante sinistro.

Quando parleremo più in dettaglio deiquadri, analizzeremo anche come funzio-na internamente la funzione AutoRe-questO. Per il momento l'importante èaverne capito l'utilizzo.

Un'ultima cosa prima di proseguire.Affinché la funzione AutoRequestO ri-torni il controllo al programma chiaman-te, è necessario che Intuition sia liberodi girare ed accettare la risposta dall'u-tente. Se l'utente preme il bottone didestra del mouse ed il segnalatore ME-NUVERIFY era attivato, Intuition si po-ne in attesa che il programma rispondaall'indicazione di verifica dell'apertura diun menu. D'altra parte il vostro codicedi controllo situato nella proceduraHandleEventO non può girare finchéAutoRequest() non torna il controllo alvostro programma. Risultato: un circolovizioso da cui non ne uscite più [dea d-lockJ.

Per evitare tutto ciò bisogna tempora-neamente cancellare il segnalatore ME-NUVERIFY dalla lista degli eventi legatialla finestra interessata, se era statoattivato in precedenza, e quindi riattivar-

Figura 3Utilizzodelle macrova-xxx.

AFigura 4 - printf().

*/*/*/*/

va arg(anyarg,.nytype); '* carichialo ora in "iinyarg" il contemlto del */- /* prilo para.etro della lista (tipo "anyty,e") */

va arg(otharg,othtype); /* carichia.o ora in "otharg" il contenuto del */- . /* secendo para.etro in lista (tipo "othtype") */

/* CORPOOELLAFUNZIONE*/

/* Qui inizia l'estrazione dei vari argolenti. [' responSilbiliti del/* prograllllatore:/* _ associare a ciascun argullento il tipo corrette/* - sapere quando h, lista è finita

va start(vl ,tempo); 1* inizi.lizza la lista - "yl" pwnta ora alb list. *'- 1* che segue il prilo para.etro, cioè "te.pe" */

anytype anyargf f* ESEMPIO: questo è un argoHflto di un qualche tip8 */othtype otharg; {* questo è un altre argelento di tipo diverso */

esempio, il pannello di controllo delvostro videoregistratore è un «quadro».Su di esso sono infatti disponibili unaserie di pulsanti, cursori, indicatori divario tipo, che vi permettono di operaresul VCR e di verificarne il corretto funzio-namento.

Nell'Amiga i vari elementi di controllosono detti gadget. D'ora in poi useremoindifferentemente sia il termine ingleseche il termine italiano controllo (non è latraduzione letterale, ma è il termine chedi fatto si usa per le varie apparecchiatu-re che usiamo ogni giorno). Abbiamo giàvisto quando abbiamo parlato di finestrealcuni controlli di sistema associabili aduna finestra: i controlli di profondità,quello per la chiusura della finestra, labarra di spostamento, il controllo delledimensioni. Intuition ci mette e disposi-zione vari tipi di controlli e svariati modidi utilizzo e di interazione con essi.Alcuni sono del tipo a pulsante, cioèvanno «spinti» con il puntatore delmouse per essere attivati, altri sono acursore, cioè vanno agganciati e fattiscorrere lungo un binario od all'interno diun rettangolo definito, altri ancora sono acampo d'ingresso, cioè permettono l'im-missione di informazioni di vario tiposotto forma di stringa di caratteri. Vedre-mo tutto ciò in dettaglio dalla prossimapuntata, quando incominceremo a parla-re dei vari tipi di contr.olli. Per il momentovediamo un modo estremamente sem-plice per generare un quadro tipo quellodi sistema senza dover definire le variestrutture associate al quadro stesso edai controlli che utilizZél.

Il quadro in questione si chiama qua-dro automatico [autorequesterl. È unquadro estremamente semplice da defi-

212 MCmicrocomputer n. 98 - luglio/agosto 1990

Page 4: Programmare in C su Amiga (24)

/* '"*.*** * .•*." *." ••• '"** *-* .*.*.. *.* ****** ._---_.- _.* * *-* * *-* * * ** ** * -------- * * ** *** ShowMsgReq: visual izza un lIIessaggio in un quadro ***** ********* ***** "'---*-* *** * * * *_w __ • * ---"'- ** ** ** ---- -*-*_._"''''- * ***** *-*._-'" I

AMIGA

/'Defi n il ion i da precollpi lare per generare la tabella SKlUSRH. SYM

'/

/*

IB ,l,JAM1, SB, S, NULL,NULL,NULL),IB,l,JAM1, 7,3,NULL,NULL,NULL),IB,l,JAM1, 7,3,NULL,NULL,NULL)

);

BOOL ShowMsgReq(wptr, .sg, type)struct Window *wptr;char *.59;UWORO type;

BOOL answer;SHORTwidth;ULONGoldflags;ITXT mtxt [3] •(

Che tipo di bottoni?*/

/*P*/H (type & PBT_YES)el se/*N*/if (type & MBT_NO)el se

/' Risposta dell 'utente *//* Larghezza del quadro */1* Sal va temporanea.ente 10 stato IDCMP *1

l'" Messaggi o ••I/* Testo positivo *//* Testo negativo */

mtxt[l].IText· (UBYTE *)"Sì";mtxt[l].IText = (UBYTE *)"Continua";mtxt [2]. IText • (UBYTE *)"No",.txt [2]. IText • (UBYTE *)"Cancella";

/'Costanti

* /Ndefine ONEGADGOxBOBONdefine TWOGADGBxBBBlNdefine PBT_VES BxB10BNdefine NBT_NO BxB2BB

/,U Tipi'/

typedef struct Node NOOE;typedef struct Hessage EMSC;typedef struct IntuiMessage IMSGjtypedef struct IntuiText nXTjtypedef struct Menu MENUjtypedef struct Menultu ITEN;

typedef struct iOesc{

UBYTE c.d;UBYTE'txt,UBYTE'alt,

/*** Calcola la larghezza del quadro in Illodo da farei entrare tutto.** Posiziona il .essaggio al centro del quadro.* /

mtxt [B].Ilext = (UBYTE *)msg;width • ITXTL(&.txt[2)) + 7,H {type & TWDGADG)width += IlXTL(&mtxt[l)) + 7,width • max(ITXTL{&mtxt [B)) ,width) + lBB,mtxt[B].LeftEdge: «(width - ITXTL(&mtxt[B]ll » l) - 5;

/*Pri.a di chi ••• re AutoRequestO, disabilita lo stato MENUVERlFY,per evitare che Intuition si ponga in attesa della risposta enon possa a sua volta gestire il quadro ("deadlock").

}lDESC,

/*U Prototipi delle funzioni di servizio'/

int TotTextNumber( IOESC*, int );BOOl ShowHsgReq (struct Window *, char *, UWORO);void SetupHenu ( HENU *, MENU., BYTE ., nEM· );void Setupltelllist( nEM·, nEM·, int, USHORT, nXT *, IDESC *, nEM *. );void SetExclude (nEM *, int, int );void ClearExclude ( nEM *, int, int )jvoid CloseSafelyWindow ( struct Window *, struct TextFont * );

*/H (wptr !. NULL)l

oldflags = wptr->IDCMPFlags;ModHylDCMP(wptr,oldflags & -MENUVERlFY);

answer = (BOOL)AutoRequest (wptr ,&mtxt [B]. (type & TWOGADG)7&.txt[l] :NULL,&mtxt [2],NULL,NULl,width, SB);

H {wptr != NULL) ModHylDCMPlwptr,oldflags);return (answer);

•Figura 7 - sklusrh. c.

••• Figura 6Show MsgReq().

ne utilizzata, le misurazioni effettuatedurante l'arco della giornata non hanno lastessa precisione, per cui è necessariointrodurre un vettore di pesi che tenganoconto dei differenti errori di misurazionedeterminati nel tempo. Il prototipo vaquindi modificato come segue:

lo una volta che AutoRequestO vi harestituito il controllo.

Funzioni a parametri variabiliUna delle funzioni più utilizzate in C è

senza dubbio la printfl). Lo scopo diquesta funzione è quello di convertiregli argomenti che le sono stati passatisecondo un certo formato e di includerliin una stringa di caratteri ASCII daemettere a video. La caratteristica piùpeculiare, tuttavia, di tale funzione - edi quelle appartenenti alla stessa fami-glia, come scanfO - rispetto alle altrefunzioni interne del C, è quella di nonavere a priori un numero di argomentipredefinito. In pratica è possibile passa-re a queste funzioni da uno ad enneargomenti, senza dover dichiararne ilnumero prima. Tali funzioni sono dettea numero di parametri variabile. Si tratta

MCmicrocomputer n. 98 - luglio/agosto 1990

di una l'lroprietà estremamente utile,che può far comodo in molte situazioni.Ad esempio, immaginiamo di doverscrivere una funzione curvaI) che calco-li e tracci sullo schermo la curva chemeglio approssima una serie di datisperimentali, per esempio l'umidità rela-tiva ad un certo giorno dell'anno, con lacondizione che i dati possano anchenon essere presi ad intervalli regolarinel tempo. Avremo allora in ingressodue vettori, uno relativo all'ora alla qualesono stati effettuati i vari rilevamenti, el'altro relativo alle corrispondenti misu-razioni effettuate. Il prototipo sarà allora

void curvaI slrucl 1m *lempo, floal *umido);

dove tempo punta al vettore dei tempied umido a quello dei dati rilevati. Ad uncerto punto ci accorgiamo che per le ca-ratteristiche proprie della strumentazio-

void curvaI slrucl 1m *lempo. floal *umido.floal *peso);

Ovviamente nei due casi la formulautilizzata sarà differente, tuttavia sareb-be ridondante scrivere due differentifunzioni, una nel caso in cui i dati speri-mentali hanno tutti lo stesso peso, eduna in cui i pesi siano differenti. Po-tremmo allora essere tentati a scriveresolo la seconda, ma questo ci costringe-rebbe a passare comunque il terzo vet-tore, riempito da una serie di valoriidentici, ad esempio da uno. Si tratta diuna soluzione sporca che, oltre a co-stringere il programmatore ad uno spre-co inutile di memoria. riduce comunquele performance di curvaO dato che uti-lizza comunque la formula completa an-che nel caso semplificato. Come fareallora, visto che comunque un prototipova definito? Semplice. Basta definireuna funzione a parametri variabili.

213

Page 5: Programmare in C su Amiga (24)

AMIGA

/* 11"" 11 * 11 11 11 •• 11 .• * * 11 11 11 11 ** 11 * .•11 11 11 ••• 11 11 11 11 11 *** 11 11 '" 11.11 11 .••..•••.• * •••••••• * *. 11 .• 11. * •.•.••• * _.*•• H HenuPlck: gestisce l'evento MEHUPlCK •••.•* ",;* .•.••••.•••• #1 •• * ** * .•*.* *. * * * * .•* * •••• 11 * ll' 11 .• * * 11 * *. *** .•••.•••*. * ** •• ** *.* ••• ** *'/'U Alcune definizioni per la stalllpa

'/Ndefine PRT MENU(o) printf{"Menù ['•• lln", (o))Ndefi ne PRT- IHM(v ,w) printf("Voce [%slln", ite.na.e[vl [wl. txt)Ndefi ne PR(SUBI (s, t ,u) printf("Sottovoce [%slln", subinaoe[s][tl [ul. txt)

case MEtIU ZOO:PRT MENU(MENU ZTX);(vold)ShowMsgReq(whichwin,MENU_ZTX, TWOGADG); /' CONT. & CANC. '/switch (itemnu.)(

case IHM ZlB: PRT IHM{MENU ZOO,IHN ZlO); break;case IHM-ZZO: PRT-IHM(MENU-ZOO,IHN-ZZO); break;case IH(Z30: PR(ITEH{HENU)00,IH(Z30l;

switch (subnu.)(

int H_MenuPick(.sg)IHSG *msg;

l'BLOCCO PER LA GESTIONE DEI CODICI

'/swi tch (menunu.)(

case MENU 100:PRT MENU(HENU lTX);(vold)ShowHsgReq(whichwin,HENU_lTX,ONEGAOG); /' Solo CANCELLA '/switch (itemnum)(

case SUBI Z3l:PRT3uBI (HENU_ZOO, ITEH_Z30,SUBI_Z3l); break;

case SUBI Z3Z:PRT3UBI (MEMU_ZOO, ITEM_Z30,SUBI_Z3Z); break;

case SUBI Z33:PRT3uBI (HENU_ZOO, ITEM_Z30.SUBI_Z33); break;

case SUBI Z34:PRT3UBI (HENU_ZOO, IHH_Z30,SUBI_Z34); break;

)break;

)break;

return(GOAHEAD) ;

)break;

)break;

case IHH_ISO: PRT_IHM(MENU_IOO,IHM_1SO); break;

case SUBI 141:PRT3UBI (MENU_IOO, ITEM_140,SUBI_14l); break;

case SUBI l4Z:PRT3UBI (HENU_100, IHN_140,SUBI_14Z); break; cro dipende infatti dal sistema, anche se

l'utilizzo è poi lo stesso in tutti i sistemi.Ma come si fa a sapere quanti argo-

menti sono stati passati, e chi verificache il tipo associato a ciascun argomen-to è quello corretto? La risposta è: è unproblema del programmatore, non delcompilatore. Ad esempio, nella printf()la funzione è in grado di sapere il numerodi argomenti passati contando il numerodi sequenze di sostituzione nella stringadi controllo, quelle cioè che iniziano colsegno di percento. In questo caso lestesse sequenze dicono al programmacome interpretare i vari argomenti perquello che riguarda il tipo. In questomodo, a parità di argomenti, è possibileeffettuare associazioni differenti, comemostrato in figura 4. Un'altra tecnicapotrebbe essere quella di passare comeprimo parametro proprio il numero diargomenti passati, o di utilizzare un argo-mento tappo che dice al programma cheè arrivato alla fine della lista. In quanto altipo degli argomenti, se-è vero da un latoche il controllo è lasciato tutto al pro-grammatore, non avendo alcun modo ilcompilatore di verificare la coerenza trala dichiarazione iniziale e l'utilizzo dellafunzione, è anche vero che ciò ci mette adisposizione una tecnica potente, che vamolto al di là del passaggio di una seriefissa di elementi dello stesso (vettori) odi differente (strutture) tipo, permetten-do di utilizzare la stessa funzione perpassare oggetti anche molto differenti.Ad esempio, si può pensare di creareuna funzione somma() che è in grado digestire sia la somma di enne numeri

Figura 8H_MenuPick{).

guendo la falsariga riportata in figura 3.Come si può vedere, l'unico parametroriportato nella testata della funzione èquello dichiarato esplicitamente nel pro-totipo. Va quindi dichiarata una variabiledi tipo va_list. Questa variabile rappre-senta in sostanza il puntatore alla listadegli argomenti passati dopo il parame-tro esplicito. Quindi essa va inizializzatautilizzando la va_sta rt() , alla quale vaanche passato il parametro dichiaratonella testata. A questo punto vi (nelnostro esempio) punta al primo argo-mento della lista variabile. Per caricarsiquesto valore in una variabile locale (oglobale) di un certo tipo, è necessariousare la véLarg() la quale vuole in in-gresso sia la variabile in cui deve esserecaricato il parametro passato, sia il tipodi tale variabile. Essa inoltre sposta inavanti il puntatore alla lista, che vienecosì a puntare all'argomento successi-vo. Si procede così per tutti i parametridella lista. A questo punto va chiamata lava_end() che si occupa di chiudere lalista. Nel caso del Lattice C è una macronulla, ma non è così in tutte le imple-mentazioni. La definizione di queste ma-

case IHH 110: PRT IHH(MENU 100,ITEM 110) break;case IHH-IZO: PRT-IHH(HENU-100,IHM-1ZO) break;case IHM-130: PRT-IHM(MENU-100,ITEM-130) break;case IHH-140: PRT-IHH(HENU-100,IHM-140)

swit~h (subnu;) - -{

Abbastanza intuitivo, no? Ma come sifa a gestire un numero variabile di argo-menti? E come fa il C a controllare a que-sto punto la coerenza dei tipi passati conquelli definiti nel prototipo, visto che ineffetti nel prototipo non sono stati affat-to definiti! A tale proposito ci vengono inaiuto tre macro standard ANSI, definitenel file stdarg.h riportato in figura 2.Vediamo il loro utilizzo, lasciando alletta-re il compito di comprendere il meccani-smo che sta sotto alloro funzionamento.

Innanzi tutto il prototipo della nostrafunzione deve sempre contenere alme-no un argomento esplicitamente. Non ècioè possibile un prototipo del tipo

void funzione L..);

Quindi la funzione va codificata se-

Lo standard ANSI prevede la possibili-tà di definire un prototipo anche perquesto tipo di funzioni, utilizzando unparticolare simbolo: i tre puntini. Nelnostro caso il prototipo verrebbe adessere scritto così:

void curva( strucl 1m *tempo, ...);

214 MCmicrocomputer n. 98 - luglio/agosto 1990

Page 6: Programmare in C su Amiga (24)

AMIGA

/ .•• CloseAll: chial'llate di chiusura........................................................................... /void CloseAll(lIsgid) /. ordine inverso rispetto StartAllO!!! ./

int IIsgidj/ .

BOOl quit • lRUE;char outmsg [MSGlENGlH);va_l ist vl i

}1f (Ilask & HSK MEH) FreeRemellber(&rellle.ory. TRUE);if (mask & HS(WIN) CloseWindow(w);if (mask & MSK GFX) Closelibrary(GfxBase};if (mask & MS(INl) Closelibrary(IntuitionBase};Exit (O);

if (osgid != EHSG NONE)( -

va_start(vl,.sgid)i /. Ora "vl" punta alla lista delle variabili ./(voi d) vspri nt f (outllsg. [rrorMessages [.sgi d] •vl ) j

/'•• Funzi ona anche se w z"" NUll'/

if (osgid .= EHSG SAFEQUIT)quit • ShowHsgR;q(w,outosg, lWOGAOGI PBl VESI NBl NO);

else - -

quit = !ShowMsgReq(w, outllsQ.ONEGADG);va_end(vl) ;

i f (!quit) returnj

i f (mask & MSK MH)( -

Cl earHenuStrip(w) j

Hodl fyIOCHP(w,SaveFlags) j

/. Allora si continua!!! ./

•• StartAll: chiamate di partenza........................................................................... /void StartAll(){/'

Apre le librerle (Intuition & Graphics) e la flnestra'/

In questo primo esempio, CloseAll O è chiamata ad emettere unmessaggio variabile, cioè un messaggio che contiene un parametromodificabile dal programma stesso durante l'esecuzione. Nel caso in esame sitratta di una stringa, cioè di un parametro di tipo %s.

w = (struct Window ·)OpenWindow(&nw)jif (w == NUll) CloseAll (EHSG CANlOPEN, "la finestra");.ask 1= HSK_WIN; -rp '"'w->RPort; / •. RastPort per la grafica ./up = w->UserPort j 1* Porta utente per IDCMP */

/,•• Alloca dinallicamente le strutture per i .enù e le voci•• Usa AllocRememberO per una gestione ordinata e flessibile'/

rememory = NUll j

\"cl secondo esempio, Cl oseA 11 O è chiamata ad emettere unmessaggio fuso, cioè un messaggio che non contiene alcun parametroe quindi non modificabile dal programma stesso durante l'esecuzione.

cata tra l'utente ed il programma, princi-palmente mirato alla gestione dei mes-saggi d'errore.

Per far questo è necessario introdurredue nuovi file ed una nuova procedura,con qualche modifica qua e là al codicegià scritt{).

ShowMsgReq()Questa procedura, riportata in figura

6, ha il compito di emettere un quadroautomatico contenente un messaggioed uno o due pulsanti, a seconda dellenecessità. Essa accetta in ingresso treargomenti. Il primo è il puntatore allafinestra alla quale il quadro va associato,il secondo è la stringa di caratteri checorrisponde al messaggio da emettere, ilterzo è il tipo di quadro da emettere. peril momento ho previsto due possibiliclassi di quadri, combinabili fra loro aformare quattro tipi distinti:• la prima classe è basata sulla presen-za di uno o di due pulsanti;• la seconda classe è definita sulla basedel testo da associare al, od ai pulsantidel quadro.

interi, sia quella di enne numeri com-piessi a coefficienti interi. AI lettore ilcompito di scoprire come ...

È anche possibile non aver bisogno diestrarre i singoli argomenti dalla listautilizzando la va_argO, qualora la listavada passata così com'è ad una o piùfunzioni che a loro volta sono a numerodi parametri variabile. È di fatto il nostrocaso, come vedremo tra poco, avendoutilizzato nel nostro scheletro un'altrafunzione interna del C ANSI, la vsprint-fO. Questa altri non è che una sprintfOche gestisce direttamente una lista va-riabile di argomenti. In figura 5 sonoriportate tutte le funzioni C ANSI chehanno fra i parametri in ingresso unalista variabile di parametri.

Da notare che, benché il Lattice C 5.0sia completamente conforme allo stan-dard ANSI, e quindi supporti completa-mente sia le macro che le funzioni fin quidescritte, esse mancano completamen-te nella documentazione, lasciando pen-sare, erroneamente, che non siano sup-portate. Il problema è comunque secon-dario, dato che chi fosse interessato,può trovare in un qualunque manuale diriferimento dello standard ANSI per il Ctutte le informazioni a riguardo. L'impor-tante è che il compilatore segua talestandard (come di fatto fa il Lattice C5.0).

/I programma scheletroVediamo ora di applicare quanto ap-

preso fin qui al fine di implementare unmeccanismo di comunicazione semplifi-

MCmicrocomputer n. 98 - luglio/agosto 1990

AFigura 9 - CloseAI/O.

Figura IO - StartAI/O ~

/,*. Menu'/

menuli st '" (MENU*)A 11 ocRellellber (&remellory l

MENU NUM ' sizeof(MENU). MEMF ClEAR);if (menulist =. NUll) CloseAl1(EMSG NOMEMORV);mask 1= MSK_MEMj ;* Nota, basta la pri.a al locazione */

In figura 7 è riportato il sorgente dellatabella simbolica utente che contiene, intesta, le quattro costanti usate per iden-tificare le varie istanze delle due classi. Ilfile contiene anche tutti i prototipi dellefunzioni di servizio, come al solito.

Vediamo in dettaglio la proceduraShowMsgReqO.

Per prima cosa definiamo tre struttureIntuiText standard. A seconda del tipospecificato dal programmatore selezio-niamo uno dei due testi previsti perognuno dei due pulsanti. Calcoliamoquindi la larghezza del quadro in mododa poterci far stare tutto il messaggio ecarichiamo il messaggio nel testo princi-pale. A questo punto disabilitiamo lostato MENUVERIFY, come raccoman-dato in precedenza, dopo aver salvato ilvecchio stato in una variabile tempora-nea (ovviamente solo se il puntatore allafinestra non è nullo). Chiamiamo quindila funzione AutoRequestO e restituia-mo il controllo al chiamante dopo avereventualmente restaurato lo stato origi-nale IDCMP avendo cura di riportare ilresponso fornito dal quadro automatico.Da notare che la funzione AutoReque-

215

Page 7: Programmare in C su Amiga (24)

AMIGA

/*Ques to è il ti 1 e che conti ene tutt i i aessaggi: gli avvert i.ent i t

quelli di errore, quelli infofllat;v;, e via dicendo.

/*U Questo e il file che contiene tutti gli identifiçativ; per i aessaggi:** gli avvertimenti, quelli di errore, quelli inforllativi, e via dicendo.

*/extern ch.r *ErrorMessages [];

stO mette in attesa il programma di unarisposta da parte del sistema o dell'uten-te. Qualunque operazione effettuata suimenu mentre il quadro è in attesa vienequindi accodata per essere successiva-mente elaborata allorché il controllo ri-torna al programma principale.

In questra procedura ci sono due puntiche sarebbe opportuno modificare. Unoin quanto potrebbe portare ad un poten-ziale errore, l'altro per ragioni di manteni-bilità. A voi trovarli ...

H_MenuPick()

*/char * ErrorMessages [] '"{

"Sei propri o s icuro?","Non riesco ad aprire %s"1"Non ho abbastanza lIIelloria"

};

Nifndef SKLMSG_HNdefine SKLMSG_H

Figura Il - sklmsg.c.

Nendif

Ndefine HSGLENGTH 256

Figura 12 - sklmsg.h.

Figura 13 - main().

modello = "Non riesco ad aprire il file %s /*diciamo %s un parametro */

(void) sprintf (messaggio, modello, nomedel-file);

"Non riesco a trovare "pippo"». D'altrocanto emettere un messaggio genericodel tipo "Non riesco ad aprire il file» puòessere di scarso aiuto se non addiritturaconfondere le idee all'utente, a volte.Come fare, allora? La tecnica da mesviluppata consiste nello scrivere i mes-saggi seguendo il formato dei modelliutilizzati da funzioni come printfO, inmodo da utilizzare poi la sprintfO pergenerare il messaggio a partire dal mo-dello base più i dati contingenti relativiallo specifico errore Nell'esempio di cuisopra il modello sarà allora

mentre il messaggio finale sarà generatocome segue

'* [ffettuiallo le chia.ete di partenza. .,'* Costruiallo i .enù da associare alla finestra. .,St.rtAll O;Bui l dMenus () i

for(;; )(

void •• inO(

/* •• * .••.***** * * ** * ••• ** * * ** *** ** ** * ••••••••••••••••••••••••••••••••••••••••••••••••••••• ••••••• •••••••• ••••••••••••• *. *.*. **. * * **. * * *. * •• *. *. **.*. * ••••••

** main: programllla principale*•• * •• **. *. * * *•• *. *. *. *. *. * * ••••••• * * •• *••• * * * * ••• * •• * ••• * *. * *. * * *. ** * *. ** * *•• *. *. *.* *. *. *.* *. *. * ** *. *. *. * *. **. *. * ••• *. * *. * *•• * * ••• ** **~.*.*. *. ** * *. * *. ,

letsGo ();. ,. Va bene. E' tutto pronto. Andia.o! *'CloseAll (EMSG_SAFEQUIT); /* FinHo. Chiud; •• o tutto. */

/* -------------------------+---------------------------------------- .//* Se si arriva qui, vuol dire che CloseAll{) non ha ter.inato il .//. programma, dietro richiesta dell'utente. Il ciclo continua... *//* --------------------_.-------------------------------------------- */

}}

Ndefine EMSG_NONE OxFFFFNdefine EMSG_SAFEQUIT ONdefine EMSG_CANTOPEN lNdefine EMSG_NOMEMORY 2

semblarlo con gli altri moduli oggetto pergenerare un nuovo eseguibile. Ovvia-mente la cosa è più complessa se sivuole aggiungere o cancellare un mes-saggio, ma in questo caso è ovvio chebisogna quanto meno modificare la pro-cedura che utilizza il nuovo messaggio.Si guadagna comunque in flessibilità edin chiarezza. Per poter utilizzare il filemessaggi, il programma deve includereil file degli identificativi riportato in figura12. Questo file è stato separato dal filemessaggi vero e proprio per evitare didover ricompilare tutti i moduli che loincludono quando si traduce il file mes-saggi in un'altra lingua.

A questo punto sorge un problema. Ingenere un messaggio contiene delleinformazioni legate all'errore verificatosi,non predefinibili nel file messaggi apriori. Ad esempio, se un editore nonriesce ad aprire il file pippo, certo nonpuò avere nel file messaggi il messaggio

Vediamo ora come si può utilizzaredirettamente questa funzione. Un esem-pio è riportato in figura 8 dove, durante lascansione degli eventi che avviene nelciclo principale della procedura H_Me-nuPickO, oltre a visualizzare nella fine-stra CLI il nome delle varie voci selezio-nate, ho introdotto due chiamate allaShowMsgReqO. Nella prima, corrispon-dente al primo menu, viene visualizzatoun quadro con il solo pulsante negativo;nella seconda, relativa al secondo menu,il quadro riporta entrambi i pulsanti discelta.

Vediamo ora come usare in modo piùgenerale la ShowMsgReqO per gestire imolti messaggi di errore che il program-ma è chiamato ad emettere in caso dierrore nello svolgimento delle varie ope-razioni, soprattutto in fase di inizializza-zione. Il tutto cercando di sconvolgere ilmeno possibile il codice già esistente,ovviamente.

Ogni volta che il nostro programmaincontra una situazione di errore tale daimpedirgli di andare avanti, invoca laCloseAIIO per chiudere in modo pulitol'esecuzione. Sembra quindi logico con-siderare questa funzione come la natura-le responsabile dell'emissione del mes-saggio di errore prima di terminare ilprogramma. Per far ciò è necessariomodificare la procedura in questionecome riportato in figura 9. Per primacosa introduciamo un parametro in in-gresso corrispondente ali 'identificativodel messaggio che vogliamo emettere[message identifier]. Questi altro non èche l'indice di un vettore di stringhe checontiene i vari messaggi utilizzati dalprogramma. Tale vettore è contenutonel file messaggi sklrnsg.c riportato infigura 11. L'utilizzo di un file esterno per imessaggi di errore rende estremamentesemplice la traduzione dei messaggi inaltre lingue, senza dover per questoricompilare tutto il programma. Bastasolo ricompilare il file messaggi e rias-

CloseAI/()

216 MCmicrocomputer n. 98 - luglio/agosto 1990

Page 8: Programmare in C su Amiga (24)

Da notare che il modello non termina Figura 14 - sk/./mk.con un ritorno carrello (\Il) in quanto ilmessaggio non va stampato ma vienecaricato come testo di lntuition.

C'è tuttavia un altro problema. Datoche la procedura che riceve l'identificati-va del messaggio ed i dati ad essorelativi è sempre la stessa (nel nostrocaso la CloseAIIOl. e dato che noi nonsolo non sappiamo a priori il valore deivari parametri definiti nel messaggio, maneanche il numero di parametri checertamente varia da messaggio a mes-saggio, come possiamo scrivere unafunzione sufficientemente generalizzatada essere utilizzabile per tutti i messag-gi? Dovremmo scriverne una per queimessaggi che non hanno parametri, unaper quelli che ne hanno uno, e così via. Èqui che ci viene in aiuto la possibilità didefinire funzioni a numero variabile diparametri. Basta infatti definire il prototi-po della CloseAII() come segue:

void CloseAl1(int, .. );

e poi utilizzare la funzione ysprintf() alposto della sprintf() come riportato infigura. Ed il gioco è fatto!

Per finire vorrei far notare che dueidentificativi di messaggio hanno un si-gnificato particolare:

EMSG_NONE

,# •• kefll e for SKl,

NAME ::: ske l donMAIN = skl •• inPROCS '"' skl procsHSGS • skh,gSTOH = skl,ldhUSRH • ,k1 ",rh,LC • lc:1cLINK· 1c:bH.kLCO~TS • -HHUSRH).sy. -OSTARTUP·lib:c.'LIBS ""'lib:lc.l ib+1ib:a.ig •. l ibLKOPTS • SO SC NO VERBOSE

,Il SKl definitions,HNAN[): I(NAIN).o H~ROCS).o 1(IISGS) ••

I(LINK) FRON I(STARTUP)+I(HAIN)"+I(PROCS).o+I(IISGS) .• \TO I(NAHE) LIB I(LIBS) I(LKOPTS)

I(HAIN) •• : I(HAIN).c I(USRH).'y. I(NSGS).hI(LC) I(LCOPTS) I(IlAIN).c

HPROCS).o: 1(~OCS).c I(USRN).sy.HLC) HLCO~1S) 1(~ROCS). c

I(NSGS).o: 1(IISGS).cHLC) I(NSGS).c

,, pre-cupiled tabhs,I(S10H).sy.: I(STOH).c

I(LC) -ph -oHS10H).syo I(STOH).c

HUSRH).'y': I(USRH).c I(STOH).sy.HLC) -ph -Hl(S10H).sy. -ol(USRH).sy. HUSRH).c

AMIGA

che indica che non c'è alcun messaggioda emettere, e

EMSG_SAFEQUIT

che emette il messaggio finale, perverificare se l'utente vuole realmenteuscire o meno. Solo nel secondo caso

Casella PostaleTorna dopo tanto tempo la rubrica Ca-

sella Postale. Questo mese risponderòad una lettera arrivatami il 28 maggio maspedita a gennaio di quest'anno. Si riferi-sce ad un'altra lettera apparsa nella 17"puntata di questa serie di articoli.

Caro Dario,ti scrivo in relazione alla lettera di MatteoOlivieri apparsa su MCmicrocomputer didicembre.

Vorrei far notare che l'Amiga ROMKernel Reference Manual: Inc/udes &Autodocs, versione 1.3, specifica chiara-mente alla voce graphics.library/Floodche ({allo scopo di usare Flood, il Rast-Port di destinazione deve avere un validoTmpRas le cui dimensioni siano grandialmeno quanto quelle del RastPort)).

MCmicrocomputer n. 98 - luglio/agosto 1990

viene emesso un quadro a due valori,dato che esso viene emesso qualoral'utente abbia deciso di chiudere volon-tariamente il programma. In tutti gli altricasi in cui la chiusura del programma èforzata da una situazione di errore, il

Sperando di averti fatto una cosa gradi-ta, ti saluto cordialmente.

Alfonso Caschili - S. Anna Arresi rCA)

Alfonso, ti ringrazio per l'informazione.AI riguardo aggiungerò che la schedarelativa alla Flood() si trova a pagoA-86del suddetto manuale e che a pago369del primo volume dei nuovi RKMs, cioèLibraries & Devices, è riportata la se-guente affermazione:Flood-fill requires a TmpRas at least aslarge as the RastPort in which the Flood-fili in being done. This is to ensure thateven if the Flood-filling ({Ieaks)), it will notflow outside the TmpRas and corruptanother task's memory.

Il che va a maggior onore di Matteo,che spedì la lettera nel lontano luglio '89,cioè diversi mesi prima che uscissero iRKMs 1.3.

Nella vecchia edizione di questi manua-li (RMKs 1.1). infatti, tale informazionenon era riportata.

quadro ha un solo bottone che servesolamente a dare il tempo all'utente dileggere il messaggio visualizzato.

Due esempi di utilizzo della nuovaCloseAII() sono riportati in figura 10,entrambi presi dalla StartAII(). Nel pri-mo esempio il messaggio emesso è ditipo variabile, nel secondo è di tipo fisso.

main()Nel caso l'utente cambi idea e decida

di continuare, la CloseAII() ritorna ilcontrollo al chiamante. Si è reso quindinecessario modificare anche la procedu-ra principale come riportato in figura 13.

Infine in figura 14 è riportato il nuovofile di generazione che tiene conto delfile messaggi e del file degli identificativiaggiunti in questa puntata.

ConclusioneNella prossima puntata incomincere-

mo a parlare di controlli [gadgetl in mododa poter entrare in dettaglio in seguitonella descrizione dei vari tipi di quadro.Riprenderemo il discorso dei menu piùavanti, per mostrare alcune interessantitecniche avanzate. -

217