Più di una volta avrete sentito della famigerata Global Assembly Cache, per gli amici
GAC. Di cosa si tratta? La GAC è una
sorta di raccoglitore di tutti gli assembly che noi vogliamo condividere
tra più di un'applicazione. Lo scopo della GAC è proprio questo: se noi
sviluppiamo un assembly DLL contenente classi che noi vogliamo utilizzare in diverse
applicazioni .NET, possiamo semplicemente installare questo assembly nella GAC e
il gioco è fatto. Per poter installare un assembly nella GAC, occorre innanzittutto firmarlo con uno strong
name
, quindi utilizzare il tool
sn.exe e decorare l'assembly con l'attributo AssemblyKeyFile,
come abbiamo fatto precedentemente. Se un assembly
utilizza a sua volta un altro assembly, anche quest'ultimo deve avere uno strong
name, altrimenti...nisba!
Perchè è obbligatorio firmare con lo strong name? Avevo già detto che i
motivi per cui viene usato uno strong name sono essenzialmente due:
versioning e authentication. In questo caso, lo strong name
serve proprio per il versioning. Ricordiamoci infatti che quando un assembly viene firmato con uno strong name, il suo
identifier diventa univoco.
Se è univoco, può entrare nella GAC senza problemi.
Diamo un'occhiata alla GAC del nostro PC
Se avete
installato il .NET Framework sul vostro PC, provate a dare un'occhiata alla
directory C:\Winnt\Assembly vedrete l'elenco degli assembly contenuti nella
vostra GAC. Nella maggior parte dei casi, sarà l'elenco predefinito creato dal
setup del framework.
La cosa in realtà non è così semplice. In effetti,
Windows utilizza il file Shfusion.dll che è una shell
extension. A cosa serve? Semplice: in realtà la struttura della GAC è un po' più
complessa di quella che Windows vi fa vedere attraverso il classico Windows
Explorer. Provate ad aprire una command line, navigate fino a C:\Winnt\Assembly
a suon di cd e vedrete di cosa sto parlando.
In altre parole, il framework è in
grado di mantenere nella GAC più versioni di un certo assembly
. Le versioni possono
variare per Culture, per Version (appunto), eccetera. Dal momento che abbiamo a
disposizione più di una versione di ogni componente, possiamo ad esempio dire
che la nostra applicazione Age deve usare un certo assembly ad una certa
release.
Lo scopo? Risolvere un bel
pasticcio!
Qual'è lo scopo di tutto questo? Avete mai sentito
parlare di 'DLL Hell' ?
Lasciatemi descrivere un tipico
scenario pre era-.NET.
La mia applicazione PrintOrders
utilizza il componente PrintPreview.dll alla versione
X.Y.Z per gestire l'anteprima di stampa. PrintPreview.dll è un file
DLL condiviso, magari messo in C:\Windows\System32. Tutto fila liscio, fino
a quando un altro programma aggiorna questa DLL, installando una nuova versione
con un object model un po' diverso. Ecco tutto ad un
tratto PrintOrders che comincia a dare errori da tutte le parti, perchè la
nuova release di PrintPreview.dll causa qualche conflitto non si sa bene dove e
perchè.
Questo perchè prima dell'avvento di
.NET Windows aveva una sola copia di ciascun componente (per renderla semplice):
se una DLL veniva aggiornata, tutte le applicazioni che usavano quella DLL
potevano potenzialmente subire qualche tipo di malfunzionamento.
Oggi invece abbiamo il .NET Framework. Oggi noi possiamo creare un assembly
PrintPreview.dll ed installarlo nella GAC. Possiamo avere a
disposizione tutte le versioni di questo assembly eventualmente installate.
Quindi, possiamo forzare la nostra applicazione dicendo che deve
obbligatoriamente usare PrintPreview.dll ad una certa versione.
Questo è lo scopo della Global Assembly Cache. Attraverso la command-line è possibile vedere
la vera struttura della GAC, composta di una serie di directory per ogni
assembly. Se siete in C:\Winnt\Assembly, provate a dare ad esempio il seguente
comando:
cd System.Drawing
Questa directory contiene tutta una serie di directory che permettono al framework
di gestire la situazione senza troppi problemi. Come possiamo aggiungere un
nostro assembly alla GAC?
Installare un assembly nella GAC
Come abbiamo detto
prima, dobbiamo averlo firmato con uno strong name. Poi possiamo aggiungerlo alla
GAC in tre modi diversi, più o meno complessi:
- Facendo un bel drag'n'drop del nostro assembly dalla
directory dentro C:\Winnt\Assembly
- Facendolo installare da un setup di qualche tipo
- Usando l'utility gacutil.exe
Siccome vogliamo diventare MCAD, sapere almeno la sintassi del comando
gacutil.exe può essere utile, giusto? Beh, allora, vediamola!!!
Usare gacutil.exe per gestire la Global Assembly
Cache
Andiamo a vedere
velocemente alcune tipiche sintassi del comando gacutil.exe.
gacutil /i C:\Progetto\ReadRegistry.exe
Installa l'assembly
nella GAC. Per disinstallare usare l'opzione /u
.
gacutil /il ElencoAssembly.txt
Installa gli assembly specificati
nel file di testo. Il file di testo deve contenere informazioni formattate
così:
myAssembly1,Version=1.1.0.0,Culture=en,PublicKeyToken=874e23ab874e23ab
myAssembly2,Version=1.1.0.0,Culture=en,PublicKeyToken=874e23ab874e23ab
myAssembly3,Version=1.1.0.0,Culture=en,PublicKeyToken=874e23ab874e23ab
Quello indicato, notare, è il nome univoco di un assembly, ottenuto grazie
allo strong name. Per disinstallare, usare l'opzione /ul.
Quando è opportuno usare gacutil.exe ?
Come succedeva con
i buoni vecchi componenti COM di una volta, il framework tiene traccia di un
reference counter, ovvero, banalmente, di quante volte un certo
assembly è referenziato da altri assembly o applicazioni. Questo per evitare di
rimuovere un assembly dalla GAC se questo in realtà serve a qualcun altro. Per
default, gacutil non incrementa questo reference counter: diverse pagine che ho letto su MSDN dicono di non usare gacutil in
produzione, ma solo durante lo sviluppo dell'applicazione.
Non è proprio vero. Utilizzando l'opzione /r di
gacutil, possiamo comunque fare in modo di incrementare il reference di
un'unità. Però la regola rimane valida: quando si deve distribuire un assembly,
è più logico e plausibile utilizzare un installer, che è anche più comodo per
l'utente finale.