Feedback

C# - Code zur Laufzeit kompilieren

Veröffentlicht von am 15.04.2011
(1 Bewertungen)
Manchmal kann es notwendig sein, dass man einen bestimmten Code zur explizit zur Laufzeit kompiliert. Man könnte sich daraus zum Beispiel eine kleine Shell bauen. Wie ihr das bewerkstelligen könnt, zeige ich euch heute.
Ihr braucht dafür einen MemoryStream, einer StreamWriter und den Namensraum System.Reflection, sowie System.CodeDom.Compiler.
Das ganze funktioniert folgendermaßen: Ihr erzeugt einen Quellcode der durch die gleich gezeigt Methode im Speicher kompiliert und ausgeführt wird. Um euch die Arbeit zu erleichtern, könnt ihr schon vornherin ein paar Definitionen vornehmen, wie z.b. die zu verwendenden Namensräume, den Funktionsrumpf etc.. In meinem Snippet habe ich ein WindowsForms-Projekt erstellt, daher benutze ich bewusst die MessageBox.
/// <summary>
/// Führt die Compilierung mit dem angegebenen Code aus
/// </summary>
private void Compile()
{
    String InputCode = String.Empty;
    //Unser TestCode, in dem Wir ein MessageBox aufrufen
    InputCode = "MessageBox.Show((1 + 2 + 3).ToString());";
 
    System.Reflection.Assembly Assembly = CompileCode(InputCode);
    //Compilefehler abfangen
    if (Assembly == null) return;
    object Temp = Assembly.CreateInstance("RunTimeCompiler.Test");
    //Fehler bei Instanzenerzeugung
    if (Temp == null) return;
    Type RefType = Temp.GetType();
    //Aufzurufende Methode auswählen, in unserem Fall heißt die Funktion Ergebnis
    System.Reflection.MethodInfo MethodInfo = RefType.GetMethod("Ergebnis");
    //Methode aufrufen, in unserem Fall haben wir in der Funktion Ergebnis keine Parameter. Andernfalls müssten diese als Object-Array angegeben Werden
    MethodInfo.Invoke(Temp, new object[] { });
}
/// <summary>
///
/// </summary>
/// <param name="InputCode"></param>
/// <returns></returns>
public static System.Reflection.Assembly CompileCode(string InputCode)
{
    System.CodeDom.Compiler.CodeDomProvider CodeDomProvider = System.CodeDom.Compiler.CodeDomProvider.CreateProvider("CSharp");
    //Parameter für die Compilierung, wie die einzubindenen Bibliotheken usw.
    System.CodeDom.Compiler.CompilerParameters CompilerParameters = new System.CodeDom.Compiler.CompilerParameters();
    CompilerParameters.ReferencedAssemblies.Add("System.dll");
    CompilerParameters.ReferencedAssemblies.Add("System.Windows.Forms.dll");
    CompilerParameters.CompilerOptions = "/t:library";
    CompilerParameters.GenerateInMemory = true;
 
    //Über den StringBuilder wird der Code zusammengesetzt
    StringBuilder Temp = new StringBuilder();
    Temp.AppendLine(@"using System;");
    Temp.AppendLine(@"using System.Windows.Forms;");
    Temp.AppendLine(@"namespace RunTimeCompiler{");
    Temp.AppendLine(@"public class Test{");
    Temp.AppendLine(@"public void Ergebnis(){");
 
    Temp.AppendLine(InputCode);
    Temp.AppendLine(@"}}}");
 
    //Compilieren
    System.CodeDom.Compiler.CompilerResults CompilerResults = CodeDomProvider.CompileAssemblyFromSource(CompilerParameters, Temp.ToString());
    //Auf CompilerFehler prüfen
    if (CompilerResults.Errors.Count > 0)
    {
        MessageBox.Show(CompilerResults.Errors[0].ErrorText, "Fehler bei Laufzeitkompilierung", MessageBoxButtons.OK, MessageBoxIcon.Error);
        return null;
    }
    //Rückgabe der compilierten Assembly
    return CompilerResults.CompiledAssembly;
}

2 Kommentare zum Snippet

c#-Hacker schrieb am 24.11.2013:
Weißt du vlt auch wie man Code mit Rückgabewert ausführen kann und
dann auch ohne MessageBox etc an den Rückgabewert kommt ???

Vielen Dank
C#-Hacker
Legion schrieb am 24.11.2013:
Dazu musst du das Verhalten der Methode ändern. Die Zielmethode "Ergebnis" hat in dem Snippet den Rückgabewert void.
Einfach void in z.b. int ändern. "Temp.AppendLine(@"public int Ergebnis(){");"

Danach den Code innerhalb der Compile Methode anpassen, in z.b. folgendes:
InputCode = "return 1 + 2 + 3;";

Das Ergebnis kannst du dann durch den Rückgabewert des Invoke herausfinden. Dieser ist vom Typ object. Muss also noch gecastet/konvertiert werden.
int theResult = Convert.ToInt32(MethodInfo.Invoke(Temp, new object[] { }));

Gruß, Chris
 

Logge dich ein, um hier zu kommentieren!