Assemblies – Pakete einer .NET Anwendung (Teil 2)

Veröffentlicht: 17. Dez 2001 | Aktualisiert: 14. Jun 2004
Von Michael Willers

* * *

Auf dieser Seite

Assemblies und Versionierung - Praxis! Assemblies und Versionierung - Praxis!
Finden und Laden von Assemblies Finden und Laden von Assemblies
Assemblies und Mehrsprachigkeit Assemblies und Mehrsprachigkeit

Assemblies und Versionierung - Praxis!

Die CLR erlaubt es, mehrere Versionen von systemweit gültigen Assemblies parallel zu betreiben. Arbeiten wir dazu ein Beispiel durch. Der Einfachheit halber verwenden wir »Single-File-Assemblies«.

Wir erstellen zunächst die Versionen 1.0.0.0 und 1.0.1.0 eines Assemblies mit der Bezeichnung dog_server. Anschließend tragen wir beide Versionen im GAC ein (siehe Bild 5.14)

Dann erstellen wir dazu zwei passende Clients dog_client_V1 und dog_client_V11 und rufen sie auf.

D.h.: Die im GAC befindlichen Versionen 1.0.0.0 und 1.0.1.0 können parallel von unterschiedlichen Clients benutzt werden !

Bild01

Abbildung 14: Verschiedene Versionen eines sytemweit gültigen Assemblies werden im GAC eingetragen.

Im nächsten Schritt löschen wir die Assembly-Version 1.0.1.0 aus dem Cache (gacutil /u). Rufen wir nun den Client, der gegen die Version 1.0.0.0 erstellt worden ist, erneut auf, erhalten wir eine Exception von Typ System.IO.FileNotFound-Exception.

Der Typ der Exception gibt Aufschluss über eine Grundregel der CLR. Da die Methodensignaturen beider Assembly-Versionen unterschiedlich sind, hätte eine
Exception vom Typ System.MissingMethodException auftreten müssen.

Die Exception ist aber vom Typ FileNotFoundException.

Anders ausgedrückt: Standardmäßig versucht die CLR die Version des Assemblies zu laden, die im Client-Manifest eingetragen ist. Befindet sich diese Version nicht im GAC, wird eine Exception vom Typ System.IO.FileNotFound-Exception geworfen.

Dieses Standardverhalten können Sie in der Konfgurationsdatei der Anwendung überschreiben und definieren, welche Assembly-Version die Runtime laden soll. Listing 5.4. zeigt die notwendigen Einträge. Dabei ist zu beachten, das die Versionsnummern komplett mit allen vier Stellen angegeben werden müssen.

Listing 5.4: Assem-bly-Versionen per Konfigurations-datei definieren

<configuration> 
<runtime> 
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> 
<dependentAssembly> 
<assemblyIdentity name="[Assembly Name ]" 
publicKeyToken="[public key token ]“ 
culture=""/> 
<bindingRedirect oldVersion="[Versionsnummer ]"newVersion=" 
[Versionsnummer ]"/> 
</dependentAssembly> 
</assemblyBinding> 
</runtime> 
</configuration>

Bild02

Abbildung 15: Die Versionsnummer im Detail.

In diesem Fall wird das Verhalten der CLR beim Laden des Assemblies über die einzelnen Stellen der Versionsnummer definiert (siehe Bild 5.15). Es gelten folgende Regeln:

  • Ein Shared Assembly ist grundsätzlich inkompatibel zumClient,wenn sich die Major- oder Minor-Version ändert

  • Beispiel: neues Produktrelease

  • Runtime wirft eine Type Load Exception aus

  • Ein Shared Assembly kann kompatibel zum Client sein, wenn sich die Revision bei gleichbleibender Major- und Minor-Version ändert

  • Beispiel: Servicepack

  • Runtime lässt Aufruf zu

  • Methodensignaturen werden nicht geprüft!

  • Ein Shared Assembly ist grundsätzlich kompatibel zumClient,wenn sich nur die Bildnummer ändert

  • In diesem Fall liegt ein sogenannter Quick Fix Engineering (QFE) vor

  • Beispiel: Security Hotfix

  • Runtime lässt Aufruf zu

  • Methodensignaturen werden nicht geprüft!

Zurück zum Beispiel: Wir fassen jetzt die Funktionalitäten beider Versionen in einem neuen Assembly mit der Versionsnummer 1.1.0.0 zusammen und installieren diese Version ebenfalls im GAC (Bild 5.16). Dann erstellen wir für den dog_client_V1 eine Konfigurationsdatei gemäß Listing 5.4 und »biegen« die Ver-sionsnummer von 1.0.0.0 auf 1.1.0.0 um. Es wird nun zukünftig das Assembly mit der Version 1.1.0.0 geladen.

Bild03

Abbildung 16: Zusammenfassen von Funktionen zu einer neuen Assembly-Version.

Es bleibt die Frage, warum es sinnvoll sein kann, eine Versionsnummer »umzu-biegen«. Stellen Sie sich vor, die Version 1.1.0.0 ist neu installiert worden und hat einen Fehler. Die Version 1.0.0.0 funktionierte hingegen reibungslos.

Ohne sämtliche Clients neu kompilieren zu müssen, können so sämtliche Aufrufe auf die zuvor funktionierende Version »umgebogen» werden und die Clients
bleiben lauffähig !!!

Das »Umbiegen« der Versionsnummer per Konfigurationsdatei bringt allerdings einen gravierenden Nachteil mit sich. Die Einträge müssen für jede Anwendung
einzeln erstellt werden. Würde man diese Information im GAC ablegen, wäre sie systemweit an zentraler Stelle abgelegt und das »Umbiegen« würde automatisch ohne Konfigurationsdateien erfolgen. Auf genau dieser Idee basieren die sogenannten Publisher Policies.

Liefert ein Hersteller eine neue Version eines Assemblies aus, kann er zusätzlich Publisher Policies ausliefern. Das sind Shared Assemblies, die »das Umbiegen« von Versionsnummern definieren und zusammen mit dem neuen Assembly im GAC installiert werden.

Per Konvention beziehen sich Policies immeraufMajor-undMinor-Version eines Assemblies, da nur in diesem Fall ein Assembly von der CLR als nicht kom-patibel angesehen wird.

Der Name dieser Assemblies ist fest vorgegeben: policy.<major>.<minor>. <As-semblyName>.dll

Bild 5.17 zeigt exemplarisch, wie Publisher Policies erstellt und eingerichtet werden.

Bild04

Abbildung 17: Die Konfigurationsdatei unseres Beispiels wird durch eine Publisher Policy ersetzt.

Stellt sich nun zum Schluss noch die Frage: Warum definiert man Publisher Policies in Form von Shared Assemblies? Auch XML-Dateien können zentral abgelegt werden.

Dieses Verfahren wurde aus Sicherheitgründen gewählt. Shared Assemblies müssen grundsätzlich signiert werden. Somit kann auch der Urheber einer Publisher Policy immer eindeutig ermittelt werden.

Finden und Laden von Assemblies

Für das Laden von Assemblies sind zwei Komponenten zuständig:

  • Der AssemblyLoader und

  • der Assembly Resolver.

Der Assembly Loader lädt ein Assembly direkt. Er bekommt eine URL übergeben und versucht dann, das über die URL angegebene Assembly zu laden. Dabei gibt
es drei Möglichkeiten:

  • Der Loader findet das Assembly und lädt es in den Speicher

  • Das Assembly existiert nicht. Die CLR wirft dann eine Exception vom Typ System.IO.FileNotFoundException

  • Die angegebene Datei ist kein CLR-Assembly. Die CLR wirft dann eine Exception vom Typ System.BadImageFormatException

Der programmatische Zugriff auf den Loader erfolgt mit der Methode System.Reflection.Assembly.LoadFrom. Das nachfolgende Listing (5.5) zeigt die Aufrufsyntax.

Listing 5.5: Direktes Laden eines Assemblies über den Assembly-Loader

using System; 
using System.Reflection; 
public static object GetDog(string id) 
{ 
Assembly a =Assembly.LoadFrom("file://c:/mwillers/bin/dog_server.dll"); 
Return a.CreateInstance("devcoach.Samples.Assemblies.DogDemo.CPoodle"); 
} 
public static void Main() 
{ 
CPoodle dog =(CPoodle)GetDog("Poodle"); 
Console.WriteLine(dog.Bark("Michael")); 
}


Der Assembly Resolver wird immer dann benutzt, wenn ein Assembly statisch referenziert wurde (per Compilerparameter /r). In diesem Fall versucht die CLR, das Assembly implizit über den Aufruf der Methode System.Reflection.Assembly. Load zu laden. Ein expliziter Aufruf ist aber auch möglich wie Listing 5.6 zeigt.

Listing 5.6: Explizites Laden eines Assemblies mit dem Assembly Resolver

using System; 
using System.Reflection; 
public static object GetDog(string id) 
{ 
Assembly a =Assembly.Load( 
"dog_server.dll,Version=1.0.0.0,Culture=neutral,PublicKeyToken=9e0fbde047643206" 
); 
Return a.CreateInstance("devcoach.Samples.Assemblies.DogDemo.CPoodle"); 
} 
public static void Main() 
{ 
CPoodle dog =(CPoodle)GetDog("Poodle"); 
Console.WriteLine(dog.Bark("Michael")); 
}


Tatsächlich erfolgt das Laden des Assemblies hier in zwei Stufen: Der Assembly-Resolver versucht eine URL zu bilden und ruft dann mit dieser URL den Assembly Loader auf (siehe Bild 5.18)

Bild05

Abbildung 18: Assembly-Loader und Assembly-Resolver.

Bitte unbedingt beachten:
Wenn der Versionierungsmechanismus der Runtime greifen soll, muss beim expliziten Aufruf dieser Methode immer der vollständige Strong Name des Assemblies angegeben werden (Name, Culture, Version und PublicKeyToken). Ansonsten führt die CLR keine Versionsüberprüfung durch!

Anders ausgedrückt: Die Prüfung, ob ein Assembly im Global Assembly Cache eingetragen ist, schlägt immer fehl!

Der Resolver beginnt seine eigentliche Arbeit mit der Überprüfung der CodeBase. Bild 5.19 zeigt die Arbeitsweise des Assembly Resolvers im Detail.

Bild06

Abbildung 19: Der Assembly Resolver im Detail

Assemblies und Mehrsprachigkeit

Assemblies können optional mit Länderkennungen versehen werden. Diese Kennung kann über das Attribut [assembly: AssemblyCulture(<Länderkennung>)] in einer der zum Assembly gehörenden Sourcedateien angegeben werden. Beim Übersetzen durch den Compiler wird dann ein Assembly erstellt, das dann automatisch von dieser Länderkennung Gebrauch macht (etwa in Routinen, die Datum, Uhrzeit oder Währungen ausgeben).

Die Länderkennung ist dabei gemäß IETF RFC 1766 anzugeben. Die folgenden Tabellen geben einige Kennungen wieder.

Bild07












Bild08












Bild09









Bild10










Eine vollständige Auflistung der Länderkennungen finden Sie in der msdn Dokumentation unter ms-help://MS.VSCC/MS.MSDNVS/cpref/html/frlrfsystemglobalizationculture-infoclasstopic.htm oder suchen Sie nach dem Begriff »CultureInfo«.


Anzeigen: