Qualsiasi programma, che faccia uso di COM, prima di potere fare qualunque altra chiamata al COM, deve anzitutto chiamare l'API di "inizializzazione" del COM stesso:
0
.
Similmente, alla fine dell'esecuzione di un programma che ha
chiamato la CoInitialize
, e in particolare dopo
qualsiasi altra chiamata al COM, bisogna infine chiamare l'API:
Vi sono vari modi di ottenere un puntatore ad una interfaccia COM di un certo oggetto, ma uno dei più semplici, e più utilizzati, è l'API:
Il "puntatore all'oggetto aggregante", pUnkOuter
, sarà
sempre 0
nei casi semplici che per ora ci interessano.
Il codice dwClsContext
sarà normalmente la costante
(macro) CLSCTX_SERVER
, a indicare che ci va bene
qualsiasi tipo di server.
Il riid
è l'identificatore dell'interfaccia
COM che desideriamo (in C++, sarebbe passato come
reference; in C, dovremo invece passarlo come puntatore),
quel codice di 128 bit cui già ci siamo riferiti come GUID
o UUID
(REFIID
, CLSID
, ecc ecc, sono vari sinonimi, del
tutto equivalenti, dello stesso tipo, a seconda del ruolo
in cui viene usato); similmente, rclsid
sarà
l'identificatore
della classe COM ("coclass") dell'oggetto che richiediamo,
un altro GUID
(codice univoco, di 128 bit).
CoCreateInstance
, come la gran parte delle chiamate
relative a COM, ritorna un HRESULT
per indicare successo
o errore; il vero e proprio "risultato", in caso di successo,
è depositato nel puntatore che viene passato come ultimo
parametro. Si noti che il tipo di questo parametro formale
è void**
, mentre quello che passiamo sarà sempre
l'indirizzo di un puntatore-a-interfaccia,
non quello di un void*
--
quindi, inevitabilmente, sarà necessario un cast su questo
ultimo argomento (anche in C, dove void*
può essere
intercambiabile con qualsiasi altro puntatore, ciò non si
applica più se, come qui, si aggiunge un ulteriore livello
di indirettezza).
L'identificatore di interfaccia deve esserci noto, visto che
su quella interfaccia dovremo poi chiamare dei metodi; ad
esempio, per un'interfaccia IScriptControl
, dovremo usare
come identificatore la costante IID_IScriptControl
che già
abbiamo visto.
Viceversa, l'identificatore di classe può non interessarci;
se, sulla macchina su cui gira il nostro programma, fosse
istallata una qualche implementazione piu` aggiornata, e
quindi presumibilmente "migliore", di questa
interfaccia, la cosa non ci darebbe alcun fastidio; in altri
termini, gli identificatori di interfaccia sono cruciali, quelli
di classe (implementazione) sono secondari.
L'identificatore di classe da usare viene così, normalmente,
recuperato dalla registry, partendo dal "nome programmatico"
(ProgID
) della classe, con l'API:
LPCOLESTR
è una stringa di wchar_t
("caratteri larghi", tipicamente
di 16 bit, previsti dagli standard C e C++), che adotta
la codifica Unicode. In C, o C++, costanti di questo tipo si
possono indicare semplicemente prefiggendo una L
a quella
che per il resto appare come una normale costante stringa.
Ecco dunque come si potrebbe presentare il codice iniziale,
per fare partire il COM e ottenere un puntatore alla interfaccia
che ci interessa, usando per semplicità la macro _S
che
abbiamo precedentemente definito...:
Stiamo pensando a questo codice come incapsulato nella funzione
IndiEs_Init
, che già conosciamo; infatti, il ruolo del
COM sarà qui,
appunto, quello di mettere a nostra disposizione un interprete per
le espressioni delle quali intendiamo disegnare il grafico.
Si noti il primo argomento che
passiamo alla CLSIDFromProgID
: la sua sintassi,
L"stringa"
, è appunto quella da
usare per una stringa Unicode; il
suo contenuto è il "Prog ID" del Microsoft Script Control (ma non
ne precisiamo il numero di versione; qualsiasi coclass che si sia
istallata nella registry con questo nome dovrebbe andarci bene!).
Se questo codice funziona correttamente, a questo punto avremo,
nella variabile statica pSC
, un puntatore all'interfaccia "script
control". Tuttavia, essa non è ancora efficace, in quanto nessuno
specifico linguaggio di scripting è ancora
stato connesso allo "script control";
per poterla effettivamente usare, dobbiamo anzitutto, chiamando
il metodo put_Language
su pSC
,
specificare quale linguaggio di scripting ci interessa usare.
A questo scopo possiamo usare il nostro argomento sOpzioni
,
magari con un default se esso è nullo o vuoto:
sOpzioni
è un char*
, mentre l'argomento della put_Language
deve essere una BSTR.
Le BSTR
sono "stringhe" particolari; vanno gestite, da C,
attraverso API specifiche, come:
BSTR
utilizza.
Il problema, però, è chiaramente, anzitutto, quello di
convertire in Unicode delle
stringhe che, per comodità e abitudine,
il resto del nostro programma ci passa invece in termini di
una "normale" codifica, come stringhe di char
,
cioè dei caratteri di 8, non 16, bit ciascuno.
Lo standard C ISO (stranamente, ed erroneamente, da
molti chiamato invece "ANSI") fornisce per fortuna
una funzione atta proprio a questa trasformazione (anche
le API di Windows forniscono funzionalità similari, ma la
chiamata Standard C è un poco più semplice, quindi,
per ora, useremo quella), dichiarata
nell'header file standard stdlib.h
:
L'input è l'argomento mbstr
, una stringa di caratteri
di 8 bit con una lunghezza massima di count
caratteri
(può essere più corta, se terminata dal solito "zero di
terminazione"); la nomenclatura "mb" ("multibyte") si
riferisce al fatto che, in certe codifiche per le lingue
orientali, 2 o più byte (char
) possono rappresentare
un singolo glifo (ideogramma), ma non faremo uso di
questa ulteriore possibilità.
Se wcstr
è 0, mbstowcs
ritorna il numero di wchar_t
di cui ha bisogno per appoggiare il risultato della
conversione che effettua; altrimenti, wcstr
deve puntare
ad un buffer di sufficiente lunghezza per ospitare questo
risultato (e mbstowcs
ritorna il numero di wchar_t
che
ha effettivamente scritto, con un massimo, naturalmente,
pari a count
).
Convertire sOpzioni
in una BSTR
, e passarla al metodo
put_Language
, si può dunque fare "in più tempi":
Si noti che il controllo del successo di put_Language
(con
la solita macro _S
) è posto
dopo la deallocazione
delle stringhe nelle due forme -- infatti, è cruciale liberare le
stringhe stesse indipendentemente dal successo o dal
fallimento di questa chiamata.
Il codice qui visto è dunque, sicuramente, tedioso e ripetitivo (purtroppo, il C non dispone dei comodi modi di "incapsulare" queste sequenze, che il C++ ci mette invece a disposizione), ma, tutto sommato, non difficile.
La IndiEs_Init
, per come l'avevamo progettata,
deve tornare TRUE
o FALSE
, non un
HRESULT
; inoltre, è importante che pSC
sia liberato
(e il puntatore azzerato), se era stato allocato ma la
sua inizializzazione è poi fallita. Le ultime poche righe
di IndiEs_Init
possono dunque essere del tipo:
La chiamata al metodo Release
è fondamentale, ancora
più importante di quanto non lo sia liberare sistematicamente
tutte le altre risorse (memoria, ecc) via via acquisite dal
nostro programma; scordandosela, infatti, si potrebbero
tenere erroneamente impegnate risorse di sistema (o di
rete!) anche al di là della terminazione dell'esecuzione
del nostro stesso programma.
Questo spreco di risorse non si verifica con i "serventi in-process", come quello che attualmente Microsoft distribuisce per lo Script Control, ma è una ottima abitudine da prendere, quella di ragionare "come se" qualsiasi servente COM che si acquisisce "potesse", un domani, essere in realtà un server remoto -- infatti, questa possibilità esiste eccome, e il nostro programma potrebbe trovarsi ad utilizzare server remoti invece che locali o in-process, a seconda solo di come le cose sono istallate su una certa macchina/rete, senza neppure venire ricompilato o relinkato...!
Se tutto è andato bene, abbiamo dunque
a nostra disposizione un servente COM,
che ci espone l'interfaccia di "script
control" attraverso il puntatore pSC
(se qualcosa è andato
invece storto, pSC
sarà 0
). È anche facile, a questo punto,
scrivere la funzione di terminazione, la IndiEs_Finis
, sulla
base di quanto abbiamo già visto -- raccomandiamo quindi
calorosamente al lettore, come esercizio, di cimentarsi appunto
in questa scrittura, promettendogli intanto, come al solito, "il
seguito alla prossima puntata"...!
Capitolo 36: il COM, visto da C (2)
Capitolo 38: Indies: scelte di progetto
Elenco dei capitoli