Der verwaltete Ausführungsprozess
Der verwaltete Ausführungsprozess schließt die folgenden Schritte ein, die weiter unten in diesem Thema ausführlich erläutert werden:
- Auswählen eines Compilers. Um die Vorzüge der Common Language Runtime nutzen zu können, müssen Sie mindestens einen Sprachcompiler mit Unterstützung der Laufzeit verwenden.
- Kompilieren Sie Ihren Code in die Zwischensprache. Beim Kompilieren wird der Quellcode in CIL-Code (Common Intermediate Language) übersetzt, und die erforderlichen Metadaten werden generiert.
- Kompilieren Sie CIL-Code in nativen Code. Zur Ausführungszeit übersetzt ein JIT-Compiler (Just-In-Time) den CIL-Code in nativen Code. Während dieser Kompilierung muss der Code eine Überprüfung von CIL-Code und Metadaten durchlaufen, bei der ermittelt wird, ob der Code als typsicher gelten kann.
- Ausführen von Code. Die Common Language Runtime stellt eine Infrastruktur bereit, die die Ausführung des Codes sowie von Diensten ermöglicht, die während der Ausführung verwendet werden können.
Auswählen eines Compilers
Um die Vorteile der Common Language Runtime (CLR) nutzen zu können, müssen Sie mindestens einen Sprachcompiler für die Laufzeit verwenden, z. B. Compiler für Visual Basic, C#, Visual C++, F# oder einen der vielen Compiler von Drittanbietern, wie etwa einen Eiffel-, Perl- oder COBOL-Compiler.
Da es sich um eine mehrsprachige Ausführungsumgebung handelt, unterstützt die Laufzeit eine große Zahl von Datentypen und Sprachfeatures. Die verfügbaren Laufzeitfunktionen hängen vom verwendeten Sprachcompiler ab. Mithilfe dieser Funktionen entwerfen Sie den Code. Die vom Code zu verwendende Syntax wird durch den Compiler, nicht durch die Laufzeit festgelegt. Wenn eine Komponente für in anderen Sprachen geschriebene Komponenten vollständig verwendbar sein soll, dürfen die exportierten Typen der Komponente ausschließlich Sprachfunktionen verfügbar machen, die in der Common Language Specification (CLS) enthalten sind. Sie können das CLSCompliantAttribute -Attribut verwenden, um sicherzustellen, dass der Code CLS-kompatibel ist. Weitere Informationen finden Sie unter Sprachunabhängigkeit und sprachunabhängige Komponenten.
Kompilieren in CIL
Beim Kompilieren in verwalteten Code übersetzt der Compiler den Quellcode in CIL (Common Intermediate Language). Dabei handelt es sich CPU-unabhängige Anweisungen, die auf einfache Weise in nativen Code konvertiert werden können. CIL enthält Anweisungen zum Laden, Speichern, Initialisieren und Aufrufen von Methoden für Objekte sowie Anweisungen für arithmetische und logische Operationen, Ablaufsteuerung, direkten Speicherzugriff, Ausnahmebehandlung und andere Operationen. Bevor Code ausgeführt werden kann, muss der CIL-Code in CPU-spezifischen Code konvertiert werden, wofür meist eine JIT-Kompilierung (Just-In-Time) verwendet wird. Da die Common Language Runtime für jede unterstützte Computerarchitektur mindestens einen JIT-Compiler bereitstellt, können CIL-Anweisungen unter jeder unterstützten Architektur JIT-kompiliert und ausgeführt werden.
Wenn ein Compiler CIL erstellt, generiert er auch Metadaten. Metadaten beschreiben im Code vorhandene Typen und enthalten die Definition jedes Typs, die Signaturen der Member jedes Typs, die Member, auf die der Code verweist, sowie weitere Daten, die von der Laufzeit zur Ausführungszeit verwendet werden. CIL-Code und Metadaten befinden sich in einer PE-Datei (Portable Executable), die auf dem traditionell für ausführbare Inhalte verwendeten, veröffentlichten PE-Dateiformat und COFF (Common Object File Format) von Microsoft beruht und dieses erweitert. Mit diesem für CIL oder nativen Code sowie für Metadaten ausgelegten Dateiformat kann das Betriebssystem Common Language Runtime-Images erkennen. Da in der Datei Metadaten und CIL gleichzeitig vorhanden sind, kann sich der Code selbst beschreiben. Dies bedeutet, dass keine Typbibliotheken oder IDL (Interface Definition Language, Schnittstellendefinitionssprache) erforderlich sind. Die Laufzeit sucht und extrahiert die Metadateien aus der Datei je nach Bedarf während der Ausführung.
Kompilieren von CIL-Code in nativen Code
Bevor CIL-Code (Common Language Runtime) ausgeführt werden kann, muss er in der Common Language Runtime in nativen Code für die Architektur des Zielcomputers kompiliert werden. .NET bietet zwei Möglichkeiten zum Ausführen dieser Konvertierung:
- Ein Just-in-time-Compiler (JIT) für .NET.
- Ngen.exe (Native Image Generator)
Kompilierung durch den JIT-Compiler
Bei der JIT-Kompilierung wird CIL-Code zur Anwendungslaufzeit auf Abruf in nativen Code konvertiert, sobald der Inhalt einer Assembly geladen und ausgeführt wird. Da die Common Language Runtime für jede unterstützte CPU-Architektur einen JIT-Compiler bereitstellt, können bei der Entwicklung verschiedene CIL-Assemblys erstellt werden, die JIT-kompiliert und auf unterschiedlichen Computern mit abweichender Architektur ausgeführt werden können. Wenn der verwaltete Code jedoch plattformspezifische systemeigene APIs oder eine plattformspezifische Klassenbibliothek aufruft, kann er nur unter diesem Betriebssystem ausgeführt werden.
Bei der JIT-Kompilierung wird berücksichtigt, dass ein Teil des Codes bei der Ausführung möglicherweise nicht aufgerufen wird. Statt für das Konvertieren des gesamten CIL-Codes einer PE-Datei in nativen Code Zeit und Speicherplatz zu beanspruchen, wird der CIL-Code zur Ausführungszeit nach Bedarf konvertiert. Der resultierende native Code wird im Arbeitsspeicher gespeichert, sodass bei nachfolgenden Aufrufen im Kontext dieses Prozesses darauf zugegriffen werden kann. Das Ladeprogramm erstellt einen Stub und fügt diesen an jede Methode des Typs an, wenn dieser Typ geladen und initialisiert wird. Beim ersten Aufruf der Methode übergibt der Stub die Steuerung an den JIT-Compiler, der den CIL-Code für diese Methode in nativen Code konvertiert und den Stub so ändert, dass er direkt auf den generierten nativen Code verweist. Nachfolgende Aufrufe der JIT-kompilierten Methode setzen deshalb direkt beim systemeigenen Code an.
Codegenerierung bei der Installation mithilfe von NGen.exe
Da der JIT-Compiler den CIL-Code einer Assembly in nativen Code konvertiert, treten zur Laufzeit unweigerlich Leistungseinbußen auf, wenn einzelne in dieser Assembly definierte Methoden aufgerufen werden. In den meisten Fällen sind diese Leistungseinbußen hinnehmbar. Eine größere Rolle spielt jedoch, dass der vom JIT-Compiler generierte Code an den Prozess gebunden ist, durch den die Kompilierung ausgelöst wurde. Er kann also nicht für mehrere Prozesse verwendet werden. Damit der generierte Code für mehrere Aufrufe einer Anwendung oder mehrere Prozesse verwendet werden kann, die eine Gruppe von Assemblys gemeinsam nutzen, unterstützt die Common Language Runtime einen vorzeitigen Kompilierungsmodus. Dieser vorzeitige Kompilierungsmodus verwendet den Native Image Generator (Ngen.exe), um CIL-Assemblys ähnlich wie der JIT-Compiler in nativen Code zu konvertieren. Die Ausführung von Ngen.exe unterscheidet sich jedoch auf drei Weisen von der Ausführung des JIT-Compilers:
- Die Konvertierung von CIL-Code in nativen Code wird vor und nicht während der Ausführung der Anwendung durchgeführt.
- Es werden jeweils ganze Assemblys und nicht einzelne Methoden kompiliert.
- Der generierte Code im Cache für systemeigene Abbilder wird als Datei auf dem Datenträger beibehalten.
Codeüberprüfung
Beim Kompilieren in nativen Code muss der CIL-Code eine Überprüfung durchlaufen. Dies ist nicht erforderlich, wenn Administratoren bzw. Administratorinnen Sicherheitsrichtlinien erstellt haben, mit denen die Überprüfung des Codes umgangen werden kann. CIL-Code und Metadaten werden daraufhin überprüft, ob der Code typsicher ist, d. h., ob er nur auf Speicherorte zugreift, für die ihm Zugriff gewährt wurde. Typsicherheit ermöglicht das Isolieren von Objekten voneinander und trägt zum Schutz dieser Objekte vor unabsichtlicher oder böswilliger Beschädigung bei. Typsicherheit bietet außerdem die Gewissheit, dass Sicherheitsbeschränkung für Code zuverlässig erzwungen werden können.
Für die Laufzeit ist erforderlich, dass die folgenden Aussagen auf überprüfbar typsicheren Code zutreffen:
- Ein Verweis auf einen Typ ist vollständig kompatibel mit dem Typ, auf den verwiesen wird.
- Für ein Objekt werden nur angemessen definierte Operationen aufgerufen.
- Identitäten sind, was sie von sich behaupten.
Während der Überprüfung von CIL-Code wird versucht sicherzustellen, dass dieser nur über ordnungsgemäß definierte Typen auf Speicherorte zugreifen und Methoden aufrufen kann. Code muss z. B. die Überlastung von Speicherorten beim Zugriff auf Felder eines Objekts verhindern. Der Code wird außerdem daraufhin überprüft, ob der CIL-Code fehlerfrei generiert wurde, da fehlerhafter CIL-Code zu einer Verletzung der Regeln für die Typsicherheit führen kann. Bei der Überprüfung wird ein genau definierter Abschnitt typsicheren Codes übergeben, und es wird ausschließlich Code übergeben, der typsicher ist. Aufgrund einiger Einschränkungen während der Überprüfung ist es jedoch möglich, dass ein Teil des typsicheren Codes die Überprüfung nicht besteht. Zudem kann in einigen Sprachen kein überprüfbar typsicherer Code erstellt werden. Wenn die Sicherheitsrichtlinien typsicheren Code erfordern, aber der Code die Überprüfung nicht besteht, wird bei der Ausführung des Codes eine Ausnahme ausgelöst.
Ausführen des Codes
Die Common Language Runtime stellt eine Infrastruktur bereit, die die verwaltete Ausführung des Codes sowie von Diensten ermöglicht, die während der Ausführung verwendet werden können. Vor Ausführung einer Methode muss diese in prozessorspezifischen Code kompiliert werden. Jede Methode, für die CIL-Code generiert wurde, wird beim ersten Aufruf JIT-kompiliert und anschließend ausgeführt. Beim nächsten Ausführen der Methode wird der vorhandene JIT-kompilierte, systemeigene Code ausgeführt. Das JIT-Kompilieren und anschließende Ausführen des Codes wird so lange wiederholt, bis die Ausführung vollständig abgeschlossen ist.
Verwaltetem Code stehen während der Ausführung verschiedene Dienste zur Verfügung, z. B. Garbage Collection, Sicherheit, Interoperabilität mit nicht verwaltetem Code, sprachübergreifende Debugunterstützung sowie verbesserte Unterstützung der Bereitstellung und Versionserstellung.
Unter Microsoft Windows Vista sucht das Ladeprogramm des Betriebssystems nach verwalteten Modulen, indem ein Bit im COFF-Header überprüft wird. Das festgelegte Bit steht dabei für ein verwaltetes Modul. Wenn das Ladeprogramm verwaltete Module erkennt, lädt es mscoree.dll. _CorValidateImage
und _CorImageUnloading
benachrichtigen das Ladeprogramm, wenn die Images der verwalteten Module geladen und entladen werden. _CorValidateImage
führt die folgenden Aktionen aus:
- Überprüfen, dass es sich um gültigen verwalteten Code handelt
- Ändern des Einstiegspunktes im Image in einen Einstiegspunkt zur Laufzeit
Bei 64-Bit-Versionen von Windows ändert _CorValidateImage
das Image im Arbeitsspeicher vom Format PE32 in das Format PE32+.