L'approccio Windows alla collaborazione fra componenti separati è il Component Object Model, COM; sia su di una sola macchina (entro lo stesso processo, o fra più processi separati), o fra macchine in rete ("DCOM"), il modello di programmazione di COM è equivalente.
Su questa tecnologia fondamentale ne poggiano mille altre, a seconda delle specifiche esigenze: interfaccia allo Shell di Windows, alla telefonia, alla grafica, al suono, e chi più ne ha, più ne metta. In particolare, fra le tecnologie "di secondo livello" basate su COM (e a volte confuse terminologicamente con esso, anche per colpa di passati errori del Marketing Microsoft!) ci sono OLE, Active/X, e Automation.
COM è indipendente dal linguaggio di programmazione
usato, ma la sua orientazione ad oggetti lo rende scomodo
(ma non certo impossibile!) da usare da C, linguaggio che
ad oggetti non è; inoltre, nelle interfacce COM pubblicate
è comune l'uso di tipi particolari (BSTR
,
per le stringhe, in
Unicode; VARIANT
, per "unioni discriminate";
SAFEARRAY
,
per i vettori di vari tipi), che sono "nativi" a certi linguaggi
(Visual Basic) e possono agevolmente essere incapsulati in
altri (C++, Java) in modo assai comodo in classi di oggetti
opportune, mentre in C vanno gestiti chiamando sempre le
varie API apposite. Insomma, diciamocelo: il COM lo si
userebbe in C più o meno solo "per scommessa"...!
Tuttavia, anche capire "ma cosa c'è poi sotto" può essere un motivo valido, alternativo alle scommesse:-). Anche un utente C++ non ha necessariamente (anche se dovrebbe averle!) le idee chiare sul modello di "cosa succeda nella macchina" a fronte dei vari costrutti del linguaggio -- men che meno, in tutta probabilità, un programmatore Java, o Visual Basic, linguaggi più ad alto livello, che meglio "schermano" il programmatore dalla macchina sottostante.
In C, invece, è più probabile che l'utente debba, anche per necessità, mantenere un modello concettuale lucido e rispondente (anche se macro e funzioni qualcosa possono pur sempre nascondere); può dunque valer la pena di sopportare un poco di scomodità, a puro fine didattico, per poi passare, naturalmente, al C++ o altri linguaggi più adatti, quando si vorrà fare uso pratico dei concetti appresi.
Il modello di programmazione proposto da COM si impernia sulle "interfacce": una interfaccia COM, dal punto di vista del C, è un vettore di puntatori a funzioni, ciascuna delle quali condivide varie caratteristiche:
STDCALL
, WINAPI
, o CALLBACK
che dir si voglia),
HRESULT
(v. oltre),
Il "cliente" di funzionalità esportate via COM le invoca solo e sempre attraverso "puntatori a interfacce", che egli ottiene in vari possibili modi (ad esempio, attraverso varie API). Un "oggetto" COM può "esporre" varie interfacce, e in questo caso il cliente può "navigare" fra esse attraverso un metodo (una delle funzioni dell'interfaccia) che tutte le interfacce COM devono supportare. Il cliente è inoltre responsabile di "rilasciare" l'interfaccia (e anche ciò si fa chiamando l'apposito metodo, sempre presente) quando ha finito di usarla.
L'HRESULT
, ritornato da tutti i metodi COM e da parecchie
delle API che riguardano COM, è una parola di 32 bit da
interpretarsi come codice di errore, o di successo; può
essere "interrogato" attraverso
delle macro, FAILED(hr)
che torna TRUE
se l'HRESULT
hr rappresenta un codice
d'errore, e SUCCEEDED(hr)
che
equivale a !FAILED(hr)
.
Gli header file di windows (implicitamente "assorbiti" da
un #include <windows.h>
) comprendono inoltre
varie macro
che possono essere usate per dichiarare una interfaccia
COM in modo tale da poterla "espandere" correttamente,
sia in C (con l'equivalenza "al nudo metallo"), sia in C++
(con la comodità di considerarla una "classe astratta",
da utilizzarsi con i comuni idiomi C++, e senza alcun aggravio
computazionale rispetto all'uso in C). Per "espandere"
queste macro "alla C", pur scrivendo in C++, basta inoltre
porre un:
#include <windows.h>
.
Ecco un esempio di uso di queste macro, che definisce, appunto, una ricca interfaccia COM...:
Il tipo GUID
, usato per dichiarare la costante
IID_IScriptControl
, è
una struttura che comprende 128 bit "arbitrari"; il nome del tipo è
una sigla, che sta per "Globally Unique ID" (si usa spesso anche il
sinonimo UUID
, "Universally Unique ID") -- il
suo ruolo è solo ed
esclusivamente quello di fare da "vero nome, inconfondibile" per
una qualche entità COM (in questo caso, un'interfaccia),
al di là
del "nome umanamente leggibile" che gli viene solitamente
affibbiato -- il GUID
"garantisce" (almeno statisticamente) la
univocità dell'identificazione (esso viene generato da apposite
API, che raramente un programmatore ha bisogno di chiamare, in
quanto sono "rivestite" da comodi programmi con nomi come guidgen
o uuidgen -- la generazione di GUID
, infatti, normalmente serve,
non mentre un programma sta girando, ma quando lo si sta
scrivendo, e solo, inoltre, se esso definisce e implementa, invece
di limitarsi ad usare, delle entità COM).
Il nome dell'interfaccia che stiamo definendo è assegnato con
il #define INTERFACE
, e poi, di nuovo,
nella macro DECLARE_INTERFACE
(in quest'ultima è associato alla sua "interfaccia di base" -- di
solito IDispatch
, la interfaccia fondamentale di "Automation", che,
come accennavamo, è una importante tecnologia di secondo livello
che poggia su COM -- essa serve, sostanzialmente, per facilitare l'uso
di COM da parte di linguaggi puramente interpretativi, come, ad
esempio, quelli di "scripting"). Questo fa sì che, in C, il nome
dell'interfaccia divenga (grazie a una typedef
) quello di un tipo:
precisamente, una struct
, contenente (sempre dal punto di vista
del C...) il solo campo lpVtbl
, che, a sua volta, punta a una
tabella di puntatori a funzioni, tabella che descriviamo nel "corpo"
della DECLARE_INTERFACE
.
Di questo "corpo", cioè dell'elenco delle funzioni che costituiscono la "tabella di puntatori" (quindi, che costituiscono in effetti l'interfaccia che stiamo descrivendo) ci occuperemo alla prossima puntata.
Capitolo 34: il grafico: ultimi dettagli
Capitolo 36: il COM, visto da C (2)
Elenco dei capitoli