Introduzione alle API Win32

Capitolo 36: il COM, visto da C (2)

Passiamo dunque ad esaminare le funzioni ("metodi") elencati nel corpo di una interface COM, così come viene definita in C, usando la macro DECLARE_INTERFACE_, vista al capitolo precedente.

Ciascuna di queste funzioni è dichiarata con la macro STDMETHOD (che garantisce l'uso di convenzione di chiamata standard, e il ritorno di un HRESULT come risultato), e ha come primo argomento la macro THIS_ (THIS, senza underline, per ragioni sintattiche, se si tratta dell'unico argomento); infine, ogni funzione è etichettata come PURE, il che, in C, non ha effetto, ma serve per l'espansione C++ (nella quale non servirebbero invece le macro THIS e THIS_, che corrispondono al "primo implicito argomento" passato da ogni chiamata a funzione "membro di un oggetto" in C++).

Notiamo, inoltre, fra gli argomenti di queste molte funzioni: una enum (di cui, per ragioni sintattiche, dobbiamo fornire una dichiarazione fittizia), vari puntatori a interface IQualcheNome (la parola interface, grazie al preprocessore, si trasforma in struct; e, di una struct che usiamo solo via puntatore, non siamo tenuti, a norma delle regole del linguaggio C, a dare una definizione), i tipi che già avevamo menzionato (BSTR, VARIANT, SAFEARRAY), e vari altri tipi definiti con le classiche macro "alla Windows", alcuni dei quali sono piuttosto chiari (UINT, ad esempio, è "unsigned int"), altri meno (ad esempio, VARIANT_BOOL è uno "short" usato come booleano, con 0==false e -1==true; OLECHAR è il tipo standard C wchar_t, che rappresenta un carattere di 16 bit a codice Unicode; ecc).

Notiamo che parecchie funzioni, per convenzione, hanno nomi che iniziano con get_ o put_, spesso "in coppia" (e con parametri di tipo X* e X rispettivamente); questa è una tipica traccia di "Automation", che, fra le sue convenzioni, ha quella che ciò che, concettualmente, è un "accesso ad una proprietà di un oggetto", avviene sempre attraverso la chiamata a una simile coppia di funzioni (per "proprietà in sola lettura", esisterà il get_ senza il relativo put_) -- una convenzione simile esiste in Java (dove i prefissi usati sono get, senza underline, e set, idem, invece di get_ e put_); le recenti versioni di compilatori C++ della Borland (C++Builder) e della Microsoft (Visual C++) permettono, facoltativamente, di usare queste coppie di funzioni, get_ e put_, con la sintassi che si userebbe per leggere o scrivere un "membro dati pubblico" della classe, approccio comune anche al Visual Basic, ma che in C++ costituisce una "estensione non-standard al linguaggio", il cui uso non può quindi essere consigliato (infatti, usando delle estensioni al linguaggio, ci si impedisce il porting ad altri compilatori standard, come qui, ad esempio, l'ottimo e gratuito GCC).

Se "in qualche modo" avessimo ottenuto un puntatore ad una simile interfaccia, lo useremmo poi, in C, in questo modo...:

HRESULT DoppioTimeout(IScriptControl* pSC) { long lMs; HRESULT hr = pSC->lpVtbl->get_Timeout(pSC,&lMs); if(SUCCEEDED(hr)) hr = pSC->lpVtbl->put_Timeout(pSC,2*lMs); return hr; }

Si noti l'idioma indispensabile: da pSC si trova con -> la sua lpVtbl; da quella, il metodo da chiamare; e lo stesso pSC va passato anche come primo argomento al metodo stesso. In C++ (senza CINTERFACE), diremmo invece:

HRESULT hr = pSC->get_Timeout(&lMs); eccetera, lasciando implicito l'uso della "virtual table" (indicata da lpVtbl) e il passaggio del this (pSC), ma ottenendo esattamente lo stesso codice eseguibile.

Si noti, inoltre, l'importante test SUCCEEDED(hr), che garantisce che, se la get_Timeout dà errore, non si procede oltre, ma si segnala l'errore al chiamante; in C++, si potrebbe, facoltativamente, scegliere di avere un "vestito", sopra l'interfaccia usata, che trasformi gli HRESULT di fallimento in "exception" del linguaggio (VC++, per default, usa quest'ottimo approccio se si adopera la sua estensione non-standard #import per accedere al COM), con un minimo costo in termini di esecuzione (ma solo in caso d'errore), e una grande semplificazione di scrittura del codice (risparmiando i continui test di if(SUCCEEDED(hr)) che altrimenti "ingolfano" il codice COM scritto in C o C++). Un approccio semplice ed efficace, in C, per ottenere una analoga semplificazione, "senza costi", è una macro simile a...:

#define _S if(FAILED(hr)) goto errore;

con la quale il corpo della funzione vista diventerebbe:

long lMs; HRESULT hr; hr = pSC->lpVtbl->get_Timeout(pSC,&lMs); _S hr = pSC->lpVtbl->put_Timeout(pSC,2*lMs); _S // eventuali altre elaborazioni errore: return hr;

Naturalmente, la risultante "linearizzazione" del codice, in una funzione così piccola, si apprezza poco, ma, quando le chiamate COM in una singola funzione sono invece parecchie, e magari vengono allocate risorse che vanno poi rilasciate, eccetera, il vantaggio di chiarezza, leggibilità, e manutenibilità, è grande.

L'unico vero costo di questo approccio sta nel parecchio tempo che esso inevitabilmente porta a sprecare negli interminabili litigi con quei "soloni della programmazione strutturata" che, non avendo capito niente sul perchè il goto vada evitato nel 99+% dei casi (la sua eccessiva potenza, e quindi il rischio che esso porta di rendere il codice meno leggibile), "naturalmente" schiumano alla bocca nel 100% dei casi in cui viene proposto, anche quando, come qui, esso semplifica il codice, e lo rende piu` leggibile, poichè usato in modo disciplinato, mirato, specifico.

Mah, tant'è: personalmente, non ho mai lasciato che la prevedibile reazione degli imbecilli mi impedisse di fare quello che considero giusto, soprattutto in campo tecnico; se il lettore, invece, apprezza più il quieto vivere, che non il senso di interna soddisfazione che viene dalla adozione delle scelte tecniche ottimali, potrà poi decidere lui di avere codice "ingombro" e/o inefficiente, per non dover subire polemiche...!-)

 

Benissimo, dunque, siamo "quasi" in grado di scrivere clienti COM (a parte "piccoli" dettagli come gestire le BSTR, i SAFEARRAY, le VARIANT, ottenere i "puntatori a interfaccia" che ci servono, e simili...:-), ma, può venire spontaneo chiederselo, perchè poi dovremmo preoccuparcene...?

La ragione è che, attraverso interfacce COM, possiamo trovare disponibili (spesso gratuitamente) dovizie di funzionalità molto interessanti, dei tipi più disparati. Ad esempio, si consideri nuovamente l'interfaccia già dettagliata -- pur restandoci misteriosa in molti suoi aspetti, non ha essa forse qualcosa di interessante, di accattivante...? AddCode, ExecuteStatement, sembra quasi un'interfaccia verso un interprete...

...beh, non lo è: è una interfaccia verso un numero, in potenza, infinito di interpreti; quella put_Language permette, infatti, di predisporre un oggetto Script Control per mettere a nostra disposizione un qualsiasi linguaggio interpretativo sia "correttamente disponibile e istallato" sulla nostra macchina!

Due simili linguaggi sono distribuiti dalla Microsoft (e quindi usabili con i vari "programmi ospite" che sanno pilotare i "linguaggi di scripting", come Internet Explorer, e il nuovo Microsoft Scripting Host, che permette di usarli al posto del vecchio e dannato .BAT per scrivere procedure di gestione della propria macchina!): VBScript, e JScript; altri ancora sono gratuitamente disponibili in rete, ce n'è proprio per tutti i gusti, dalla pragmatica potenza di Perl (Perlscript), alla sottile e potente eleganza di Python.

Se solo agganciamo, e usiamo correttamente, questa interfaccia, e permettiamo in qualche modo all'utente di passarci la stringa da usare nella put_Language, stiamo implicitamente permettendo al nostro utente di usare qualsiasi linguaggio di scripting gli aggradi, purchè correttamente istallato sulla sua macchina, anche un linguaggio che noi non abbiamo mai sentito nominare -- è questa la potenza della "separazione fra interfaccia e implementazione", che costituisce uno dei cardini dell'informatica moderna, e che COM elegge a propria ragion d'essere!

L'implementazione più agevolmente disponibile di questa interfaccia sarà offerta dall'oggetto "Microsoft Script Control" (che a sua volta sa come agganciare i "veri" interpreti di cui sopra), msscript.ocx; se avete una istallazione recente di Windows, può darsi che esso si trovi già nel vostro folder di sistema, correttamente istallato, visto che non poche delle applicazioni Microsoft "se lo portano dietro"; se no, vi conviene sicuramente scaricarlo, gratuitamente, dal sito della Microsoft stessa -- sarà accompagnato dal suo file di help, msscript.hlp, orientato purtroppo all'uso da Visual Basic, ma, non preoccupatevi: alla prossima puntata, vedremo quel poco (vabbè...:-) che ci manca, per permettere anche l'uso di alcune delle sue funzionalità dai programmi C!


Capitolo 35: il COM, visto da C (1)
Capitolo 37: COM: alcune API, e le BSTR
Elenco dei capitoli