Reference couting für Assemblies im GAC
Eine kleine Erkenntnis für zwischendurch: Gerade habe ich versucht, eine Assembly aus dem GAC zu löschen. Zuerst wollte ich es über den Windows Explorer machen, der verweigerte aber den Dienst mit dem Hinweis xyz.dll could not be uninstalled because it is required by other applications. Hm... eine Anwendung, die die Assembly aber (gerade) benutzte, war mir nicht bekannt. Also habe ich es nochmal zu Fuß mit gacutil.exe probiert. Mit dem selben schlechten Erfolg - aber dafür einer etwas ausführlicheren Fehlermeldung: Unable to uninstall: assembly is required by one or more applications Pending references: SCHEME:
Die Erklärung ist also: Wenn eine Assembly mit dem Windows Installer im GAC installiert wird, dann kann auch nur der Windows Installer sie wieder deinstallieren.
Das war im ersten Moment eine Überraschung für mich; ich hatte es nicht gewusst. Nach einem kleinen Moment des Nachdenkens ist es aber eine sehr positive Überraschung. Ich würde sagen: So und nicht anders sollte es sein. Hier ein Statement von Microsoft aus einem Chat dazu:
You should avoid using GACUtil to install managed assemblies for several reasons. First, you lose the ability to trace installation references in the GAC, unless you use the /r option to add tracking references. Second, GACUtil is part of the frameworks SDK, and is not guaranteed to be on all target machines - it's also not marked as redistributable, so you'd need to check the licensing to make sure you can actually ship it in your package. Third, MSI installation gets you repair and servicing for free, where GACUtil doesn't. Finally, GACUtil is more about populating an empty GAC from the start (as in the case of the frameworks installer) than about installing assemblies on a running system.
Es weist auch auf den allgemeinen Mechanismus hin, der da am Werk ist: Installationen von Assemblies in den GAC können mit Referenzen versehen werden. So wie bei COM-Objekten gibt es auch im GAC ein "reference counting". Jede Installation einer Assembly kann die Referenzen der Assembly hochzählen, jede Deinstallation wieder herunter zählen. (Das bedeutet auch, Sie können die selbe Assembly mehrfach im GAC installieren, aber sie liegt darin in Wirklichkeit nur einmal - allerdings mit mehreren Referenzen.)
Anders als bei COM ist der Referenzzähler einer Assembly keine simple Zahl (Anzahl der Referenzen), sondern eine Liste von Referenzen. Es gibt mehrere Arten von Referenzen (s. .NET Framework Doku zum Stichwort "gacutil.exe"). Eine Referenzenart ist z.B. FILEPATH, d.h. Sie können den Namen einer Assembly angeben, die die im GAC zu registrierende benötigt. Ein GacUtil.exe Aufruf sieht dann z.B. so aus:
gactutil.exe /i mylib.dll /r FILEPATH c:\myapp\myapp.exe MeineAnwendung
Auf FILEPATH folgt der vollständige Pfad zur Assembly, die von mylib.dll abhängig ist, "MeineAnwendung" ist nur eine Kurzbeschreibung dieser Referenz.
Die einfache Deinstallation mit gacutil.exe /u mylib ist jetzt nicht mehr möglich. Solange noch Referenzen an einer Assembly im GAC hängen, müssen Sie genau angeben, welche Referenz Sie deinstallieren wollen, z.B.
gactutil.exe /u mylib /r FILEPATH c:\myapp\myapp.exe MeineAnwendung
Erst wenn die Liste der Referenzen leer ist, wird die Assembly tatsächlich aus dem GAC gelöscht. Damit ist sichergestellt, dass nur derjenige, der eine Assembly im GAC installiert, sie auch wieder deinstallieren kann. Programminstallationen, die Assemblies im GAC ablegen, können also genauso robust und gefeit gegen unwillkürliche Löschungen "im System" sein, wie lokal installierte.
Ein wenig relativiert sich Microsofts Kommentar damit schließlich auch ein wenig: Sie können gacutil.exe schon anwenden, aber sie sollten zumindest darauf achten, dass Sie immer Referenzen angeben.
(Als Trost für alle, die die /r Option von gacutil.exe auch nicht gekannt haben: Sie ist schlecht dokumentiert. Im .NET Fx SDK ist sie zwar zu finden, aber online habe ich sie nicht auf der korrespondierenden Seite http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cptools/html/cpgrfglobalassemblycacheutilitygacutilexe.asp gefunden. Nur eine externe Quelle hat auch ein Beispiel geliefert: https://mailserver.di.unipi.it/pipermail/dotnet/msg00074.html.)