GAC (Global Assembly Cache)
L’acronimo GAC sta per Global Assembly Cache.
Il nome può trarre in inganno, perché più che di una cache si tratta di un repository in cui sono depositati tutti gli assembly condivisi installati su una macchina.
Un assembly installato nella GAC è disponibile a tutte le applicazioni .NET.
Nella GAC possono essere installate più versioni dello stesso assembly, e per ogni versione possono esistere diverse “specializzazioni” dello stesso assembly in base alla cultura richiesta. Questo consente di avere versioni localizzate dello stesso assembly che differiscono anche per operazioni che vengono effettuate (si pensi alle lingue che non vengono scritte da sinistra verso destra) all’interno dell’applicazione oltre che per risorse come stringhe e bitmap.
In .NET non è necessario installare in modo “globale” qualsiasi componente (cosa che invece avveniva praticamente sempre con COM), ci si può invece aspettare che la maggior parte dei componenti usati da un’applicazione siano assembly privati. I motivi per cui può essere conveniente installare un assembly condiviso sono principalmente questi:
- Condivisione dello stesso componente da parte di più applicazioni
- Minor tempo di caricamento dell’assembly (dovuto anche alla mancanza di validazione, rispetto ad un assembly privato ma dotato di strong name)
- Possibilità di installare diverse versioni dello stesso componente (non si possono avere due versioni diverse di un assembly privato)
La GAC risiede in una particolare directory del sistema operativo (normalmente \windows\assembly\GAC o \winnt\assembly\GAC).
L’installazione di un componente nella GAC può avvenire in tre modi:
- con il tool GACUTIL.EXE;
- attraverso la shell extension, posizionandosi con explorer su \windows\assembly o \winnt\assembly, a seconda della versione di Windows;
- durante l’installazione effettuata con un package Windows Installer (.msi).
In realtà la GAC non è altro che un insieme di file organizzati in determinate directory; in teoria è possibile installare e/o disinstallare un componente agendo direttamente sul file system, ma ciò è vivamente sconsigliabile.
Come già detto, nella GAC possono risiedere diverse versioni dello stesso componente, che si differenziano per numero di versione e cultura.
Quando un’applicazione utilizza un assembly nella GAC, lo fa sempre referenziando uno strong name (ricordiamo che un assembly può essere installato nella GAC solo se possiede uno strong name). Tipicamente un riferimento ad uno strong name specifica il nome dell’assembly, il public key token e la versione; la cultura può essere specificata esplicitamente, anche se solitamente ciò non avviene (vedi Risoluzione degli assembly – Assembly e cultura).
Lo strong name diventa quindi una specie di chiave primaria per l’accesso alla GAC, identificando univocamente l’assembly da caricare. È possibile specificare dei meccanismi di ridirezione dell’assembly da caricare in base alla versione richiesta, sia da parte dell’autore del componente (con un publisher policy file), sia nel file di configurazione dell’applicazione (file .CONFIG).
Al di là di questi aspetti, che verranno trattati più avanti (vedi Risoluzione degli assembly e Versioning), la GAC consente di mantenere installate nello stesso momento due versioni diverse dello stesso componente, utilizzando senza nessun tipo di modifica sia le applicazioni scritte per la prima versione del componente condiviso, sia le applicazioni scritte per la seconda versione. Ogni applicazione continua ad usare la versione del componente per cui è stata scritta, senza ricorrere ad assembly privati.
A questo punto vediamo come si installa un componente condiviso. Usiamo il solito assembly fooasm.dll con strong name, referenziato da footest.exe.
using System; using System.Reflection; [assembly:AssemblyKeyFile("publicprivate.snk")] [assembly:AssemblyDelaySign( false )] [assembly:AssemblyVersion("1.0.0.0")] public class FooAsm { public static string GetName() { return "Answer:FooAsm"; } }
using System; public class FooTest { public static void Main() { Console.WriteLine( "Asm = {0}", FooAsm.GetName() ); } }
Generiamo una coppia di chiavi e compiliamo il tutto:
> SN –k publicprivate.snk
> csc /target:library fooasm.cs
> csc footest.cs /r:fooasm.dll
A questo punto possiamo installare fooasm.dll nella GAC, usando il tool GACUTIL:
> GACUTIL /i fooasm.dll
Tanto per vedere se tutto funziona come pensiamo, cancelliamo fooasm.dll dalla directory di lavoro e proviamo ad eseguire footest.exe:
> DEL fooasm.dll
> footest.exe
Il risultato è quello che ci aspettiamo, l’applicazione funziona perché evidentemente sta usando l’assembly nella GAC:
Asm = Answer:FooAsm
A questo punto l’assembly fooasm.dll è stato installato nella GAC: questo significa che tutti i moduli di cui è composto l’assembly (in questo caso solo un file) sono stati fisicamente ricopiati in sotto-directory della GAC.
Ad esempio, l’assembly appena installato ha generato il seguente ramo di directory:
C:\>dir \windows\assembly\gac\fooasm /s Volume in drive C has no label. Volume Serial Number is 0493-53F6 Directory of C:\windows\assembly\gac\fooasm 09/02/2002 16.39. 09/02/2002 16.39 .. 09/02/2002 16.39 1.0.0.0__65dde9fd4cee9b6d 0 File(s) 0 bytes Directory of C:\windows\assembly\gac\fooasm\1.0.0.0__65dde9fd4cee9b6d 09/02/2002 16.39 . 09/02/2002 16.39 .. 09/02/2002 16.39 3.584 fooasm.dll 09/02/2002 16.39 210 __AssemblyInfo__.ini 2 File(s) 3.794 bytes Total Files Listed: 2 File(s) 3.794 bytes 5 Dir(s) 29.961.863.168 bytes free
La prima directory creata ha un nome che corrisponde a quello dell’assembly (senza estensioni): fooasm. Dentro questa directory ne viene creata un’altra, specifica della versione dell’assembly: il nome è composto dal numero della versione (1.0.0.0) seguito da due caratteri ‘_’ (underline) e dal public key token (nell’esempio 65dde9fd4cee9b6d). Questa tecnica garantisce l’univocità della directory, perché due componenti con lo stesso nome avranno versioni diverse se provengono dallo stesso produttore, o due public key token diversi se provengono da due produttori diversi (e a quel punto potrebbero anche avere la stessa versione).
Dentro la directory 1.0.0.0__65dde9fd4cee9b6d troviamo tutti i moduli dell’assembly (in questo caso solo fooasm.dll) e un file creato da GACUTIL, __AssemblyInfo__.ini.
Analizziamo il contenuto di questo file:
[AssemblyInfo] MVID=5af4039a29c1eb47a343cbde3d600c20 URL=file:///E:/dev/DotNet/DevLeap/Assembly/Assembly_gac/fooasm.dll DisplayName=fooasm, Version=1.0.0.0, Culture=neutral, PublicKeyToken=65dde9fd4cee9b6d
L’estensione non inganna, effettivamente questo file è un formato testo, il buon vecchio file di configurazione di Windows 3.1 (alcune API per accedere a questo formato sono GetPrivateProfileString e WritePrivateProfileString).
Tutti i parametri sono contenuti nella sezione [AssemblyInfo]. La seconda riga, URL, definisce la posizione fisica dell’assembly al momento dell’installazione (in pratica, la sua provenienza). La terza riga, DisplayName, fornisce la definizione dello strong name. Quello che può sembrare incomprensibile è la prima riga della sezione, MVID: questa sigla sta per Module Version ID, si tratta di un GUID generato durante la compilazione e salvato nei metadati dell’assembly. Ogni volta che l’assembly viene ricompilato, anche se si mantiene la stessa versione (e quindi lo stesso strong name), si ottiene comunque un MVID diverso.
Volendo creare a mano questo file (abbiamo detto che teoricamente è possibile installare “manualmente” un assembly nella GAC), tutte queste informazioni sono ottenibili dal manifest dell’assembly e dalla sua posizione una volta prima dell’installazione. Il MVID è visibile nel manifest con questa sintassi (questa parte di dump è stata ottenuta analizzando fooasm.dll con ILDASM):
.module fooasm.dll
// MVID: {9A03F45A-C129-47EB-A343-CBDE3D600C20}
Per disinstallare un componente è necessario specificare tutti i riferimenti per lo strong name.
> GACUTIL /u fooasm,Version=1.0.0.0,Culture=neutral,
PublicKeyToken=65dde9fd4cee9b6d
Si può anche specificare il solo nome dell’assembly, ma ovviamente è un’operazione rischiosa, perché qualora siano installate più versioni dello stesso assembly nella GAC, sarebbero tutte disinstallate. Per chi ama il rischio, la scorciatoia è:
> GACUTIL /u fooasm
Dopo aver rimosso fooasm.dll dalla GAC, proviamo ad eseguire nuovamente footest.exe: otterremo l’errore che segue.
Unhandled Exception: System.IO.FileNotFoundException: File or assembly name fooa sm, or one of its dependencies, was not found. File name: "fooasm" at FooTest.Main()
Lo strong name non viene più risolto, né come assembly condiviso né come assembly privato, pertanto l’esecuzione del programma si interrompe con l’eccezione appena mostrata.
Abbiamo visto come si può installare un assembly usando il tool GACUTIL ed eventualmente creando a mano le directory e il file .INI necessario. Un’altra possibilità, oltre a usare Windows Installer che però non trattiamo in questa sede, è quella di usare la shell extension. Posizionandosi con Explorer su C:\WINDOWS\assembly si ottiene una visualizzazione simile a questa:

Per eliminare un assembly è sufficiente selezionarlo ed eseguire l’eliminazione come un qualsiasi altro file; per l’installazione è invece sufficiente usare il drag & drop, trascinando l’assembly da installare all’interno di questa cartella.
L'articolo continua con Risoluzione degli assembly
