Implementieren Sie eigene Host-Prozesse für die Laufzeitumgebung

Veröffentlicht: 07. Sep 2001 | Aktualisiert: 19. Jun 2004
Von Steven Pratschner

Es ist zwar selten notwendig, einen eigenen Host für die CLR zu schreiben, jedoch ist das Verständnis von dessen Funktionsweise für einen tieferen Einblick in die CLR-Architektur sehr hilfreich. Dieser Beitrag zeigt, wie Anwendungen und Prozesse in der .NET-Umgebung mit der CLR zusammenarbeiten.

* * *

Auf dieser Seite

Microsoft .NET Microsoft .NET
Start der CLR Start der CLR
Das Laden der CLR in einen Prozess Das Laden der CLR in einen Prozess
Versionswahl Versionswahl
Server oder Workstation Server oder Workstation
Gleichzeitige Garbage Collection Gleichzeitige Garbage Collection
Ladeoptimierung Ladeoptimierung
ICorRuntimeHost ICorRuntimeHost
Entwurf der Host-Architektur Entwurf der Host-Architektur
Domänengrenzen Domänengrenzen
Konfiguration der Anwendungsdomänen Konfiguration der Anwendungsdomänen
Laden und Ausführen von Anwendercode Laden und Ausführen von Anwendercode
Auflösung von Referenzen auf Baugruppen Auflösung von Referenzen auf Baugruppen
Festlegung der Sicherheitsrichtlinien Festlegung der Sicherheitsrichtlinien
Das Ausladen von Anwendungsdomänen Das Ausladen von Anwendungsdomänen
Fazit Fazit

Diesen Artikel können Sie hier lesen dank freundlicher Unterstützung der Zeitschrift:

sys.gif

Microsoft .NET

Die allen Sprachen gemeinsame Laufzeitschicht (CLR) ist die Grundlage des gesamten .NET-Systems. Die CLR bietet eine Ausführungsumgebung mit diversen Diensten an, die dem Softwareentwickler das Leben erleichtert und zudem in der Lage ist, mit verwaltetem Code umzugehen. Zu den Dienstleistungen gehört eine automatische Speicherverwaltung, eine sprachübergreifende Integration, die Zusammenarbeit mit vorhandenem Code und vorhandenen Systemen, eine vereinfachte Installation und ein feinstufiges Sicherheitssystem.

Die CLR (Common Language Runtime) ist flexibel genug, um eine ganze Reihe von verschiedenen Anwendungsarten fahren zu können. So sind die Leistungen der CLR gleichermaßen für Konsolenanwendungen und Webserverskripte verfügbar, für heruntergeladene Steuerelemente, traditionelle Win32-Anwendungen, Datenbankabfragen, für die Makros in den kommerziellen Anwendungen und so weiter und so fort. Die CLR hat für die meisten Szenarien etwas zu bieten, in denen Code geschrieben und ausgeführt wird.

In Zukunft wird der Code, der seitens des Systems für die Ausführung der verschiedenen Programmarten erforderlich ist, automatisch im Betriebssystem zur Verfügung stehen. Derzeit ist es aber noch so, dass für jede Anwendung noch ein spezielles Stückchen Code erforderlich ist, damit es unter .NET läuft. Dieses spezielle Stückchen Code wird CLR-Host genannt. Ein CLR-Host ist insbesondere für das Laden der CLR in den Prozess zuständig, für die Definition der Anwendungsdomänen innerhalb des Prozesses und für die Ausführung des Anwendercodes in diesen Domänen. Auf die Anwendungsdomänen und den Anwendercode komme ich später noch zurück, wenn ich die Entwicklung eines anwenderdefinierten CLR-Hosts beschreibe.

Mit dem .NET Framwork werden eine ganze Reihe von Hosts ausgeliefert, darunter folgende:

ASP.NET Ein ISAPI-Filter, das mit dem ASP.NET ausgeliefert wird, ist für den Start der CLR und für die Initialisierung des Mechanismus verantwortlich, der die Webaufträge an die ASP.NET-Prozesse weiterleitet.

Internet Explorer Das .NET Framework wird mit einem MIME-Filter geliefert, das sich in den Internet Explorer 5.01 (oder höher) einklinkt und für die Ausführung der "verwalteten" Steuerelemente zuständig ist, die auf den HTML-Seiten benutzt werden.

Shell-Programme Wird von der Shell aus ein Programm gestartet, so wird jedes Mal auch ein kleines Stückchen unverwalteter Code aufgerufen, der die Kontrolle an die CLR übergibt.

Andere Hosts wären zum Beispiel:

Datenbankmaschinen Eine zukünftige Version vom SQL Server wird gespeicherte Prozeduren verarbeiten können, die in Sprachen für das .NET Framework geschrieben sind und mit der CLR ausgeführt werden.

Persönliche Assistenten Einige E-Mail-/Kalender-/Terminverwaltungsprogramme ermöglichen es den Anwendern, zur Arbeitserleichterung eigene Skripte zu entwickeln. Man kann sich leicht vorstellen, dass diese Skripte auf der CLR laufen. Das Sicherheitssystem der CLR ist unter diesen Umständen besonders wichtig, weil sich Viren besonders gerne über das E-Mail-System ausbreiten.

Zur Implementierung eines neuen Hosts muss man natürlich wissen, welche Aufgaben der Host hat und wie man die Lösungen implementiert. Die Beschreibung der Aufgaben möchte ich mit der Frage beginnen, wie die CLR gestartet, konfiguriert und in einen Prozess geladen wird und wie man eine bestimmte CLR-Version auswählt, falls mehrere Versionen verfügbar sind.

Start der CLR

Bevor verwalteter Code in einem Prozess ausgeführt werden kann, muss die CLR geladen und initialisiert werden. Das ist Sache des Hosts. Derzeit werden alle Hosts mit unverwaltetem Code gestartet. Daher bietet das .NET Framework einige unverwaltete Funktionen an, mit der sich die CLR laden lässt.

Von der CLR lassen sich sogar mehrere Versionen nebeneinander laden und betreiben, damit sich der Umstieg auf neue Versionen flexibler gestalten lässt. Ältere Laufzeitbibliotheken von Microsoft, wie zum Beispiel die Laufzeitschicht von Visual Basic, die virtuelle Maschine für Java und die COM-Infrastruktur zwangen die Administratoren dazu, auch dann auf eine neue Version umzusteigen, wenn nur eine einzige Anwendung die neue Version brauchte oder die fragliche Laufzeitbibliothek aus einem Service Pack mit Fehlerkorrekturen für andere Windows-Komponenten stammte.

Während die Flexibilität, die sich aus der Installation von verschiedenen Versionen nebeneinander ergibt, für den Administrator eine gute Sache ist, wird die Arbeit für den Host dadurch schwieriger. Er muss entscheiden, wie er angesichts mehrerer CLR-Versionen arbeiten soll. Und er muss eine bestimmte CLR-Version aussuchen, die er in den gegebenen Prozess lädt. Obwohl es auf einer gegebenen Maschine mehrere CLR-Versionen geben kann, darf in einem bestimmten Prozess immer nur eine Version laufen. Sobald sich der Host also für eine bestimmte Version entschieden hat, benutzt der gesamte verwaltete Code, der im betreffenden Prozess läuft, diese eine CLR-Version.

Für die Auswahl der richtigen CLR-Implementierung unter mehreren gibt es ein spezielles Startmodul mit der seltsamen Bezeichnung "startup shim". Dieses Startmodul ist im Prinzip nur ein kleines Stückchen Software, das mit einer Versionsnummer und andere Argumenten aufgerufen wird und die gewünschte CLR startet. Auf einer gegebenen Maschine gibt es immer nur eine Version das Startmoduls. Sie ist im Standardsuchpfad der Maschine zu finden (derzeit in %windir%\system32). Das Modul wird so klein und einfach wie möglich gehalten, damit es möglichst keine Kompatibilitätsprobleme mit zukünftigen CLR-Versionen gibt.

Das Startmodul ist in der Datei mscoree.dll zu finden, während der Hauptteil der CLR-Maschine in der mscorsvr.dll oder mscorwks.dll steckt, je nachdem, ob Sie mit der Serverversion oder der Workstation-Version arbeiten (dazu später mehr). Die mscorsvr.dll und mscorwks.dll werden in Unterverzeichnissen von %windir%\Microsoft.NET\Framework installiert, die Versionsnummern als Namen haben. Alle DLLs, die eine gegebene Version der CLR implementieren, werden zusammen in einem Verzeichnis installiert - also nicht über mehrere Verzeichnisse verstreut. Bild B1 zeigt die Beziehung zwischen dem Startmodul und den DLLs mit dem CLR-Kern

13Host01.gif

B1 Startmodul (shim) und CLR-DLLs

Wie beschrieben besteht die Hauptrolle des CLR-Startmoduls darin, eine Versionsnummer anzunehmen und die entsprechende CLR-Version zu laden. Tabelle T1 zeigt die wichtigsten Funktionen aus dem CLR-Startmodul.

T1 Die wichtigsten Funktionen aus dem CLR-Startmodul

Funktion

Beschreibung

CorBindToRuntimeEx

Der Haupteintrittspunkt ins CLR-Lademodul. Dient zum Laden einer bestimmten Version der Laufzeitschicht in den Prozess.

GetCorVersion

Gibt die Version der in den Prozess geladenen Laufzeitschicht an.

GetCorSystemDirectory

Gibt das Installationsverzeichnis der Laufzeitschichtversion an, die in den aktuellen Prozess geladen wurde.

Das Laden der CLR in einen Prozess

Hosts laden die CLR mit der Funktion CorBindToRuntimeEx in einen Prozess. Es gibt vier Werte, die der Host beim Aufruf von CorBindToRuntimeEx festlegen kann. Diese Werte entscheiden, welche CLR geladen wird und wie sich bestimmte Grundfunktionen im Prozess verhalten, zum Beispiel die Garbage Collection und der Klassenlader. Die vier Werte betreffen die CLR-Version, die Ausführung (Server oder Workstation), die GC und die Ladeoptimierung. Die Versionsvorgabe legt natürlich fest, welche CLR-Version geladen wird. Durch die Wahl der Ausführung entscheidet der Aufrufer darüber, ob die Serverversion oder die Workstation-Version geladen wird. Die Vorgabe zur GC entscheidet darüber, ob die GC gleichzeitig erfolgt oder nicht. Die Ladeoptimierung schließlich wirkt sich darauf aus, ob die Baugruppen (Assemblies) domänenneutral geladen werden.

Durch die Wahl seiner Funktionsargumente hat der Host die direkte Kontrolle darüber, wie die CLR geladen wird. Allerdings ist jede Wahl optional. Für fehlende Vorgaben werden einfach Standardwerte benutzt. In den folgenden vier Abschnitten möchte ich genauer auf die Parameter eingehen.

Neben diesen vier Vorgaben kann der Host auch einen Schnittstellenzeiger auf eine der COM-Schnittstellen von der CLR anfordern. Die übliche Wahl fällt auf ICorRuntimeHost. Auch diese Schnittstelle werde ich im Folgenden noch genauer beschreiben. Im Wesentlichen ermöglicht diese Schnittstelle dem Host die Feineinstellung der Optionen, die Erstellung von Anwendungsdomänen und die Ausführung von Anwendercode im Prozess. Listing L1 zeigt, wie man den Zeiger auf ICorRuntimeHost mit CorBindToRuntimeEx anfordert.

L1 So liefert CorBindToRuntimeEx einen ICorRuntimeHost-Zeiger

LPWSTR pszVer = L"v1.0.2121"; 
LPWSTR pszFlavor = L"wks"; 
ICorRuntimeHost *pHost = NULL; 
hr = CorBindToRuntimeEx( 
                      // Version 
                      pszVer,        
                      // swr oder wks 
                      pszFlavor,     
                      // Domänenneutralität, GC-Vorgaben 
                      STARTUP_LOADER_OPTIMIZATION_MULTI_DOMAIN_HOST |  
                      STARTUP_CONCURRENT_GC,  
                      CLSID_CorRuntimeHost,  
                      IID_ICorRuntimeHost,  
                      (void **)&pHost); 
if (SUCCEEDED(hr)) 
{ 
}

Versionswahl

Die Versionsangabe, die via pszVersion übergeben wird, legt fest, welche CLR-Version zu laden ist. Das Argument pszVersion verweist genau genommen auf einen String mit dem Namen des Unterverzeichnisses von %windir%\Microsoft.NET\Framework, in dem die gewünschte CLR-Version zu finden ist. Der eigentlichen Versionsangabe im String muss dabei noch der Buchstabe "v" vorangestellt sein (zum Beispiel "v1.0.2212").

Wird für diesen Parameter nur eine null übergeben, so delegiert der Host die Entscheidung über die zu ladende Version an die getroffenen maschinen-, anwender- oder anwendungsspezifischen Vorgaben. Sofern nicht anders konfiguriert, wird die CLR mit der höchsten Versionsnummer geladen.

Server oder Workstation

Das .NET Framework wird mit zwei Ausführungen der CLR ausgeliefert, nämlich eine für Arbeitsplatzrechner und eine für Server. Der zweite Parameter von CorBindToRuntimeEx entscheidet nun darüber, ob die Ausführung für die Workstation oder für den Server geladen wird. Erstere wurde so optimiert, dass sie für die Client-Anwendungen die beste Leistung bringt, letztere dagegen in Multiprozessor-Umgebungen. Insbesondere nutzt die Server-Ausführung den Vorteil aus, der sich aus der Verfügbarkeit von mehreren Prozessoren ergibt, so dass die Garbage Collection für jeden Prozessor parallel zum normalen Betrieb erfolgen kann.

Wird eine null als Argument übergeben, so lädt das CLR-Startmodul die Workstation-Version. Auf einer Einprozessormaschine wird unabhängig von den Vorgaben des Hosts sowieso immer die Workstation-Version geladen, selbst wenn der Host "svr" verlangt. Der Grund für diese Beschränkung ist die erzielbare Leistung. Auf einer Einprozessormaschine ist die Workstation-Version immer schneller als die Serverversion.

Gleichzeitige Garbage Collection

Der Garbage Collector aus der CLR kann in zwei verschiedenen Betriebsarten laufen: gleichzeitig oder nicht gleichzeitig. Mit diesem Parameter legt der Aufrufer also fest, ob die "Müllsammlung" parallel zum normalen Betrieb erfolgen soll oder nicht. Den gleichzeitigen Betrieb des Garbage Collectors wählt der Host durch die Übergabe des Flags STARTUP_CONNCURRENT_GC für den dwflags-Parameter von CorBindToRuntimeEx. Wird dieses Flag nicht angegeben, läuft der GC in der anderen Betriebsart (nicht gleichzeitig).

Bei der Wahl der gleichzeitigen Garbage Collection erfolgen die Sammlungen nicht auf den Threads, die schon den Anwendercode fahren, sondern auf Hintergrundthreads. Mit Anwendercode meine ich jeden verwalteten Code, der nicht explizit zum Host gehört. So besteht der Anwendercode im Internet Explorer zum Beispiel aus den verwalteten Steuerelementen und dem Skriptcode, aus denen sich die HTML-Seiten zusammensetzen. Aus der Sicht des Hosts, der die ausführbaren Dateien von der Shell aus startet, ist der Anwendercode einfach der Code aus den gestarteten ausführbaren Dateien. Unter dem Strich bleibt festzuhalten, dass die Anwender bei der gleichzeitigen Garbage Collection eine schneller reagierende Anwendungsoberfläche sehen. Obwohl die Anwendung aber im Einzelfall schneller reagiert, ist die Gesamtleistung der Garbage Collection geringer. Gleichzeitige GC wird praktisch nur in Anwendungen mit komplexen Anwenderschnittstellen eingesetzt.

Im Gegensatz dazu führt der GC seine Sammlungen bei nicht gleichzeitigem Betrieb auf denselben Threads durch, die auch den Anwendercode fahren. Dadurch reagiert das Programm etwas langsamer auf die Aktionen des Anwenders, aber die Gesamtleistung des GCs ist unter diesen Bedingungen besser als bei gleichzeitiger Sammlung. Für Serveranwendungen wie Webserver oder Datenbankserver wird fast immer die nicht gleichzeitige Sammlung gewählt. Außerdem kommt die gleichzeitige GC gar nicht zum Zuge, wenn der Host auf einer Einprozessormaschine die Serverausführung der CLR anfordert.

Ladeoptimierung

Der vierte Punkt, in dem der Host seine Wünsche äußern darf, betrifft die Frage, ob die Baugruppen (Assemblies) domänenneutral geladen werden oder nicht. Welche Bedeutung diese Wahl hat, lässt sich erst abschätzen, wenn man weiß, wie die CLR Anwendungsdomänen definiert. Betriebssysteme und Laufzeitbibliotheken sorgen normalerweise in irgendeiner geeigneten Form für die Isolierung der laufenden Anwendungen voreinander. Diese Isolierung ist erforderlich, damit sich der Code einer Anwendung nicht nachteilig auf andere Anwendungen auswirken kann. Unter Windows wurde diese Isolierung bisher durch die Einführung von Prozessen (und Prozessgrenzen) erreicht. Nach diesem Modell läuft jede Anwendung in ihrem eignem Prozess. Daher wird keine andere Anwendung beeinträchtigt, wenn ein Prozess (eine Anwendung) abstürzt.

Selbstverständlich bleibt das Bedürfnis nach Isolation auch auf der .NET-Plattform erhalten. Aber dort gibt es viele Szenarien, in denen die Einrichtung einer Prozessgrenze unter Leistungsgesichtspunkten zu hohe Kosten mit sich bringt, weil bei jedem Übergang eine Threadumschaltung anfällt, die Umschaltung der Aufrufstapel und so weiter.

Unter .NET lässt sich überprüfen, ob der Anwendercode typsicher ist. Damit werden Speicherüberläufe von vorneherein verhindert. Daher können mehrere Anwendungen im selben Prozess laufen und sicherstellen, dass keine Anwendung den gesamten Prozess in den Abgrund reißt. Das sorgt für eine Isolierung zu niedrigeren Kosten, als bei Prozessgrenzen anfallen. Die CLR erlaubt es mehreren Anwendungen, im selben Prozess zu laufen, und zwar durch eine Konstruktion namens "Anwendungsdomäne" (application domain). Solche Anwendungsdomänen isolieren die Prozesse voreinander.

Anwendungsdomänen sind in vielerlei Hinsicht das CLR-Äquivalent eines Prozesses, wie ihn das Betriebssystem zur Verfügung stellt. In diesem Sinne wird der Anwendercode durch die Domäne isoliert, in welche er geladen wird. Das bedeutet, dass sich der Code nicht direkt durch andere Codeteile aufrufen lässt, die nicht in der betreffenden Anwendungsdomäne liegen, und auch selbst keine Codeteile aus anderen Anwendungsdomänen direkt aufrufen kann. Soll eine bestimmte Baugruppe von mehreren Anwendungen benutzt werden, die im selben Prozess laufen, wird die CLR mehrere Kopien von der Baugruppe laden - nämlich eine für jede Domäne, in der die Baugruppe benutzt wird.

Damit die Isolierung gewahrt bleibt, hat jede Domäne ihre eigene Kopie des Anwendercodes und der Datenstrukturen, die von der CLR zur Ausführung des Codes aufgebaut werden. In vielen Fällen lässt sich die Benutzung dieser Strukturen soweit optimieren, dass sich alle Domänen aus demselben Prozess solche CLR-Datenstrukturen teilen, die nur ausgelesen werden, aber nicht geändert werden können. Diese Optimierung kann den Speicherbedarf beträchtlich verringern, wenn dieselbe Baugruppe von mehreren Anwendungen im Prozess benutzt wird. Von Baugruppen, die in dieser Weise geladen werden, sagt man, dass sie "domänenneutral" geladen werden.

Domänenneutraler Code verbraucht zwar weniger Speicher, läuft aber etwas langsamer. Die geringere Geschwindigkeit resultiert aus der Art und Weise, in der die statischen Variablen und Methoden der Baugruppe benutzt werden. Jede Domäne erhält eine eigene Kopie der statischen Variablen, damit sich keine neuen Gelegenheiten für Ressourcenlecks ergeben, zum Beispiel durch die Übergabe von Objekten in statischen Variablen an andere Domänen. Mit dem Ergebnis, dass die CLR nun Tabellen führen muss, in denen die Zugehörigkeit eines gegebenen Aufrufers zu einer gegebenen Kopie der statischen Variablen festgehalten wird. Die Umleitung über diese Verknüpfungstabellen führt dazu, dass der Code insgesamt etwas langsamer läuft. Der Zugriff auf nichtstatische Daten und Methoden bleibt aber gleich und unabhängig davon, ob die Optimierung nun eingeschaltet wurde oder nicht (Ich werde später noch darauf zu sprechen kommen, wie man die Anwendungsdomänen konfiguriert.).

Die CLR ermöglicht dem Host die Wahl der Ladeoptimierung, und zwar im dwFlags-Parameter der Funktion CorBindToRuntimeEx. So nutzt das ASP.NET zum Beispiel diesen Vorteil aus und optimiert den Einsatz von Baugruppen wie System.WebForms und System.Data. Der Host kann einen der drei Werte aus Tabelle T2 angeben.

T2 Die Vorgaben zur Ladeoptimierung

Vorgabe

Beschreibung

Keine Baugruppe wird domänenneutral geladen (mit Ausnahme von mscorlib, die immer domänenneutral geladen wird).

Diese Vorgabe heißt "Einzelne Domäne", weil sie normalerweise benutzt wird, wenn der Host eine einzige Anwendung im Prozess fährt. Übergeben Sie das Flag STARTUP_LOADER_OPTIMIZATION_SINGLE_DOMAIN für den Parameter dwFlags von CorBindToRuntimeEx.

Alle Baugruppen werden domänenneutral geladen.

Diese Vorgabe wird normalerweise dann benutzt, wenn es im Prozess mehrere Domänen gibt und alle denselben Code fahren. So kann der Host zum Beispiel dieselbe Anwendung in mehreren Domänen fahren, die jeweils zu verschiedenen Anwendern gehören. Übergeben Sie das Flag STARTUP_LOADER_OPTIMIZATION_MULTI_DOMAIN an CorBindToRuntimeEx.

Gemeinsame Baugruppen (solche mit starken Namen) werden domänenneutral geladen.

Wählen Sie diese Einstellung, wenn Sie mehrere verschiedene Anwendungen im selben Prozess fahren. Übergeben Sie das Flag STARTUP_LOADER_OPTIMIZATION_MULTI_DOMAIN_HOST an CorBindToRuntimeEx. "Starke Namen" werden nach einem bestimmten Muster definiert und sollen sicherstellen, dass gemeinsame Baugruppen (das sind Baugruppen, die von mehreren Anwendungen gemeinsam benutzt werden) global eindeutige Namen haben und dass die Baugruppe seit dem Zeitpunkt ihrer Fertigstellung weder absichtlich noch zufällig verändert wurde

.

ICorRuntimeHost

Diese Schnittstelle ermöglicht es dem Host, wie schon erwähnt, die Optionen genauer festzulegen, neue Anwendungsdomänen einzurichten und den gewünschten Anwendercode im Prozess zu betreiben. Genauer gesagt, mit ICorRuntimeHost kann der Host eine ganze Reihe von zusätzlichen Konfigurationsparametern festlegen, Start und Stopp der CLR explizit steuern, einen Zeiger auf eine neue Anwendungsdomäne anfordern (also eine Anwendungsdomäne anlegen) und die Kontrolle an den verwalteten Code übergeben.

Die GetConfiguration-Methode von ICorRuntimeHost bietet den Zugang zu einer Schnittstelle namens ICorConfiguration, die sich zur Konfiguration bestimmter Aspekte für das Laden der CLR in den Prozess verwenden lässt oder für die Eintragung von zusätzlichen Ereignismeldungen (Events). So könnte ein Host zum Beispiel GetConfiguration benutzen, um die Größe der GC-Speicherhalde festzulegen oder um eine spezielle Funktion anzumelden, die immer dann aufgerufen werden soll, wenn ein bestimmter Thread im Debugger gestoppt wird. Tabelle T3 beschreibt die Methoden von ICorConfiguration.

T3 Die Methoden der Schnittstelle ICorConfiguration

Methode

Beschreibung

SetAppDomainLoadEvent

Meldet eine Funktion an, die aufgerufen wird, wenn im Prozess eine neue Anwendungsdomäne eingerichtet wurde.

SetGCThreadControl

Meldet eine Funktion an, mit der sich Threads außerhalb der CLR einsetzen lassen, die sonst für eine GC blockiert wären.

SetGCHostControl

Ermöglicht dem Host die Anpassung der Größe der GC-Speicherhalde.

SetDebuggerThreadControl

Meldet eine Funktion an, die aufgerufen wird, sobald Threads im Debugger gestartet oder angehalten werden.

AddDebuggerSpecialThread

Damit äußert ein Host den Wunsch, dass ein bestimmter Thread auch dann weiterlaufen soll, wenn der Debugger eine Anwendung angehalten hat.

Die Start- und Stop-Methoden von ICorRuntimeHost erlauben dem Host die explizite Kontrolle der Lebensdauer der in den Prozess geladenen CLR. Allerdings wird vom Host nicht verlangt, dass er diese Methoden explizit aufruft, denn Start wird implizit aufgerufen, sobald der erste verwaltete Code im Prozess ausgeführt werden soll, und Stop wird beim Herunterfahren des Prozesses ebenfalls implizit aufgerufen. Allerdings gibt es durchaus Szenarien, in denen der explizite Aufruf dieser Funktionen sinnvoll wird. So könnte einem Host zum Beispiel bekannt sein, dass er von einem bestimmten Punkt an keinen weiteren verwalteten Code mehr ausführen wird. An diesem Punkt könnte er die CLR stoppen, damit der von ihr beanspruchte Speicher und die Ressourcen ans System zurückgehen. Die CLR lässt sich allerdings nicht erneut in denselben Prozess laden, wenn sie einmal ausgeladen wurde.

Damit er mit der Ausführung von verwaltetem Code beginnen kann, muss sich der Host einen Zeiger auf die Anwendungsdomäne verschaffen. In vielen Fällen wird es sich um die Standarddomäne des Prozesses handeln. Der Host kann bei Bedarf aber zusätzliche Domänen anlegen (darauf komme ich später noch zurück).

Entwurf der Host-Architektur

Nachdem Sie nun erfahren haben, wie die CLR gestartet und initialisiert wird, können Sie mit der Konzeption des geplanten eigenen Hosts beginnen. Dieser Abschnitt beschreibt die Architektur eines typischen CLR-Hosts. Mit "typischer Architektur" meine ich die Architektur, die am besten läuft, am leichtesten zu schreiben ist und dem Host die größte Flexibilität bietet. Die meisten Hosts vereinen den herkömmlichen unverwalteten Code und den neuen verwalteten Code unter einem Dach. Natürlich ist der unverwaltete Code dafür zuständig, die CLR zu konfigurieren, in den Prozess zu laden und die Kontrolle an den verwalteten Code abzugeben. Der verwaltete Teil des Hosts ist normalerweise für die Erstellung der Domänen verantwortlich, in welchen der Anwendercode läuft, und für die Zustellung der Kundenaufträge an diese Domänen.

Die Hosts setzen sich aus zwei Gründen aus verwaltetem und unverwaltetem Code zusammen. Der erste ist die reine Laufleistung. So bringen Aufrufe, die vom unverwalteten in den verwalteten Code hinein erfolgen, gewisse Kosten mit sich. Im Allgemeinen ist es daher empfehlenswert, einmal in den verwalteten Code überzugehen - und dann dort zu bleiben, statt ständig hin und her zu springen. Der zweite Grund ist die einfachere Implementierung. Bei der Entwicklung der CLR war es ja eines der Ziele, die Codeentwicklung zu vereinfachen. Daher sollte man sich an die CLR halten, solange es nur geht.

Ein Teil des Hosts wird vermutlich als eine .NET-Baugruppe geschrieben, da sich sämtlicher verwalteter Code in einer .NET-Baugruppe befinden muss. Daher muss der Host entscheiden, in welcher Anwendungsdomäne sein eigener verwalteter Code laufen soll. Jeder Prozess hat eine Standarddomäne, die sich für diesen Zweck eignet. Diese Standarddomäne wird von der CLR automatisch angelegt, sobald sie in einem Prozess initialisiert wird. Beim Herunterfahren des Prozesses wird die Domäne wieder zerstört. Die meisten Hosts fahren in der Standarddomäne keinen Anwendercode, weil sich diese Domäne nicht unabhängig vom restlichen Prozess herunterfahren lässt.

Von ICorRuntimeHost::GetDefaultDomain erhält man einen Schnittstellenzeiger auf die Standarddomäne. Der gelieferte Zeiger verweist auf eine Instanz der Klasse System.AppDomain, von der die Standarddomäne repräsentiert wird. Der Schnittstellenzeiger hat den Typ _AppDomain und wird von der COM-Verbindungsschicht der CLR automatisch generiert. Kurz gesagt, der Host ruft die Methoden von einer Instanz der verwalteten Klasse System.AppDomain über die COM-Verbindungsschicht auf. Wie schon erwähnt ist es aus Leistungsgründen von Vorteil, wenn man die Übergänge zwischen verwaltetem und unverwaltetem Code auf ein Minimum reduziert.

Bild B2 skizziert diese Architektur.

13Host02.gif

B2 Die typische Architektur eines CLR-Hosts

Listing L2 zeigt, wie man die Standarddomäne ermittelt und den verwalteten Code des Hosts darin unterbringt. In diesem Fall liegt der verwaltete Code des Hosts in einer Baugruppe namens MyManagedHost.dll. Der Beispielcode legt eine Instanz von einem Typ namens HostProcessRequest aus der MyManagedHost.dll an. Der Kürze halber wurde der Code zur Fehlerbearbeitung weggelassen.

L2 Hier wird der verwaltete Code des Hosts geladen

// Importiere die Typbibliothek, in der die COM-Schnitt- 
// stellen für den Zugriff auf die verwalteten Klassen 
// aus der mscorlib definiert sind. Insbesondere müssen 
// wir via _AppDomain eine Instanz der verwalteten Klasse 
// System.AppDomain aufrufen. Diese Typbibliothek stammt 
// aus dem SDK. 
#import <mscorlib.tlb> raw_interfaces_only high_property_prefixes("_get","_put","_putref") 
using namespace ComRuntimeLibrary; 
// Importiere die Typbibliothek für den verwalteten Code 
// des Hosts. Diese Typbibliothek wurde mit dem SDK- 
// Programm tlbexp erstellt. 
#import <MyManagedHost.tlb> raw_interfaces_only high_property_prefixes("_get","_put","_putref") 
using namespace MyManagedHost; 
// Definiere eine Variable für den Zeiger auf die 
// Schnittstelle. 
ICorRuntimeHost *pCLR = NULL; 
// Lade die CLR mit CorBindToRuntime. Diese Funktion  
// liefert einen Zeiger auf ICorRuntimeHost, wie im 
// vorigen Codebeispiel gezeigt (der entsprechende  
// Code wurde hier weggelassen) 
// Ermittle die Standarddomäne. 
_AppDomain *pDefaultDomain = NULL; 
IUnknown   *pAppDomainPunk = NULL; 
HRESULT hr = pCLR->GetDefaultDomain(&pAppDomainPunk); 
_ASSERT(pAppDomainPunk);  
 hr = pAppDomainPunk->QueryInterface(__uuidof(_AppDomain),  
                                        (void**) &pDefaultDomain); 
_ASSERT(pDefaultDomain); 
// Lege eine Instanz der HostProcessRequest aus der  
// MyManagedHost.dll an. Der Schnittstellenzeiger auf 
// diese Instanz wird von nun an zur Weiterleitung der 
// Kundenaufträge an den verwalteten Code benutzt. 
_ObjectHandle *pObjHandle = NULL; 
hr = pDefaultDomain->CreateInstance( 
    _bstr_t("MyManagedHost")),       // Baugruppenname 
    _bstr_t("HostProcessRequest")),  // Typname 
    &pObjHandle);                    // Objekthandle 
_ASSERT(pObjHandle); 
VARIANT v; 
VariantInit(&v); 
hr = pObjHandle->Unwrap(&v); 
_ASSERT(v.pdispVal); 
_HostProcessRequest *pPR = NULL; 
hr = v.pdispVal->QueryInterface(__uuidof(_HostProcessRequest),  
                                (void**) &pPR); 
_ASSERT(pPR); 
// Vergessen Sie nicht die Release()-Aufrufe!

Domänengrenzen

Anwendungsdomänen sind, wie beschrieben, ein Mittel zur Isolierung der verschiedenen Anwendungen in einem Prozess. Die Definition, was eine Anwendung für den betreffenden Host bedeutet und wo daher die Grenzen der Anwendungsdomäne liegen, gehört zu den wichtigsten Entscheidungen, die ein Host treffen muss. Für den ASP.NET-Host entspricht eine Anwendung zum Beispiel einer vroot, wie sie das Administratorprogramm vom Webserver definiert. Aus der Sicht eines Datenbankservers wie dem SQL Server kann eine Anwendung einer bestimmten Datenbank entsprechen.

Bei der Entscheidung, wie eine Anwendung für Ihren Host definiert wird, spielen verschiedene Bedingungen eine Rolle:

Codeisolierung Direktaufrufe zwischen zwei Typen sind nur erlaubt, wenn die beiden Typen in derselben Anwendungsdomäne liegen. Alle Aufrufe, die aus der Domäne heraus oder in die Domäne hineinführen, erfolgen mit Hilfe von Proxies. Kurz gesagt, wenn ein Host sicherstellen möchte, dass sich die Codeteile aus zwei verschiedenen .NET-Baugruppen nicht gegenseitig aufrufen, muss er diese Baugruppen in verschiedene Domänen laden.

Konfiguration und Anwendungsisolierung Die Isolierung der Konfigurationsdaten und des Ortes, von dem die privaten Komponenten (die Baugruppen) geladen werden, ist eine entscheidende Voraussetzung dafür, dass der Entwickler seine Anwendung so auslegen kann, dass diese nicht durch Änderungen gestört wird, die für andere Anwendungen am System vorgenommen werden. Im Normalfall liegt die Anwendung in einem bestimmten Verzeichnis im Dateisystem. In diesem Verzeichnis und dessen Unterverzeichnissen werden die angeforderten privaten Baugruppen gesucht und geladen. So definiert der Internet Explorer zum Beispiel von Haus aus eine Anwendung pro Website. Das Stammverzeichnis der Site wird dann auch als Stammverzeichnis der Anwendung angesehen.

Sicherheit Ein Host hat in hohem Maße die Kontrolle über die Rechte, die der Code in einer bestimmten Anwendungsdomäne erhält (dazu später mehr). So kann ein Host zum Beispiel vorschreiben, dass der gesamte Code, der in einer bestimmten Domäne läuft, von einem bestimmten Herausgeber signiert worden ist. Zudem können Hosts ihre Sicherheitsrichtlinien von anwenderdefinierten Daten abhängig machen (auch dazu später mehr).

Stellen Sie sich ein Szenario vor, bei dem der Host eine bestimmte Vorstellung von der Identität des Anwenders hat. In diesem Fall könnte der Host zum Beispiel die Domänen anhand der Anwenderkonten unterteilen und durch eine entsprechende Definition der Sicherheitsrichtlinien dafür sorgen, dass nur solcher Code mit dem Konto einer bestimmten Person läuft, der in einer bestimmten Domäne zugelassen ist.

Ausladen Anwendungsdomänen sind die kleinste Einheit, die in der CLR aus dem Speicher ausgeladen werden kann. Nichts, was kleiner als eine Domäne ist, lässt sich ausladen. Also auch keine Baugruppen oder Typen. Die Möglichkeit, den Code nach Bedarf laden und wieder ausladen zu können, hat durchaus einen Einfluss auf die sinnvolle Festlegung einer Domänengrenze.

Konfiguration der Anwendungsdomänen

Einmal im verwalteten Code angekommen wird der Host vermutlich mit der Erzeugung der Anwendungsdomänen beginnen wollen, die wiederum nach den bereits erwähnten Kriterien (Sicherheit, Isolierung, Ausladen und so weiter) den Anwendercode fahren sollen. Die meisten Hosts fahren den Anwendercode aus mehreren Gründen nicht in der Standarddomäne. Erstens lässt sich die Standarddomäne erst ausladen, wenn der Prozess terminiert. Außerdem ist es unter den Gesichtspunkten der Sicherheit und Isolierung nicht sinnvoll, Hostcode und Anwendercode in derselben Domäne zu betreiben.

Es gibt eine ganze Reihe von Properties, die der Host an einer Anwendungsdomäne ändern und damit praktisch alles kontrollieren kann, angefangen bei der Suche nach Baugruppen bis hin zur Verriegelung der DLLs, die sich dadurch nicht mehr dynamisch austauschen lassen. Diese Properties werden von der verwalteten Klasse System.AppDomainFlags definiert. Die beiden wichtigsten Properties dieser Klasse sind ApplicationBase und ConfigurationFile.

Wenn der Host plant, Baugruppen von der Festplatte zu laden, wird ApplicationBase praktisch immer gesetzt. ApplicationBase definiert das Stammverzeichnis der Anwendung. Die CLR schaut bei der Auflösung von Referenzen auf Baugruppen immer zuerst in der ApplicationBase nach. Durch die Definition einer ApplicationBase ermöglicht es der Host zudem, dass Baugruppen sozusagen das Privateigentum einer bestimmten Anwendung sein können. Private Baugruppen sind ein Schlüsselelement in der Erstellung von isolierten Anwendungen.

Das Property ConfigurationFile gibt eine XML-Datei an, in der Vorgaben und Werte für die Konfiguration der Anwendung liegen, die in der Domäne läuft. So können bei dieser Konfiguration zum Beispiel die Versionierungsregeln der Baugruppen festgelegt werden und Hinweise darauf gegeben werden, wie die "fernen" Typen zu suchen sind, die von der Anwendung benutzt werden.

Der folgende Beispielcode setzt ApplicationBase und ConfigurationFile, bevor er eine Domäne anlegt:

IDictionary properties = new Hashtable(2); 
// Properties des CLR-Laders 
properties.Add(AppDomainFlags.ApplicationBase,  
                  "c:\\program files\\myapp"); 
properties.Add(AppDomainFlags.ConfigurationFile,  
                  "c:\\program files\\myapp\myapp.config"); 
AppDomain appDomain = AppDomain.CreateDomain("MyDomain", 
                                             null, 
                                             null, 
                                             properties);

Eine Beschreibung der anderen Properties, die sich zur Konfiguration einer Anwendungsdomäne eignen, finden Sie in der Dokumentation der Klasse System.AppDomainFlags im .NET Framework SDK.

Laden und Ausführen von Anwendercode

Nachdem der Host nun eine oder mehrere Anwendungsdomänen angelegt und konfiguriert hat, ist die Ausführung von Anwendercode in diesen Domänen der nächste Schritt.

Der gesamte Code, der auf der CLR gefahren wird, muss zu einer Baugruppe gehören. Solch eine Baugruppe (Assembly) ist sozusagen die Verpackungseinheit für Typen und Ressourcen. Und sie ist die wichtigste Ausführungseinheit der CLR. Die Art und Weise, in der die Baugruppen zur Ausführung geladen werden, hängt von den speziellen Umständen im Host ab. Im Allgemeinen gibt es zwei Alternativen. Die erste Alternative besteht darin, vorkompilierte Baugruppen von der Festplatte zu laden. Das geschieht normalerweise mit den Methoden Assembly.Load, Assembly.LoadFrom und AppDomain.CreateInstance.

Die zweite Alternative ist die dynamische Erstellung von Baugruppen mit den Funktionen aus dem Namensraum System.Reflection.Emit. Das ASP.NET benutzt diese Funktionen, um die Baugruppen für die .aspx-Seiten einer Webanwendung dynamisch anzulegen. Diese Baugruppen lassen sich in einer gegebenen Domäne direkt ausführen und können anschließend weggeworfen werden. Natürlich bietet der Namensraum System.Reflection.Emit auch die Gelegenheit, die Baugruppen dauerhaft auf einer Festplatte zu speichern.

Auflösung von Referenzen auf Baugruppen

Die CLR befolgt bei der Auflösung von Referenzen auf andere Baugruppen einige wohldefinierte Regeln. Selbstverständlich gehört es zum üblichen Verfahren, in ApplicationBase nachzuschauen und es auch im globalen Baugruppencache zu versuchen. Unter bestimmten Umständen könnte es aber soweit kommen, dass diese Standardregeln nicht ausreichen. Besonders dann nicht, wenn der Host die Baugruppen mit Reflection.Emit dynamisch generiert. In diesem Fall kann es durchaus geschehen, dass sich keine passende dauerhaft gespeicherte Baugruppe finden lässt.

Die CLR ermöglicht den Eingriff in diesen Vorgang des Ladens der Klassen. Daher kann der Host seine eigenen Regeln für die Auflösung der Referenzen auf Baugruppen implementieren. Der Eingriff erfolgt mit Hilfe des Ereignisses TypeResolveEvent, das in der Klasse System.AppDomain definiert wird. Dieses Ereignis erfordert einen Delegierten des Typs ResolveEventHandler. ResolveEventHandler hat folgende Signatur:

public delegate Assembly ResolveEventHandler( 
   object sender, 
   EventArgs e);

Sollte die CLR mit ihren Standardverfahren keine passende Baugruppe finden, meldet sie dieses Ereignis und gibt in der Meldung die Identität der gesuchten Baugruppe bekannt (im EventArgs-Parameter). Ein Host, der solch eine Meldung erhält, kann die Referenz auf die Baugruppe mit allen Mitteln auflösen, die er für angemessen hält. Er könnte die Baugruppe dynamisch zusammenstellen, an einer bestimmten Stelle auf der Festplatte nachsehen und so weiter. Wichtig ist, dass er eine Instanz von System.Reflection.Assembly zusammenbauen kann, die vom Delegierten zurückgegeben wird. Der Beispielcode in Listing L3 zeigt, wie sich der Host als Empfänger der Ereignismeldung TypeResolveEvent anmeldet.
L3 Der Host meldet sich als Empfänger von TypeResolveEvent an.

Public class Host 
{ 
   private Assembly TLoadHandler(System.Object sender, EventArgs e) 
   { 
        // suche die Baugrupppe oder stelle eine zusammen 
        // und gib sie zurück. 
   } 
    public static void Main(string[] args) 
    { 
       Host host = new Host(); 
       AppDomain appDomain = AppDomain.CreateDomain("MyDomain", 
                                                    null, 
                                                    null, 
                                                    null); 
    // Installiere den Empfänger der Ereignisnachricht 
    ResolveEventHandlertrh= new ResolveEventHandler ( 
       host.TLoadHandler); 
    appDomain.AddOnTypeResolve(trh); 
    // starte die Anwendung und so weiter... 
}

Festlegung der Sicherheitsrichtlinien

Das Sicherheitssystem vom .NET Framework für den Codezugriff soll den Administratoren die Gelegenheit geben, möglichst präzise festzulegen, ob ein gegebenes Codestückchen auf eine bestimmte Ressource zugreifen darf oder nicht. Die Entscheidung darüber, was der fragliche Code tun darf, beruht auf bestimmten Eigenschaften des Codes selbst und hängt nicht vom mehr oder weniger zufällig ausgewählten Anwender ab, der den Code ausführen soll. Die wichtigste dieser Eigenschaften ist der "Beweis" (evidence). Zu solchen Beweisstücken zählen in diesem Sinne die Website oder die Zone, aus welcher der Code heruntergeladen wurde, oder die digitale Signatur des Herausgebers, der den Code veröffentlicht hat.

Wenn der Code geladen wird und gestartet werden soll, vergleicht das Sicherheitssystem für den Codezugriff das Beweisstück mit den definierten Rechten. Diese Rechte legen fest, welche Arbeiten der Code durchführen darf. So könnte der Code zum Bespiel die Erlaubnis für Lesezugriffe in einem bestimmten Teil des Dateisystems haben oder Schreibzugriffe auf ein bestimmtes Netzlaufwerk durchzuführen. Die spezielle Verknüpfung zwischen einem bestimmten Beweisstück und den daraus für den Code resultierenden Rechten wird vom Administrator oder vom Host festgelegt und Sicherheitsrichtlinie (security policy) genannt. So könnte der Administrator zum Beispiel in seinen Sicherheitsrichtlinien festlegen, dass der Code, der aus dem Intranet heruntergeladen wird, umfangreichere Rechte als der Code erhält, der aus dem Internet stammt (zum Beispiel das Recht für den Zugriff auf das Dateisystem).

Ein Host kann die Rechte, die dem Code gewährt werden, wenn dieser in einer Anwendungsdomäne des Hosts läuft, auf zwei Wegen beeinflussen. Erstens kann der Host das Beweisstück mit der Domäne selbst verknüpfen. Dann wird das Beweisstück in die Sammlung der Beweisstücke aufgenommen, die von den anderen in der Domäne laufenden Codeteilen stammen, bevor die Sicherheitsrichtlinien angewendet werden. Dieser zusätzliche Beweis ist besonders nützlich, wenn der Host ein Stückchen Information über die Umgebung beisteuern möchte, in welcher der Code läuft. Wenn der Host zum Beispiel eine Domäne startet, die mit einer bestimmten Website verknüpft ist, dann lässt sich der URL der Site auf der Domänenebene als Beweisstück einsetzen, um sicherzustellen, dass kein Code, der in dieser Domäne läuft, jemals höhere Rechte erhält als der Code, der von der Site stammt. Die Möglichkeit zur Vorlage eines Beweisstückchens auf Domänenebene setzt voraus, dass der Host selbst das Recht ControlEvidence erhält. Die folgenden Zeilen demonstrieren, wie ein Beweisstück auf der Ebene der Domäne geltend gemacht wird:

Using System.Security.Policy; 
Evidence     evidence      = new Evidence(); 
evidence.AddHost(new Url("http://www.somesite.com")); 
AppDomain appDomain = AppDomain.CreateDomain("MyDomain", 
                                             evidence, 
                                             null, 
                                             null);

Der zweite Weg zur Beeinflussung der Sicherheitsrichtlinien für den Codezugriff führt über die Vorgabe einer Sicherheitsrichtlinie auf der Ebene der Anwendungsdomäne. Im Sicherheitssystem für den Codezugriff gibt es auch das Konzept der Richtlinienstufe oder Richtlinienebene (policy level). Solch eine Richtlinienebene ist kurz gesagt einfach nur eine Größe, mit der sich eine Richtlinie verknüpft lässt.

Das .NET Framework kennt vier Richtlinienebenen: unternehmensweit, maschinenweit, anwenderbezogen und anwendungsdomänenbezogen.
Jede Richtlinienebene kann die Rechte, die eine Stufe über ihr vergeben wurden, weiter einschränken. Wenn aus den maschinenweiten Richtlinien zum Beispiel hervorgeht, dass der Code mit einem bestimmten starken Namen die Umgebungsvariablen auslesen darf, dann können die anwenderbezogene Richtlinien immer noch dafür sorgen, dass dem Code dieses Recht verwehrt wird. Eine gegebene Richtlinienebene kann niemals umfassendere Rechte vergeben, als die nächsthöhere Ebene gewährt hat.

Die Sicherheitsrichtlinien werden auf allen vier Ebenen angewendet. Aus den Ergebnissen aller Ebenen wird die Schnittmenge gebildet. Und was dabei an Rechten übrig bleibt, wird dem fraglichen Code gewährt (Mit anderen Worten: es bleiben die Rechte übrig, die von allen Ebenen gewährt werden.),
Die unternehmensweiten, maschinenweiten und anwenderbezogenen Richtlinien werden vom Administrator in den entsprechenden Verwaltungsprogrammen des .NET Frameworks festgelegt. Die anwendungsdomänenbezogenen Richtlinien legt der Host nach der Erstellung einer neuen Domäne durch den Aufruf von AppDomain.SetAppDomainPolicy fest.

Gerade der Umstand, dass die Richtlinien, die auf der Ebene der Anwendungsdomäne gelten, alle auf den höheren Ebenen vergebenen Rechte noch beschränken können, gibt dem Host eine umfassende Kontrolle über die Rechte, die dem Code in seiner Domäne gewährt werden. Diese umfassende Kontrolle wird in verschiedenen Situationen wichtig. So stellt der SQL Server zum Beispiel mit den Richtlinien für die Domänenebene sicher, dass nur solche Baugruppen laufen, die explizit im SQL-Katalog eingetragen wurden. Man kann sich nun gut vorstellen, dass ein Host dafür sorgen möchte, dass kein Code, der in seinem Auftrag läuft, Zugriff auf die Registrierdatenbank erhält oder eine Anwenderschnittstelle anzeigt, die den aktuellen Thread blockiert. Der Host muss selbst das Recht ControlEvidence haben, um die Richtlinien für die Ebene der Domänen festlegen zu können.

Der Beispielcode aus Listing L4 legt Richtlinien für die Ebene der Domänen fest, die in der Domäne nur die Ausführung von Baugruppen mit starken Namen zulassen.

L4 So werden die Richtlinien für die Ebene der Domänen festgelegt

Using System.Security: 
Using System.Security.Policy; 
Using System.Security.Permissions; 
// Stelle die Richtlinien zusammen. 
PolicyLevel pl = PolicyLevel.CreateAppDomainLevel(); 
// Lasse nur Baugruppen mit starken Namen zu 
// (mit s_somePublicKey) 
UnionCodeGroup snCG = new UnionCodeGroup(  
              new StrongNameMembershipCondition(new 
              StrongNamePublicKeyBlob( s_somePublicKey ), null, null ),  
  new PolicyStatement(new PermissionSet(PermissionState.Unrestricted))); 
pl.RootCodeGroup.AddChild(snCG); 
AppDomain appDomain = AppDomain.CreateDomain("MyDomain", 
                                             null, 
                                             null, 
                                             null); 
// Trage nun die neuen Richtlinien als Domänen- 
// richtlinien ein. 
appDomain.SetAppDomainPolicy(pl);

Das Ausladen von Anwendungsdomänen

Wie schon gesagt stellen Anwendungsdomänen in der CLR die Einheit für das Ausladen von Code dar. In der Klasse AppDomain aus dem Namensraum System gibt es eine statische Methode namens Unload, mit der ein Host die Ressourcen wieder ans System zurückgeben kann, die von einer bestimmten Domäne beansprucht werden.
Der Aufruf der Methode AppDomain.Unload führt sozusagen zu einer ehrenvollen Verabschiedung der betreffenden Domäne. Jeder Thread, der in dieser Domäne läuft, erhält eine ThreadAbort-Meldung und wird bis zur Domänengrenze abgewickelt. Außerdem werden keine neuen Threads mehr in die Domäne hineingelassen. Anschließend werden alle anwendungsdomänenspezifische Datenstrukturen freigegeben.

Wenn die Domäne Code aus einer domänenneutralen Baugruppe gefahren hat, werden die betreffenden Kopien der statischen Variablen und entsprechender CLR-Datenstrukturen freigegeben. Der Code der domänenneutralen Baugruppe bleibt aber im Prozess, bis der Prozess heruntergefahren wird. Es gibt keinen anderen Mechanismus zum Ausladen einer domänenneutralen Baugruppe außer dem Herunterfahren des Prozesses oder dem vollständigen Ausladen der CLR selbst, und zwar mit der Methode ICorRuntimeHost::Stop.

Durch den Aufruf von ICorRuntimeHost::Stop verschwindet die CLR aus dem Prozess. Alle Domänen werden heruntergefahren. Nach dem Aufruf der Stop-Methode lässt sich die CLR nicht erneut in den selben Prozess laden.

Fazit

Die CLR wurde so konzipiert, dass sie die verschiedensten Anwendungen fahren kann, angefangen bei herkömmlichen Anwendungen, die von der Shell gestartet werden, über Webanwendungen bis hin zu den gespeicherten Prozeduren, die von Datenbanken benutzt werden. Da alle Anwendungen derzeit noch einen Host brauchen, damit sie hochgefahren werden können, bietet das .NET Framework SDK eine Reihe von Schnittstellen an, mit denen sich solche Hosts entwickeln lassen. Das ist der Schlüssel für den nahtlosen Übergang zum .NET und für dessen weite Verbreitung.


Anzeigen: