Introduzione alle API Win32

Capitolo 38: Indies: scelte di progetto

Ecco la funzione di terminazione che abbiamo, nel capitolo scorso, proposto al lettore come esercizio:

BOOL IndiEs_Finis(void) { if(pSC) { pSC->lpVtbl->Release(pSC); pSC = 0; } CoUninitialize(); return TRUE; }

Se pSC è diverso da zero, era stato acquisito, e quindi deve ora, in terminazione, venire rilasciato (lo azzeriamo poi, come da buona prassi, ad evitare futuri equivoci!). Infine, CoUninitialize deve venire chiamata, visto che alla inizializzazione avevamo chiamato CoInitialize.

 

Più interessante il problema di assegnare la funzione (IndiEs_Espr) e valutarla (IndiEs_Valuta). Lo script control offre un metodo Eval, che valuta una espressione e ne ritorna il risultato, e la cosa più semplice è sicuramente quella di usarlo per la IndiEs_Valuta; non è, tuttavia, nè il metodo più efficiente, nè il più potente dal punto di vista del nostro utente.

Dal punto di vista dell'efficienza: con Eval, obblighiamo l'interprete che usiamo a ripetere ogni volta il parsing dell'espressione; a seconda dell'interprete in gioco, questo potrebbe essere un carico notevole, rispetto ad una alternativa come usare AddCode per definire una funzione avente come corpo la nostra espressione (il che permette all'interprete di fare il parsing una sola volta, nella AddCode, e "compilarsi" una forma interna di più rapida valutazione), poi Run per la "esecuzione" della funzione così definita.

Dal punto di vista dell'utente, essere limitato a scrivere una singola espressione può essere una grossa sottrazione della potenza effettivamente messa a sua disposizione dal linguaggio interprete usato; ad esempio, a seconda del linguaggio in questione, in una "espressione" potrebbe non essere in grado di usare condizionali (IF), cicli (WHILE), eccetera.

In questo senso, sarebbe sicuramente meglio mettere a disposizione un edit multi-linea per la "espressione", con la convenzione, ad esempio, che essa deve così definire una funzione F che prende un solo argomento; di questa intera funzione possiamo fare AddCode, e così soddisfare anche le esigenze di prestazioni.

Non si può, inoltre, non rilevare che l'utente apprezzerebbe certo anche la possibilità di "caricare" il codice da un generico file di testo -- ma non abbiamo ancora visto cosa le API mettono a nostra disposizione per questo compito, e l'utente potrà comunque fare copia-e-incolla da un notepad (o altro programma editor di testi) al controllo edit multi-line di cui sopra, con meno comodità ma egual funzionalità.

 

Tuttavia -- non esploreremo ulteriormente questa strada, pur così più interessante, preferendo, invece, scegliere la semplicità d'uso di Eval. Il lettore è, come sempre, calorosamente invitato ad esplorare in proprio le ricche possibilità (cui noi ci limitiamo ad accennare, poichè questo è solo un tutorial, cioè una introduzione!); l'unica difficoltà reale nell'uso di AddCode e Run potrebbe stare nell'argomento Parameters di quest'ultima, che, e questo lo anticipiamo volentieri!, può essere gestito così...:

// chiameremo sempre la funzione F(X) static BSTR bName = SysAllocString(L"F"); // array di 1 solo elemento, di tipo VARIANT static SAFEARRAY* saArgs = SafeArrayCreateVector(VT_VARIANT,0,1); VARIANT vArg,vResult; // prepariamo il risultato (inizialmente vuoto) vResult.vt = VT_EMPTY; // prepariamo l'argomento (pari al parametro X) vArg.vt = VT_R8; vArg.dblVal = X; // insieriamo l'argomento nell'"array" static long i=0; SafeArrayPutElement(saArgs,&i,&vArg); // e finalmente chiamiamo il metodo Run: HRESULT hr = pSC->lpVtbl->Run(pSC, bName, &saArgs, &vResult); // ora, se SUCCEEDED(hr), abbiamo in result il risultato

Qui, in nome dell'ottimizzazione, abbiamo usato parecchie variabili con storage-class static, per non dover ripetere ogni volta l'inizializzazione (in C, a rigor di standard, non si può inizializzare uno static con un'espressione run-time, a differenza che in C++; in C, bisognerà dunque usare un esplicito "first-time switch", un idioma d'altronde ben noto nel linguaggio in questione!).

 

Ma torniamo al discorso più semplice...! La Eval accetta come espressione da valutare una BSTR (abbiamo già visto al capitolo precedente come crearla da una stringa "normale"), e ritorna il risultato (se ha successo) in una VARIANT. Windows offre varie API per manipolare le VARIANT; in particolare, due che qui ci interessano sono:

void VariantInit(VARIANT* pVar); che "inizializza" la VARIANT a "vuota" (si può sostituire, un po' volgarmente ma efficientemente, con l'assegnazione di VT_EMPTY al campo vt del VARIANT, che è il "tag" che ne determina il tipo); e, più importante (nel senso di, meno facilmente rimpiazzabile!-): HRESULT VariantChangeType( VARIANT* pVarDest, // destinazione VARIANT* pVarSrc, // sorgente unsigned short wFlags, // opzioni VARTYPE vt // tipo desiderato ); pVarDest e pVarSrc possono puntare allo stesso VARIANT, e le wFlags sono normalmente 0; in vt, si pone il tipo che si vuole fare assumere a *pVarSrc, VT_R8 nel nostro caso, e VariantChangeType (se l'HRESULT che torna soddisfa la SUCCEEDED, naturalmente) provvede ad eseguire la trasformazione richiesta; dopo di che, possiamo ottenere il risultato double che ci interessa, direttamente dal campo dblVal del VARIANT.

Dal punto di vista della struttura del codice, vogliamo sicuramente evitare, almeno!, di "pagare" ad ogni chiamata ad IndiEs_Valuta anche per la trasformazione da char* a BSTR; questa trasformazione, dunque, vorremo farla "una volta per tutte" nella IndiEs_Espr -- e, visto che la BSTR serve poi nella IndiEs_Valuta, dovremo farne (nella impostazione di tipo procedurale che abbiamo dato al pacchetto IndiEs) un'altra variabile static del sorgente IndiEs.c (dovrà, naturalmente, essere liberata in IndiEs_Finis).

 

Abbiamo dunque tutti gli elementi che ci servono per trasformare questo progetto in codice... e, come al solito, lasciamo il completamento di questo lavoro come esercizio al lettore, promettendo, pure come al solito, "il seguito alla prossima puntata". Ma il lettore dovrebbe davvero provare a realizzare l'IndiEs.c, imperniata sul VBScript, la cui traccia abbiamo sinora fornito, senza crogiolarsi nella pigrizia per la probabilità (certezze, nella vita, non ce ne sono!-) che il nostro prossimo capitolo presenti la soluzione già fatta... non si impara veramente un argomento di programmazione, senza provare un poco, in prima persona, a scrivere il codice in gioco...!


Capitolo 37: COM: alcune API, e le BSTR
Capitolo 39: Indies: l'implementazione
Elenco dei capitoli