## 15.2 Assemblys ### 15.2.1 Konzept der Assemblys Entwickeln Sie eine Konsolen-, Windows- oder Windows-Dienst-Anwendung, wird eine EXE-Datei erzeugt. Ist das Projekt zum Beispiel vom Typ Klassenbibliothek, wird eine DLL-Datei generiert. Die Kompilate werden, abhängig von der Konfigurationseinstellung, im Ordner /bin/Debug bzw. obj/Debug unterhalb des Projektordners gespeichert. Für etwas Verwirrung kann die Endung DLL (Dynamic Link Library) sorgen. DLLs waren ursprünglich als reine Funktionssammlungen gedacht (denken Sie beispielsweise an die Betriebssystemfunktionen der Win32-API oder die Funktionen in der ODBC-API), später wurde dieselbe Dateierweiterung aber auch für COM-basierte InProc-Server benutzt. Hier haben wir es mit einem weiteren Typus zu tun, der auf der Common Language Runtime basiert. Zumindest eines ist allen DLL-Typen gemeinsam: Sie sind, um sie ausführen zu können, immer auf die Unterstützung einer ausführbaren EXE-Datei angewiesen. Die Softwarekomponenten allgemein verfügbar zu machen, ist ein Ziel, das viele Hersteller anstreben. Microsoft hat in den 90er-Jahren dazu eine Technologie entwickelt, die sich über viele Jahre hinweg auf den Windows-Plattformen etablierte und im Laufe der Zeit immer mächtiger und komplexer wurde: COM und DCOM. Diese schnittstellenorientierten Technologien beschreiben, wie Softwarekomponenten miteinander kommunizieren, auch wenn sie in unterschiedlichen Programmiersprachen realisiert worden sind. Der Datenaustausch musste dabei über ein COM-spezifisches, von den Programmiersprachen unabhängiges Typsystem abgewickelt werden. Das war aber nicht das einzige Problem, das COM/DCOM bereitete. Ein ganz wesentlicher Nachteil war die Trennung des Programmcodes von seiner Selbstbeschreibung, die in der Typbibliothek der Registrierungsdatenbank zu finden ist. Zudem war es nicht möglich, mehrere versionsunterschiedliche COM-Komponenten gleichzeitig auf einem Rechner zu installieren. Das führte in der Vergangenheit häufig dazu, dass Programme, die für den Zugriff auf eine ältere Komponentenversion geschrieben waren, ihren Dienst quittierten, wenn mit einem anderen Programm eine neuere Komponentenversion installiert wurde. Um diesen Kreislauf aufzubrechen, wurde ein völlig neues Konzept spezifiziert, das die folgenden Anforderungen definierte: Eine Anwendung muss ihre Dienste selbst beschreiben können, ohne von anderen Systemdiensten wie der Registrierungsdatenbank abhängig zu sein. Dementsprechend müssen Code und Selbstbeschreibung einer Komponente eine kompakte Einheit bilden. Zur Vermeidung von Versionskonflikten müssen mehrere Versionen einer Komponente parallel installiert werden können. Damit wird gewährleistet, dass eine Anwendung, die sich der Dienste einer Komponente bedient, nicht durch die Installation einer neuen, jedoch inkompatiblen Komponente in das laufzeittechnische Nirwana befördert wird. Die verschiedenen Versionen einer Softwarekomponente müssen gleichzeitig ausführbar sein. Da eine neu zu verteilende Komponentenversion durchaus auch nur das Ziel haben kann, einen bekannt gewordenen Fehler zu beseitigen, sollte eine von dieser Komponente abhängige Anwendung in der Lage sein, aus einer Vielzahl gleicher, jedoch versionsverschiedener Komponenten diejenige zu finden, mit der problemlos zusammengearbeitet werden kann. Es muss sichergestellt werden, dass die von einer Anwendung geforderte, richtige Version der Komponente geladen und ausgeführt wird. Bei allen genannten Punkten setzt das Konzept der Assemblys an. Mehrere Versionen derselben Softwarekomponente dürfen auf einem Rechner installiert sein – mehr noch, sie dürfen sogar gleichzeitig ausgeführt werden. Damit wird zwar einerseits das Prinzip der Abwärtskompatibilität aufgegeben, das unter COM eine elementare Forderung war, andererseits ist Abwärtskompatibilität auch nicht mehr notwendig, weil an eine Anwendung ebenfalls Forderungen hinsichtlich des Komponentenzugriffs gestellt werden. Die Selbstbeschreibung einer COM-Komponente erfolgt in der Typbibliothek, die als eigenständige Einheit getrennt vom Binärcode existiert. Die Beschreibung einer Assembly samt ihrer internen Komponenten hingegen erfolgt in einem Block, der als Manifest bezeichnet wird und mit dem Code unzertrennlich verbunden ist. Eine Assembly lässt sich nicht nur als die Baugruppe einer Anwendung verstehen. Sie bildet auch gleichzeitig die Einheit, die verteilt wird, beschreibt Sicherheitsrichtlinien und ist die Basis der Versionierung. ### 15.2.2 Allgemeine Beschreibung privater und globaler Assemblys Die Frage, die bei jeder Anwendungsentwicklung neu gestellt werden muss, ist, ob der Code in einer .NET-DLL (Assembly) nur einer Anwendung zugänglich sein soll oder ob auch andere Programme darauf zugreifen dürfen. Aus der Fragestellung, wie und von wem eine Assemblierung genutzt werden darf, folgt die Definition zweier unterschiedlicher Assembly-Typen: private Assemblys, die nur von einer Anwendung genutzt werden können gemeinsame Assemblys (globale Assemblys), die allen Anwendungen gleichermaßen ihre Dienste offenlegen Eine private Assembly zu entwickeln, ist denkbar einfach: Man muss nichts Besonderes dafür tun; private Assemblys sind der Standard. Genauso verhält es sich auch mit der weiter oben bereitgestellten Klassenbibliothek GeometricObjects. Sie gehört zur Anwendung TestApplication. EXE- und DLL-Dateien müssen bei der Verteilung gemeinsam installiert werden. Dabei muss die Klassenbibliothek im gleichen Verzeichnis liegen wie die darauf zugreifende Anwendung. Nutzen mehrere Anwendungen die gleiche Klassenbibliothek, gilt diese Regel für jede Installation. Im Extremfall kann das dazu führen, dass ein und dieselbe Klassenbibliothek mehrfach auf einem Rechner vorliegt. Das ist zwar grundsätzlich ein Nachteil, weil dadurch Speicherressourcen verschwendet werden, andererseits relativiert sich dieser Nachteil im Zeitalter der TByte-Festplatten. Gravierender ist jedoch die Auswirkung, wenn die Assembly geändert wird, beispielsweise aufgrund eines Bugs. Das hätte zur Folge, dass man jede einzelne Assembly suchen und austauschen müsste. Hier betreten die globalen Assemblys die Bühne, die an einem zentralen Ort gespeichert sind und von jeder Anwendung gleichermaßen genutzt werden können. Dieser zentrale Ort ist nicht, wie vielleicht zu vermuten wäre, die Registry, sondern der Global Assembly Cache, kurz GAC genannt. Für eine Veröffentlichung im GAC ist ein kryptografischer Schlüssel Voraussetzung. Dieser gewährleistet, dass eine Assembly von einer anderen, zufälligerweise gleichnamigen Assembly eines anderen Entwicklers eindeutig unterschieden werden kann. ### 15.2.3 Struktur einer Assembly #### Allgemeine Beschreibung Eine Assembly muss vielen Anforderungen gerecht werden, um die gesteckten Ziele einer einfachen und sicheren Versionierung und Verteilung zu erreichen. Der wesentlichste Punkt ist die Zusammenfassung von Code und Selbstbeschreibung. Überlegen wir, was zu einer Selbstbeschreibung alles gehört: der Name, um die Assembly zu identifizieren Informationen, um anderen Assemblys mitzuteilen, ob die vorliegende Assembly das ursprüngliche Original oder eine neuere Version ist Informationen darüber, von welchen anderen Komponenten sie abhängt. Dazu gehören unter anderem der Name und die Versionsnummer der von ihr referenzierten Assemblys. Informationen über die von der Assembly exportierten Typen die Bezeichner aller Methoden, einschließlich der Parameternamen und -typen, sowie der Typ des Rückgabewertes Diese Punkte lassen sich in zwei logische Kategorien zusammenfassen: Metadaten, die eine Assembly ganzheitlich beschreiben und als Manifest der Assembly bezeichnet werden Daten zur Beschreibung des IL-Codes, die Typmetadaten Metadaten sind Daten, die andere Daten beschreiben. Wenn Ihnen das zu abstrakt ist, denken Sie an die Tabelle einer Datenbank. Die Entitäten (Spalten bzw. Felder) der Tabelle werden ebenfalls über Metadaten beschrieben. Dazu gehört beispielsweise die Typdefinition eines Feldes, Gültigkeitsregeln, Standardwerte usw. Selbst die einfachste Assemblierung setzt sich damit konsequenterweise aus drei Blöcken zusammen: aus den Metadaten, die die Assembly allgemein beschreiben (das Manifest) aus den Typmetadaten, die die öffentlichen Typen beschreiben aus dem IL-Code Assemblys können nicht nur Module für Typen enthalten, oft gehören auch Ressourcen dazu, beispielsweise BMP-, JPEG- und HTML-Dateien, die von der Assembly zur Laufzeit benötigt werden. Diese Dateien sind dann ebenfalls Bestandteil einer Assembly. Es lässt sich auch eine Assembly vorstellen, die weder Code noch Ressourcen enthält – ob eine solche Assembly allerdings noch Sinn macht, lassen wir dahingestellt. #### Manifest und Metadaten Metadaten sind binäre Informationen, die beim Kompilieren einer Datei, sei es in eine DLL- oder EXE-Datei, hinzugefügt werden und die Daten ganzheitlich beschreiben. Jeder Typ, den man innerhalb einer Anwendung definiert oder einbindet, wird von den Metadaten erfasst. Zur Laufzeit werden die Metadaten in den Speicher geladen und von der Common Language Runtime dazu benutzt, die benötigten Informationen zu beziehen, die zur Erstellung und Verwendung eines Objekts erforderlich sind. Der Informationsgehalt der Metadaten ist vielseitiger Natur und lässt sich in zwei Gruppen einteilen: in das Manifest, das die Struktur einer Assembly beschreibt. Zu dem Informationsgehalt eines Manifests gehören: der Typname die Versionsnummer der öffentliche Schlüssel die Liste aller Dateien, aus denen sich die Assembly zusammensetzt die Liste aller weiteren Assemblys, die statisch an die aktuelle Assembly gebunden sind Sicherheitsrichtlinien, die die Berechtigungen an der Assembly steuern in Typmetadaten, die die Typen innerhalb einer Komponente beschreiben. Das schließt den Namen des Typs, seine Sichtbarkeit, seine Basisklassen und die von ihm implementierten Schnittstellen ein. Mit dem Manifest und den Typmetadaten verfügt die Common Language Runtime über genügend Informationen, um Klassen aus einer Datei zu laden, Objekte zu erstellen, Methodenaufrufe aufzulösen und auf Objektdaten zuzugreifen. Es spielt keine Rolle, in welcher Sprache eine Assembly entwickelt worden ist. Das Manifest verwischt die Spuren des zugrunde liegenden Quellcodes. Unter COM war zur binären Bindung zweier Komponenten noch Bindecode notwendig (IDL – Interface Definition Language), um eine gemeinsame Basis für die Kommunikation und den Datenaustausch beider Komponenten zu schaffen. Die Intermediate Language (IL) und die Common Language Runtime (CLR) schaffen mittels des Manifests die Voraussetzung für den problemlosen Austausch, ohne dass man solche Behelfsbrücken bauen muss. #### Der IL-Disassembler Sie können sich die Metadaten einer Komponente ansehen, wenn Sie das mit Visual Studio 2010 gelieferte Tool ildasm.exe, den sogenannten IL-Disassembler, an der Konsole aufrufen. Sie finden diese Datei in einem Unterordner der Visual Studio-Installation. Haben Sie die Standardvorgaben bei der Installation übernommen, wird es sich um ..\Programme\Microsoft SDKs\Windows\v6.0A\Bin handeln. Optional geben Sie beim Aufruf des Tools den Pfad zu einer Anwendung bzw. Assembly an, beispielsweise:
    ildasm C:\MeineProjekte\MyFirstAssembly.exe
Wenn Sie das Tool ohne Dateiangabe starten, können Sie über das Menü Datei • Öffnen die zu inspizierende Assembly wählen. Wir wollen uns nun mithilfe des ILDASM-Tools das Manifest einer Konsolenanwendung ansehen, die neben dem Ausgabecode der Methode Main in derselben Quellcodedatei noch die Definition der Klasse ClassA enthält. In einer zweiten Quellcodedatei des Projekts ist die Klasse ClassB definiert. Um den Informationsgehalt im IL-Tool zu verdeutlichen, enthält der Typ ClassB insgesamt drei Variablendeklarationen mit unterschiedlichen Sichtbarkeiten.
    // Die Klassen Program und ClassA sind in der Quellcodedatei
    // ClassA.cs definiert.
    using System;
    using System.Data;
    namespace MyAssembly {
      class Program {
        static void Main(string[] args) {
          DataColumn col = new DataColumn();
          Console.WriteLine("Hallo Welt.");
          Console.ReadLine();
        }
      }
      public class ClassA {
        public int intVar;
      }
    }
    // Klasse ClassB ist in der Quellcodedatei ClassB.cs definiert.
    public class ClassB {
      public int intVar;
      private long lngVar;
      protected string strText;
    }
Der Name der Assembly sei MyAssembly. Beachten Sie bitte, dass in Main zu Demonstrationszwecken ein Objekt vom Typ DataColumn erstellt wird, also auf die standardmäßig eingebundene Datei System.Data.dll zugegriffen wird. Sehen wir uns jetzt an, was uns das IL-Tool liefert, ohne dabei allzu sehr in die Details zu gehen (siehe Abbildung 15.2). Unterhalb des Wurzelknotens, der den Pfad zu der Assemblierung angibt, ist – mit einem roten Dreieck gekennzeichnet – das Manifest angeführt. Darunter befindet sich der Knoten MyAssembly, der in der Abbildung bereits vollständig geöffnet ist. Das blaue Rechteck, das mit seinen drei nach rechts weisenden Linien an einen Stecker erinnert, symbolisiert Klassendefinitionen. Neben den Typmetadaten listet das Tool alle Variablen und Klassenmethoden auf – in unserem Beispiel nur die statische Methode Main aus Program sowie die mit .ctor bezeichneten Konstruktoren – und gibt den Sichtbarkeitsbereich der Variablen an. Der Rückgabewert der Methoden wird, getrennt durch einen Doppelpunkt, hinter dem Methodennamen angeführt. Hier klicken, um das Bild zu vergrößern Abbildung 15.2 Die Anzeige des ILDASM-Tools Werfen wir jetzt einen Blick auf das Manifest dieser Assemblierung. Ein Doppelklick auf den Manifest-Eintrag des Disassemblers öffnet ein weiteres Fenster, das die Metadaten der Assemblierung wiedergibt (siehe Abbildung 15.3). Hier klicken, um das Bild zu vergrößern Abbildung 15.3 Das Manifest einer Assembly Der Reihe nach werden alle externen Assemblys aufgelistet, von der die aktuelle Anwendung abhängt. Dazu gehört die wichtigste aller Assemblys, die mscorlib (beschrieben durch die Datei mscorlib.dll). Ihr folgt die Assembly, die aufgrund der Erzeugung des Objekts vom Typ DataColumn ebenfalls von der Anwendung benutzt wird: System.Data (bzw. die Datei System.Data.dll). Da in der Assemblierung MyAssembly keine weitere externe Assembly eine Rolle spielt, werden auch keine anderen im Manifest aufgeführt. Der Liste der externen Assemblierungen schließt sich im Block
    .assembly MyAssembly
eine Liste diverser Attribute an, mit denen die Assemblierung beschrieben wird. Die Attribute können in der Datei AssemblyInfo.cs der Entwicklungsumgebung festgelegt werden. Sehen wir uns nun die Angaben zu einer externen Assembly genauer an, die beispielsweise folgendermaßen lautet:
    .assembly extern mscorlib
    {
      .publickeytoken = (B7 7A 5C 56 19 34 E0 89 )
      .ver 1:0:3300:0
    }
Weiter oben wurde bereits ein wesentlicher Unterschied zwischen einer privaten und einer gemeinsam genutzten, externen Assembly beschrieben: Eine gemeinsam genutzte, globale Assembly verfügt über einen öffentlichen Schlüssel. Im Manifest wird dieser Schlüssel hinter dem Attribut .publickeytoken in geschweiften Klammern angegeben. Neben dem Namen einer Assemblierung trägt unter anderem auch der Schlüssel zur eindeutigen Identifizierung der Assembly bei und sichert gleichzeitig die Identität des Komponentenentwicklers. Auf dieses Thema werden wir gleich zurückkommen. Unter .NET dürfen mehrere gleichnamige, allerdings versionsunterschiedliche Assemblierungen nebeneinander existieren, ohne damit Konflikte zu verursachen. Die Versionsinformationen sind in einem standardisierten Format dargestellt, das im Manifest der referenzierenden Assemblierung mit dem Attribut .ver gekennzeichnet ist. ### 15.2.4 Globale Assemblys Eine globale Assembly stellt ihre Dienste allen .NET-Anwendungen des Systems gleichermaßen zur Verfügung. Das beste Beispiel globaler Assemblys sind die Klassen des .NET Frameworks. Die Entscheidung, ob eine Assembly global zur Verfügung stehen soll, muss schon vor der Kompilierung berücksichtigt werden, weil standardmäßig immer eine private Assembly erzeugt wird. Globale Assemblys werden in einem speziellen Verzeichnis installiert: dem Global Assembly Cache (GAC). Der GAC ist ein Speicherort, in dem sogar mehrere unterschiedliche Versionen derselben Assembly installiert werden dürfen. Zu finden ist der GAC im Verzeichnis \<Betriebssystemordner>\assembly In Abbildung 15.4 sehen Sie die Anzeige des GAC im Explorer. Hier klicken, um das Bild zu vergrößern Abbildung 15.4 Der Global Assembly Cache (GAC) Der GAC ist so strukturiert, dass für jede eingetragene Assembly ein auf dem Dateibezeichner basierender Unterordner angelegt wird. Bei diesem Unterordner wird auf die Dateiextension verzichtet. Dieser Ordner enthält seinerseits weitere Unterordner, deren Bezeichner sich aus der Versionsnummer, der Kultur (das bedeutet: der Sprachversion) und dem öffentlichen Schlüssel der Assembly zusammensetzt. Erst im letztgenannten Unterordner findet sich die entsprechende DLL. Der Windows Explorer lässt es nicht zu, tiefer in das Verzeichnis assembly hineinzuschauen. Sie können jedoch mit den alten DOS-Befehlen an der Eingabekonsole ohne Weiteres die Struktur des GAC erkunden. In Abbildung 15.5 können Sie das Verzeichnis für die Datei System.Drawing.dll sehen. Der Ordner System.Drawing beherbergt den Unterordner 2.0.0.0__ b03f5f7f11d50a3a. In diesem ist die gesuchte Datei zu finden, nämlich genau die mit der Versionsnummer 2.0.0.0, einer neutralen, weil nicht explizit angegebenen Kultur und dem öffentlichen Schlüssel b03f5f7f11d50a3a. Würden wir eine weitere Datei System.Drawing.dll im GAC installieren, würde für diese ein weiterer Unterordner angelegt, denn zumindest im öffentlichen Schlüssel wird sich Ihre Datei von der des .NET Frameworks unterscheiden. Hier klicken, um das Bild zu vergrößern Abbildung 15.5 Ein Blick in das Innere des Global Assembly Caches Doch nach welchen Kriterien wird nun eine von einer Anwendung benötigte Assemblierung gesucht? Das Prinzip ist sehr einfach. Die Common Language Runtime wertet beim Starten einer Anwendung das Manifest aus. Wie Sie wissen, enthält das Manifest alle Angaben zu den benötigten externen Assemblierungen (siehe dazu auch Abbildung 15.3). Mit den Informationen zu Dateiname, Version, Kultur und öffentlichem Schlüssel aus dem Manifest der Anwendung sucht die CLR die passende Assembly im GAC. Wird die CLR fündig, wird die entsprechende Komponente geladen. War die Suche erfolglos, wird im Verzeichnis der Anwendung weitergesucht, weil die CLR dann davon ausgehen muss, dass es sich um eine private Assembly handelt. Streng genommen geht die Suche sogar noch weiter, weil unter Umständen auch noch mögliche Vorgaben in den Konfigurationsdateien eine wichtige Rolle spielen. Auf diese Gesichtspunkte und auf die Konfigurationsdateien komme ich in Abschnitt 15.3, noch zu sprechen. #### Versionierung von Assemblys Zur Laufzeit ermittelt die Common Language Runtime anhand der Versionsnummer, welche Version einer Assembly von einer Anwendung benutzt werden soll. Standardmäßig wird die Version geladen, die im Manifest der Anwendung angegeben ist. Die Beschreibung einer Version erfolgt nach einer festgelegten Spezifikation. Jede Baugruppe hat eine Versionsnummer, die sich aus vier Elementen zusammensetzt, beispielsweise:
    1.0.2.2
Die ersten beiden Zahlen beschreiben die Haupt- und Nebenversion. Werden an einer Komponente Änderungen vorgenommen, die inkompatibel zu der Vorgängerversion dieser Komponente sind (beispielsweise durch die Änderung der Parameterliste einer Methode), müssen sich die beiden Komponenten in der Haupt- oder Nebenversionsangabe unterscheiden, zum Beispiel:
    2.0.2.2
Eine abwärtskompatible Änderung wird durch die Elemente Build und Revision beschrieben. Änderungen, die über diese beiden Elemente bekannt gegeben werden, sind nur Korrekturen oder Fehlerbeseitigungen im Programmcode, die auf den Client keinen Einfluss ausüben – zumindest nicht im negativen Sinne, denn normalerweise dürfte ein Client von solchen Änderungen nur profitieren. Hier klicken, um das Bild zu vergrößern Abbildung 15.6 Das Schema der Versionierung Die Versionsnummer wird vor der Kompilierung einer Assembly als Attribut in der Datei AssemblyInfo.cs festgelegt.
    [Assembly: AssemblyVersion("1.0.1.0")]
Alternativ können Sie die Version auch im Eigenschaftsfenster des Projekts festlegen. Klicken Sie dazu auf den Knoten Properties im Projektmappen-Explorer, und vergewissern Sie sich, dass die Registerkarte Anwendung angezeigt wird. In dieser finden Sie die Schaltfläche Assemblyinformationen. Darüber gelangen Sie zu einem Dialog, wie er in Abbildung 15.7 zu sehen ist. Hier können Sie übrigens nicht nur die Version festlegen, sondern auch eine Reihe weiterer allgemeiner Informationen, die nach dem Schließen des Dialogs in die Datei AssemblyInfo.cs eingetragen werden. Hier klicken, um das Bild zu vergrößern Abbildung 15.7 Festlegen der Assembly-Version #### Schlüsseldatei erzeugen Globale Assemblys sind dadurch gekennzeichnet, dass sie mit einem binären Schlüsselpaar signiert sind, das aus einem öffentlichen und einem privaten Schlüssel besteht. Beide kryptografischen Schlüssel dienen einerseits zur Identifizierung einer Assembly und gewährleisten andererseits bei einer Änderung, dass der Autor der neuen Version derselbe ist wie der Autor der alten Version. Nur der Entwickler der Ursprungsversion ist im Besitz von beiden Schlüsseln. Beim Kompiliervorgang wird ein Teil des öffentlichen Schlüssels (Token) in das Manifest geschrieben und die Datei, die das Manifest enthält, mit dem privaten Schlüssel signiert. Der öffentliche Schlüssel ist ein Teil der Informationen, die eine Clientanwendung zur eindeutigen Identifikation einer bestimmten Assembly benötigt. Der private Schlüssel ist für den Aufrufer bedeutungslos, er sichert aber die Arbeit des Komponentenentwicklers und schützt gleichzeitig vor unbefugter oder gar böswilliger Änderung einer globalen Assemblierung. Privater und öffentlicher Schlüssel korrespondieren miteinander, mit anderen Worten: Zu einem öffentlichen Schlüssel gehört auch ein bestimmter privater – das ist ein wichtiger Aspekt, der in seiner Bedeutung nicht hoch genug eingeschätzt werden darf. Der öffentliche und der private Schlüssel werden durch eine Schlüsseldatei beschrieben, die mit dem Tool sn.exe erzeugt wird. Sie können dieses Tool an der Kommandozeile aufrufen und die notwendigen Optionsschalter setzen. Eleganter und einfacher ist es jedoch, wenn Sie dazu Visual Studio 2010 nutzen. Öffnen Sie dazu das Projekteigenschaftsfenster, und wählen Sie die Lasche Signierung. Markieren Sie anschließend die Auswahlbox Assembly signieren. Daraufhin wird die Auswahlliste aktiviert, die die Suche nach einer bereits vorhandenen Schlüsseldatei oder das Erstellen einer neuen ermöglicht. Wenn Sie sich für letztgenannte Alternative entscheiden, geben Sie in einem zusätzlichen Dialogfenster den Schlüsseldateinamen an. Darüber hinaus können Sie die Schlüsseldatei auch mit einem Kennwort schützen. Beim Signieren einer Assembly haben Sie möglicherweise nicht immer Zugriff auf den privaten Schlüssel. So kann ein Unternehmen beispielsweise ein stark gesichertes Schlüsselpaar haben, auf das die Entwickler nicht täglich zugreifen können. In diesem Fall müssen Sie eine verzögerte Signierung vornehmen, um zunächst nur den öffentlichen Schlüssel verfügbar zu machen. Markieren Sie hierzu in der Registerkarte Signierung den Optionsschalter Nur verzögerte Signierung. Das Hinzufügen des privaten Schlüssels wird auf den Zeitpunkt verschoben, zu dem die Assembly bereitgestellt wird. Hier klicken, um das Bild zu vergrößern Abbildung 15.8 Eine Schlüsseldatei mit Visual Studio 2010 erzeugen #### Installation einer Assembly im GAC mit dem Tool »gacutil.exe« Eine Assembly global bereitzustellen erfordert zwei Arbeitsgänge:
1. | Der Quellcode wird kompiliert, und dabei wird eine vorher erzeugte oder bereits vorhandene Schlüsseldatei eingebunden. Damit ist die Assembly zur gemeinsamen Nutzung vorbereitet.
2. | Das Kompilat muss im GAC installiert werden.
Wie der erste Punkt erfüllt wird, habe ich Ihnen im Abschnitt zuvor gezeigt. Zur Erfüllung des zweiten bieten sich zwei Alternativen: das Kommandozeilenprogramm gacutil.exe des .NET Frameworks eine Installationsroutine mit dem Micosoft Windows Installer Widmen wir uns zunächst dem Kommandozeilentool gacutil.exe. Wie alle anderen Tools findet man gacutil.exe unter: \Programme\Microsoft SDKs\Windows\V6.0A\bin Die allgemeine Aufrufsyntax lautet:
    gacutil [Optionen] [Assemblyname]
Aus der Liste der Optionen ragen zwei besonders heraus: der Schalter /i, um die darauf folgend angegebene Assembly im GAC zu installieren, und der Schalter /u, um eine gemeinsam genutzte Assembly zu deinstallieren, zum Beispiel:
    gacutil /i MyGlobalAssembly.dll
beziehungsweise:
    gacutil /u MyGlobalAssembly
#### Installation einer Assembly im GAC mit einer Setup-Routine Viel komfortabler lässt sich eine Assembly im GAC durch die Bereitstellung einer automatisch ablaufenden Installationsroutine eintragen. Visual Studio bietet uns hierfür mit der Projektvorlage Setup-Projekt einen eigenen Projekttyp an. An dieser Stelle möchte ich nicht in allen Einzelheiten erläutern, wie Sie eine Setup-Routine für ein zu verteilendes Projekt erzeugen. Stattdessen beschränke ich mich darauf, zu zeigen, wie Sie mittels einer Setup-Routine eine Assembly in den GAC eintragen können. Dazu nehmen wir uns wieder unser Beispiel GeometricObjectsSolution zu Hilfe. Die Klassenbibliothek GeometricObject wird zunächst wie beschrieben um eine Schlüsseldatei ergänzt, die anschließend im Projektmappen-Explorer zu sehen ist (siehe Abbildung 15.9). Hier klicken, um das Bild zu vergrößern Abbildung 15.9 Projektmappe mit Schlüsseldatei Danach sollten Sie nicht vergessen, das Projekt noch einmal zu kompilieren.
Hinweis
Sie sollten in jedem Fall die Option Neu erstellen auswählen. Aus Performance-Gründen cacht Visual Studio beim »einfachen« Kompilieren (Erstellen) Daten. Das kann dazu führen, dass die Installation der Klassenbibliothek im GAC fehlschlägt.
Der Projektmappe fügen Sie anschließend ein Setup-Projekt hinzu. Öffnen Sie dazu das Kontextmenü der Projektmappe, und wählen Sie Hinzufügen • Neues Projekt. Stellen Sie die Optionen so wie in Abbildung 15.10 gezeigt ein. Als Speicherort wird Ihnen die Projektmappe angeboten, die Sie auch übernehmen sollten. Hier klicken, um das Bild zu vergrößern Abbildung 15.10 Auswahl des Setup-Projekts Nachdem Sie auf die OK-Schaltfläche geklickt haben, wird im Codeeditor der Dateisystemeditor des Setup-Projekts angezeigt. Er dient dazu, unter anderem alle zu installierenden kompilierten Dateien zusammenzufassen. Markieren Sie jetzt im linken Teilbereich des Fensters den Knoten Anwendungsordner, und öffnen Sie dessen Kontextmenü. Im Kontextmenü wählen Sie zuerst Hinzufügen und danach Datei. Es wird sich dann ein Dialog öffnen, in dem Sie zu der kompilierten EXE-Datei der Anwendung TestApplication navigieren. Diese wählen Sie aus. Danach sehen Sie nicht nur die von Ihnen ausgewählte ausführbare Datei im rechten Teilbereich des Dateisystem-Editors. Darüber hinaus wird auch automatisch das Kompilat der Klassenbibliothek, also die DLL, als notwendige Komponente der Anwendung erkannt und ebenfalls in den Dateisystem-Editor kopiert. Hier klicken, um das Bild zu vergrößern Abbildung 15.11 Ansicht des Dateisystem-Editors Würden Sie nun das Setup-Projekt kompilieren, installieren und ausführen, könnten wir zwar unsere TestApplication starten und die Typdefinitionen in der Klassenbibliothek benutzen, aber die DLL wäre noch nicht als globale Assemblierung im GAC eingetragen, weil wir unsere Absicht dem Setup-Projekt nicht mitgeteilt haben. Das wollen wir nun nachholen. Öffnen Sie dazu im linken Bereich des Dateisystem-Editors auf dem obersten Knoten Dateisystem auf Zielcomputer, und öffnen Sie das Kontextmenü. Über Speziellen Ordner hinzufügen gelangen Sie an eine lange Liste Windows-spezifischer Ordner. Wählen Sie hier Cacheordner für globale Assembly. Danach wird ein gleichnamiger Knoten im Dialog angezeigt. In diesen verschieben Sie die DLL unserer Klassenbibliothek GeometricObjects, die unter Anwendungsordner zu finden ist. Nun ist der Moment gekommen, das Setup-Projekt zu kompilieren. Als Resultat dieses Vorgangs wird eine setup.exe-Datei im Ausgabeordner des Setup-Projekts erzeugt. Installieren Sie nun durch Aufruf dieser Installationsdatei das Programm. Alternativ lässt sich die Installation auch aus dem Visual Studio heraus bewerkstelligen. Klicken Sie dazu im Kontextmenü des Setup-Projekts im Projektmappen-Exporer auf Installieren. Nach der Beendigung können Sie sich überzeugen, dass die Klassenbibliothek GeometricObjects tatsächlich im GAC zu finden ist (siehe Abbildung 15.12). Hier klicken, um das Bild zu vergrößern Abbildung 15.12 Die Klassenbibliothek »GeometricObjects« im GAC (unter Windows XP)
Hinweis
Zu dem Zeitpunkt, als dieses Buch entstand, unterstützte das Plug-in von Windows 7 noch nicht die .NET 4.0-Bibliotheken. Mit anderen Worten: Die Bibliothek wird zwar richtig im GAC installiert, ist jedoch nicht sichtbar. Auch Beiträge in diversen Foren konnten nicht zur Lösung des Problems beitragen.
Der Vorteil des Global Assembly Caches ist, dass andere Anwendungen, die ebenfalls auf die Dienste von GeometricObjects zurückgreifen, sich derselben Komponente bedienen können, weil sie zentral registriert ist. Auf der Buch-DVD finden Sie die drei Projekte GeometricObjects (als Klassenbibliothek), TestApplication und GeometricObjects-Setup in der Projektmappe GeometricObjectsSolution. Um die gleich folgenden Ausführungen anschaulich zu zeigen, wurde in der Klasse Circle die folgende Methode ergänzt:
    // Methode zur Demonstration der Versionierung
    public string GetVersionNumber() {
      return "1.0.0.0"; ;
    }
Beim Starten von TestApplication wird ein Circle-Objekt erzeugt und die Methode aufgerufen. Erwartungsgemäß wird 1.0.0.0 angezeigt. #### Neue Version der globalen Assembly installieren Nicht ungewöhnlich für eine allgemein zur Verfügung stehende Komponente ist, dass eine neue Version von ihr verteilt wird – sei es, um Bugs zu beseitigen, sei es, um die alte Komponentenversion um neue Fähigkeiten zu erweitern. Angenommen, die erste Version von GeometricObjects sei bereits verteilt (unter anderem über TestApplication). Nun stellen wir fest, dass eine Änderung in der Klasse Circle notwendig ist. Diese sei symbolisiert durch eine Änderung der Methode GetVersionNumber in
    public string GetVersionNumber() {
      return "2.0.0.0"; ;
    }
Sie sollten dazu eine neue Projektmappe anlegen, dieser das existierende Projekt Geometric Objects hinzufügen und darin die Änderung durchführen. Nach der Änderung dürfen Sie nicht vergessen, auch die Versionsnummer der Klassenbibliothek zu erhöhen. Das können Sie im Eigenschaftsfenster des Projekts oder in der Datei AssemblyInfo.cs tun. Stellen Sie hier bitte Assembly Version auf 2.0.0.0 ein, und kompilieren Sie danach das Projekt. Natürlich soll auch diese neue Version wieder verteilt werden. Das kann dadurch geschehen, dass neue Anwendungen die zweite Version von GeometricObjects der Codierung zugrunde legen, das kann aber auch durch ein Update mittels einer eigens dafür bereitgestellten Setup-Routine der Fall sein. Wir wollen an dieser Stelle die letztgenannte Variante einsetzen und fügen der Projektmappe ein neues Setup-Projekt hinzu. Die sich daran anschließenden Schritte sind nahezu mit den oben beschriebenen identisch. Allerdings interessiert uns zur Verteilung von Version 2 nur die neu kompilierte DLL, die wieder in dem Knoten Cacheordner für globale Assembly des Dateisystem-Editors bekannt gegeben wird. Hier klicken, um das Bild zu vergrößern Abbildung 15.13 Zwei versionsverschiedene Einträge im GAC (unter Windows XP) Auch die neue Setup-Routine muss ausgeführt werden, damit sich die zweite Version von GeometricsObject in den GAC einträgt.