All in Sight Development and Digital Life 2.0

6Apr/110

Code zur Laufzeit kompilieren

kmines

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;
        }