Mit Mono.Cecil .NET Assemblies modifizieren

IL-Modifikation mit Mono.Cecil

IL-Modifikation mit Mono.Cecil

Mit Mono.Cecil .NET Assemblies modifizieren

IL-Modifikation mit Mono.Cecil


Werkzeuge wie PostSharp erlauben es, nach dem eigentlichen Kompilieren Assemblies zu modifizieren. Dies ist ein möglicher Weg, performant aspektorientiert zu programmieren. Mit Mono.Cecil hat man ein öffentlich zur Verfügung stehendes API, um IL-Code zu lesen und zu modifizieren. Dieser Artikel beschreibt erste Schritte und die Anwendung anhand eines Beispiels.

Bei der Entwicklung mit dem .NET Framework kommen Entwickler um die IL, die „Intermediate Lan­guage“, auch „Common Intermediate Language“ (CIL) genannt, nicht herum. Jeglicher Programmcode, der in einer .NET-kompatiblen Sprache geschrieben wurde, wird vom Compiler in die IL übersetzt und in einer Assembly als deploybare Einheit gespeichert. Auf dem Zielsystem kommt eine erneute Kompilierung durch den Just-In-Time-(JIT-)Compiler zum Zug, der die IL-Instruktionen in einen ausführbaren und plattformspezifischen, sprich nativen Maschinencode übersetzt. Entwickler müssen den IL-Code also praktisch nie zwingend genauer anschauen. Dennoch zahlt sich eine nähere Betrachtung bereits nach kurzer Zeit aus, denn der zusätzliche Aufwand wird mit tiefen Einblicken in die Funktionsweise von .NET-Anwendungen belohnt.

Metaprogrammierung

Oft ist es wünschenswert, eine bestehende Assembly unabhängig vom Quellcode genauer zu untersuchen oder gar zu verändern. Dieser Artikel geht auf die Techniken ein, die es gibt, um Assemblies nach dem abgeschlossenen Build-Prozess zu modifizieren, und erläutert die Möglichkeiten, die sich daraus ergeben.

Es gibt einen Hauptgrund, warum die nachträgliche Codemodifizierung ein sehr beliebtes Werkzeug ist. In den meisten Fällen geht es darum, repetitive Codefragmente erst nach der Kompilierung und automatisiert in den Code einzubauen. Ein Beispiel ist Code für Logging oder Instrumentierung. Eine Applikation soll entwickelt und jeder Methodenaufruf in einer Log-Datei protokolliert werden. Der einfachste Ansatz wäre, den Logging-Code von Hand in jede Methode zu schreiben. Dies hat jedoch erhebliche Nachteile, da es den Code unnötig aufbläht und gern vergessen wird. Viel eleganter ist es, wenn durch einen automatisierten Prozess der Logging-Code nachträglich bei allen Methoden eingefügt wird. Mit diesem Ansatz erreicht der Entwickler die Welt der aspektorientierten Programmierung, in der durch Attribute Funktionalität zu bestehendem Code hinzugefügt wird. PostSharp ist zum Beispiel ein Framework, mit dem sich der oben erwähnte Anwendungsfall mit aspektorientierter Programmierung recht komfortabel lösen lässt [1].

Wenn Code bereits bestehenden modifiziert, wird das oft als Metaprogrammierung [2] bezeichnet. Das .NET Framework bietet hierzu verschiedene Möglichkeiten, durch die sich Konzepte der Metaprogrammierung umsetzen lassen. Die wichtigsten Ansätze sind:

  • System.Reflection

  • System.Reflection.Emit

  • CodeDOM

  • Expression Trees

  • Mono.Cecil

Reflection

Am bekanntesten ist Reflection. Mit den Klassen aus dem Namespace System.Reflection haben Entwickler die Möglichkeit, zur Laufzeit Assemblies zu laden, deren Inhalt anzuschauen und darin enthaltene Typen zu instanziieren. Reflection arbeitet dazu mit den Metadaten, die in der Assembly enthalten sind. Das klassische Beispiel ist die Benutzung von Typen, die sich in projektfremden Assemblies befinden, die erst zu Laufzeit bekannt sind. Mit Reflection können Assemblies zum Beispiel aus dem Dateisystem geladen und Typen, die darin definiert sind, instanziiert und im Programm verwendet werden.

Reflection.Emit

Die Klassen aus System.Reflection.Emit bieten die Möglichkeit, zur Laufzeit Code zu generieren, indem wir mit der Emit()-Methode IL-Anweisungen schreiben. Die Einschränkung dabei ist, dass nur neuer Code erzeugt werden kann, eine Veränderung von bestehendem Programmcode ist nicht direkt möglich – doch Mono.Cecil hilft hier aus.

CodeDOM

Beim CodeDOM handelt es sich um das Document Object Model des .NET Frameworks. Analog zu anderen Technologien, wie zum Beispiel HTML oder JavaScript, lässt sich auch .NET-Code in Form eines Objektmodells darstellen. Das CodeDOM erlaubt Entwicklern, zur Laufzeit einen sprachunabhängigen Codegraphen zu erzeugen, der anschließend über Generatoren in eine der .NET-Sprachen übersetzt werden kann. Das Resultat der Übersetzung ist dann zum Beispiel C#-Code. Das CodeDOM ist seit Version 1.0 fixer Bestandteil des .NET Frameworks. Mittlerweile ist es aber ein wenig in den Hintergrund gerückt, da mit dem Aufkommen von Language Integrated Query (LINQ) Unterstützung für Expression Trees in das Framework eingebaut wurde.

Expression Trees

Expressions werden benutzt, um Code in Form von Datenstrukturen abzubilden. Zur Laufzeit können Expressions verschachtelt und zu einem Expression Tree zusammengebaut werden, der anschließend kompiliert und ausgeführt werden kann. Expression Trees werden intensiv...