Eccoci alla secondo parte della serie di post dedicati al fantastico e antico mondo degli oggetti COM.
Nel primo post (Componenti COM: Introduzione - parte 1 di 6) ho introdotto l'argomento e cercato di evidenziare i punti che hanno reso necessaria l'introduzione di questa tecnologia.
Da ora in poi inizieremo ad addentrarci nella parti più tecniche, e non disdegnerò di pubblicare parti di codice in VB6 per meglio chiarire i vari punti.
Sinora abbiamo parlato genericamente di oggetto o componente COM: è arrivato il momento di dettagliare meglio i termini utilizzati in quersto ambito.
Come accennato un oggetto COM è costituito da una o più classi che forniscono funzionalità che possono essere utilizzate da altri codici "consumatori". Queste classi prendono il nome di componenti COM (o COM Component).
Il linguaggio di programmazione con cui sono state scritte queste classi possono essere differenti rispetto a quello che si vuole utilizzare per implementare il codice consumatore.
Nel caso di cui figura sopra si stanno creando 3 classi in Visual Basic 6 le cui funzionalità saranno esposte tramite COM: ma una volta compilato il progetto queste classi potranno essere usate non solo da altri codici scritti sempre in VB6, ma anche da altri linguaggi di programmazione.
L'elemento unificante deve essere che sia il risultato della compilazione delle 3 classi in figura, che il codice consumatore, deve essere in grado di comprendere e seguire le specifiche COM.
Il risultato ottenuto, e che "impacchetta" le 3 classi, prende il nome di COM Server.
Le interfacce
Un'altra parte costitutiva importante della tecnologia relativa agli oggetti COM è l'interfaccia esposta dalle classi.
Come intuibile con il termine interfaccia si indentifica la lista dei metodi e proprietà pubbliche esposte dalle varie classi che sono poste all'interno di un oggetto COM.
Le interfacce descrivono come manipolare le classi esposte da un COM component.
Quindi, detto in altro modo, un componente COM è qualcosa che fornisce delle funzionalità implementando una interfaccia (o anche più di una, vedremo dopo): ogni componente quindi è impacchettato, magari insieme ad altri componenti, in un oggetto COM, che è un file dll o un exe.
Oss.: Il termine impacchettato è improprio, ma rende bene l'idea. Il termine corretto è ovviamente compilato.
Il formato binario così ottenuto, sia dll che exe, deve seguire uno standard ben preciso, definito appunto dalle specifiche COM.
Per rendere più pratico il concetto è possibile dire che quando in Visual Basic 6 vengono create 3 classi, ognuna delle quali espone espone metodi pubblici, si sta creando un COM Server che contiene al suo interno 3 componenti COM che implementano 3 diverse interfacce.
Scusandomi la pedanteria, occorre che osservi a questo punto che una classe in VB6 può anche implementare una interfaccia definita separatamente (vedere istruzione Interfaces): per tale motivo possiamo dire che non è automatico che ogni classe disponga di una interfaccia ad essa associata, ma può esistere il caso che due o più classi implementino la stessa interfaccia.
Nel seguito per semplicità non tratteremo questo punto, ma non dovrebbe essere difficile immaginare le variazioni introdotte nella trattazione a fronte di una classe che implementa un'interfaccia tramite Interfaces.
Dll vs Exe
Un componente COM può essere ospitato sia in un COM server dll che exe.
Un componente COM implementato come dll entrerà a far parte del processo del codice chiamante e utilizzatore.
Diversamente un componente COM compilato con estensione EXE sarà sempre eseguito in un processo separato a sè stante: il processo chiamante sarà in grado di contattare il componente COM con una serie di messaggi inter-processo (marshalling), ma i due processi (quella che ospita il codice chiamate e quello che accoglie l'oggetto COM) saranno ben distinti.
Un oggetto COM di tipo EXE addirittura può essere ospitato su un processo separato in esecuzione su un'altra macchina rispetto a quella su cui è in esecuzione il processo consumatore dei servizi: in tal caso si parla di oggetto DCOM/COM+.
Ogni modalità ha i suoi pregi e difetti, nonchè particolarità: ma anche su questo argomento ritengo utile non addentrarmi oltre.
IUnknown. Ehm….. cioè ?
Ogni COM component espone non solo l'interfaccia creata dal programmatore (insieme dei metodi pubblici), ma anche un ulteriore interfaccia chiamata Iunknown.
Lo scopo ultimo di questa ulteriore interfaccia è quello di permettere un utilizzo più efficace della RAM che ospita il tutto, grazie all'introduzione un meccanismo che permette di comprendere quando un componente COM è ancora utilizzato da qualche parte di codice cosnumatore.
Partiamo dal solito esempio: abbiamo un pezzo di codice A che usa la libreria B (diciamo che il COM è un EXE). Quando A richiama la libreria B, la stessa viene caricata in RAM dal disco e quindi posta in esecuzione.
Ora anche un altro codice, diciamo C, usa la stessa libreria B: siccome questa è già in RAM il sistema operativo valuterà che non è necessario ricaricarla in RAM.
Il problema che si pone è di capire quando il processo che afferisce la libreria può essere "scaricata" dalla RAM poichè non vi è più nessun codice che la sta utilizzando.
Per risolvere questo dilemma il componente COM implementa al suo interno un contatore: ad ogni riferimento tale contatore viene incrementato, e ogni volta che lo stesso viene distrutto allora viene decrementato.
Quando il contatore assume il valore nullo allora significa che nessun codice sta usando il componente in parola, e quindi può essere scaricato dalla RAM.
Lo stesso problema si ripropone quando si parla di componente COM contenuto in una tramite dll.
Per incrementare/decrementare questo contatore ad ogni componente COM viene associato oltre che all'interfaccia creata dal programmatore anche un'altra interfaccia, chiamata IUnknown, che permette la gestione cui sopra.
Usando molti linguaggi di programmazione, per esempio VB6, di questa attività il programmatore non ne troverà traccia alcuna: in modo nascosto in fase di compilazione verranno generate le varie chiamate atte a interagire correttamente con i metodi esposti da Iunknown.
Quindi in definitiva, usando una terminologia VB6-style.
Set oggettoCOMA = New oggettoCOMA
In tal caso viene richiamato in modo silente e senza che il programmatore debba fare qualcosa un metodo dell'interfaccia IUnknown che incrementa il contatore degli utilizzi del COM Component.
Set oggettoCOMA = nothing
Anche in questo caso viene richiamato il metodo apposito dell'interfaccia Iunknown per decrementare il contatore.
A quanto ne so quanto esposto sopra non ha mai funzionato a meraviglia: presenza di riferimenti circolari(oggetto COM A che instanzia un oggetto COM B, che a sua volta instanzia un oggetto COM A), dimenticanze di porre oggetti inutilizzati a nothing, etc etc potevano provocare errori nella gestione dell'interfaccia Iunknown e quindi nella gestione di tali contatori di utilizzo di un componente COM.
Oggigiorno .Net utilizza come garbage collector un algoritmo statistico molto più fine ed efficace, eppoi a dirla tutta la RAM a disposizione è molto maggiore rispetto a quella mediamente presente sulle macchine dell'epoca dei COM, per cui i problemi derivanti da un utilizzo eccessivo di di memoria fisica sono sicuramente molto meno importante rispetto a un tempo.
Quindi una delle particolarità che un compomente COM deve avere è implementare l'interfaccia IUnknown.
A scopo di essere noioso desidero ripetere che in VB6, così come in altri linguaggi, di quest'ultima interfaccia non se ne trova alcuna traccia: il compilatore fa tutto per noi in modo silente, creando l'interfaccia nonchè la sua implementazione.
In pratica il programmatore scrive le classi, dotate delle loro interfacce, e in modo trasparente in fase di compilazione verranno create per ogni classe una interfaccia che raggruppa tutti i metodi pubblici, nonchè un'ulteriore interfaccia IUnknown con la correttamete implementazione che permette di tenere conto dei riferimenti.
Anche per quanto riguarda il codice consumatore vale la stessa cosa: in fase di instanziazione di una classe COM vengono richiamati I corretti metodi dell'interfaccia IUnknown, e così anche quando l'oggetto viene posto a nothing.
Logical name vs Physical name
Ad ogni interfaccia COM è assegnato un nome logico (logical name) e un nome fisico (physical name).
Il logical name è, in buona sostanza, il nome della classe che si assegna all'interno dell'IDE VB6.
Il physical name, invece, è un GUID a 128 bit: spesso viene anche identificato con il termine IID interface identifier.
Anche in questo caso in VB6 (come in altri linguaggi di programazione comlatibili con COM) tutto questo avviene in modo silente.
Come noto il GUID è un numero univoco a 128 bit, che ha la caratteristica di essere univoco nel mondo.
Ad ogni classe, inoltre, in fase di compilazione viene assegnato un ulteriore GUID, chiamato CLSID (class ID).
La motivazione che sta dietro al fatto di assegnare dei GUID è evidente: occorre un elemento univoco che permetta di identificare univocamente componenti COM e relative interfacce (I nomi ogici sono assegnati dal programmatore, e non è detto ovviamente che possano essere univoci).
Type Library
Diciamo che compiliamo la dll COM su una macchina, e quindi la stessa venga spostata su una ulteriore macchina dove si eseguirà lo sviluppo del codice consumatore di questa.
Occorre un meccanismo che permetta di decrivere cosa si trova all'interno di un COM Server: le classi implementate, le relative interfaccce, etc etc.
La descrizione di tutto questo prende il nome di type library.
In altri termini la type library altro non è che una descrizione, in formato binario, di tutto quanto si trova all'interno dell'oggetto COM, e quindi rappresenta un metadata di quanto contenuto nella dll stessa.
Oss.: Quando nell'IDE VB6 si esegue un riferimento a un componente COM, come noto è possibile accedere ai vari metodi di questo con il punto è la TAB, che elencherà tutti i metodi disponibili: questo viene ottenuto grazie alla type lib !
Questa rappresentazione può essere contenuta sia un un file separato, usualmente con estensione tlb, o anche essere inglobato direttamente dentro il file del COM Server. Per componenti COM di tipo dll creati (e consumati) in VB6 usualmente la type library è posta all'interno del componente COM stesso.
Per altri linguaggi (per esempio C++) o situzioni è necessario avere la type library in un file separato.
Grazie dell'attenzione: alla prossima puntata !