• [C#] Kompilierung zur Laufzeit

    In diesem Tutorial erkläre ich dir, wie du schnell und einfach per Reflection eigene *.exe Dateien zu Laufzeit kompilieren kannst. Du fragst dich jetzt sicherlich, was das für einen Nutzen hat. Ein kleines Beispiel, um es verständlicher zu machen. Du hast ein Programm programmiert, womit man Autostart CDs erstellen kann. Der Benutzer kann sich die Oberfläche gestalten, Buttons platzieren etc. . Er möchte es gerne verbreiten, jetzt kommt das Reflection ins Spiel. Du kannst jetzt nämlich eine *.exe Datei aus deinem Programm generieren lassen, das genau das alles beinhaltet, was der Benutzer in deinem Programm erstellt hat. Wir werden in diesem Tutorial gemeinsam zwei Projekte erstellen. Das erste wird unser Hauptprogramm sein, womit wir unsere *.exe Datei erstellen lassen.
    Das zweite wird unser generiertes Programm sein - eine einfache MessageBox, die einen Namen ausgibt. Dies gilt nur für Lernzwecke. Gleich einen Taschenrechner oder desgleichen sollte man erst erstellen lassen, wenn man das dahinter steckende Prinzip verstanden hat. Eins vorweg: ich erkläre hier nicht Schritt für Schritt, wie man ein Projekt erstellt und/oder wo sich einzelne Menüpunkte befinden. Lass uns erstmal gemeinsam unser Hauptprojekt erstellen. Dazu erstellen wir eine ganz normale Windows Forms Anwendung, jetzt müssen wir die Anwendung etwas gestalten, damit wir später per Button unsere *.exe Datei erstellen können.
    Lass uns jetzt ein Label auf die Form legen, als Text nehmen wir Folgendes: „Mein Name lautet:“ und platzieren ihn schön oben links auf unserer Form. Als Nächstes platzieren wir eine Textbox, die setzten wir gleich neben dem Label und als Name geben wir ihm „tbNameStr“. Zum Schluss setzen wir einen Button gleich unter dem Label und der Textbox. Als Text setzen wir ihm „Generiere *.exe“. Nun sollte unser Projekt wie folgt aussehen:



    Gehen wir nun ans Eingemachte. Ich beginne zuerst damit folgende usings einzutragen,

    Code :
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    
    using System;
    using System.Drawing;
    using System.Collections;
    using System.ComponentModel;
    using System.Windows.Forms;
    using System.Data;
    using System.IO;
    using System.Diagnostics;
    using System.CodeDom.Compiler;
    using System.Reflection;
    using Microsoft.CSharp;

    So sollte es nun aussehen. Und als Nächstes müssen wir eine Klasse programmieren, die unseren Code später kompiliert und bei eventuellen Fehlern diese ausspuckt.

    Wir nennen die Klasse „Builder“ und erstellen dann eine Funktion. Sie wird die ganzen Einstellungen für die Kompilierung erzeugen.

    public bool Generate(out string errString)

    Lass und jetzt an die ganzen Compilereinstellungen rangehen!

    Wir erstellen zwei Instanzen: von ICodeCompiler und CompilerParameters

    Code :
    1
    2
    
    ICodeCompiler compiler = new CSharpCodeProvider().CreateCompiler();
    CompilerParameters compArgs = new CompilerParameters();

    Jetzt wollen wir die Argumente übergeben:

    //hier sagen wir, dass es eine *.exe Datei werden soll
    compArgs.GenerateExecutable = true;

    //hier sagen wir ihm, wie die Datei heißen soll
    compArgs.OutputAssembly = "generiert.exe";

    //und zuletzt sagen wir ihm, dass wir keine Konsole haben wollen
    compArgs.CompilerOptions = "/target:winexe";

    Nun werden alle global vorhandenen assemblies referenziert:

    foreach(Assembly asm in AppDomain.CurrentDomain.GetAssemblies())
    compArgs.ReferencedAssemblies.Add(asm.Location);

    Danach kommt das Wichtigste: wir übergeben ihm die Argumente und den Sourcecode

    CompilerResults ret = compiler.CompileAssemblyFromSource(compArgs,
    SourcecodeZ);

    Nun wollen wir was für die Fehlererfassung tun:

    Code :
    1
    2
    3
    4
    5
    6
    
    if (ret.Errors.Count > 0)
    {
    errString = ret.Errors[0].ToString(); // liefert den ersten Fehler
    zurück
    return false; // ERROR
    }

    Wir erstellen den Event, wenn man auf den Button „Generiere *.exe“ klickt:

    Den Sourcecode übergeben wir dem StringBuilder, somit ist es einfacher mit dem Sourcecode herumzuspielen.

    Code :
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    
    sb.Append("using System;\r\n");
    sb.Append("using System.Windows.Forms;\r\n");
    sb.Append("public class MainClass\r\n");
    sb.Append("{\r\n");
    sb.Append("public static void Main()\r\n");
    sb.Append("{\r\n");
    sb.Append("MessageBox.Show(\"Mein Name lautet: \" + \"");
    sb.Append(tbNameStr.Text);
    sb.Append("\", \"Generierte *.exe\", MessageBoxButtons.OK,
    MessageBoxIcon.Information);");
    sb.Append("}\r\n");
    sb.Append("}\r\n");

    Danach übergeben wir den StringBuilder Sourcecode an unseren string Sourcecode

    Sourcecode = sb.ToString();

    Dann wird eine Instanz von unserer Klasse „Builder“ erstellt:
    Builder ab = new Builder();

    Des Weiteren erstellen wir einen string error damit wir später dort unsere Fehlermeldungen darstellen können.
    Kommen wir nun zu dem Kompilieren. Falls die Kompilierung nicht erfolgreich abläuft, wird der Fehler angezeigt. Ansonsten wird die Datei kompiliert:

    Code :
    1
    2
    3
    4
    5
    
    if (!ab.Generate(out error))
    {
    MessageBox.Show(error);
    return;
    }

    Jetzt ist die Datei erstellt, öffne diese und bestaune deine generierte MessageBox.

    So sollte das Ergebnis nun aussehen:



    Das war's nun mit meinem Tutorial. Ich hoffe, es hat dir gefallen. Wenn du Fragen haben solltest, melde dich unter webmaster@texturenland.de. Wie du siehst, ist das Thema gar nicht so kompliziert.

    Aufgrund einiger Nachfragen, der Sourcecode zu dem Projekt:
    http://www.texturenland.de/downloads/Hauptprojekt.zip