Xmas
2003-02-17, 13:42:14
Ich habe mal in C# testweise ein kleines Programm geschrieben, das in einer Textbox die Eingabe von c#-Code erlaubt, der das Programm selbst beeinflussen soll.
Dazu wird der eingegebene Text an ein CSharp-Compiler-Objekt übergeben und im Speicher in eine Assembly kompiliert. Dann lege ich eine Instanz des ersten in der Assembly definierten Typs an und rufe testweise die Methode "ChangeText" für diese auf.
Beim Kompilieren wird die gerade ausgeführte Assembly referenziert, so dass der "Scriptcode" Zugriff auf alle public-Elemente hat. Im Beispiel kann z.B. die Methode ChangeText auf eine statische Methode ChangeText( string ) zugreifen, um den Text eines Labels zu verändern.
Der Code sieht folgendermaßen aus (Auszug, ist ein Button-Click Handler):
using Microsoft.CSharp;
using System.CodeDom.Compiler;
using System.Reflection;
// [...]
{
CSharpCodeProvider codeProv = new CSharpCodeProvider();
ICodeCompiler icodecomp = codeProv.CreateCompiler();
CompilerParameters options = new CompilerParameters();
options.GenerateExecutable = false;
options.GenerateInMemory = true;
options.OutputAssembly = "Test";
options.ReferencedAssemblies.Add( Assembly.GetExecutingAssembly().Location );
CompilerResults results =
icodecomp.CompileAssemblyFromSource( options, textBox1.Text );
if ( results.Errors.HasErrors )
{
label1.Text = results.Errors[0].ErrorText;
return;
}
label1.Text = "";
object obj = results.CompiledAssembly.CreateInstance( results.CompiledAssembly.GetTypes()[0].FullName );
if( obj == null ) return;
obj.GetType().GetMethod( "ChangeText" ).Invoke( obj, null );
//Delegate mydel = MyDelegate.CreateDelegate( typeof(MyDelegate), obj, "ChangeText" );
//mydel.DynamicInvoke( null );
}
Mal abgesehen von der mangelhaften Fehlerbehandlung, ist der Code so "sauber"? Gibt es vielleicht bessere Wege eine Assembly zu erstellen und an die darin definierten Methoden ranzukommen?
Wie könnte ich den Code am besten "selbstregistrierend" machen, d.h. dass ich einen Event-Handler schreibe und dieser möglichst einfach an das vorgesehene Ereignis gebunden wird? Ich könnte ja im Scriptcode eine Methode Init() verwenden, die die entsprechenden Methoden an Ereignisse bindet und zu Anfang aufgerufen wird.
Und die letzten beiden, auskommentierten Zeilen zeigen ein Problem. Der Code funktioniert so, was ich aber haben will wäre eher sowas:
MyDelegate mydel = MyDelegate.CreateDelegate( typeof(MyDelegate), obj, "ChangeText" );
mydel();
CreateDelegate gibt aber nur eine Instanz vom Typ System.Delegate zurück, nicht von MyDelegate. Ist da eine Umwandlung möglich, so dass ich obj.ChangeText in ein MyDelegate bekomme?
Dazu wird der eingegebene Text an ein CSharp-Compiler-Objekt übergeben und im Speicher in eine Assembly kompiliert. Dann lege ich eine Instanz des ersten in der Assembly definierten Typs an und rufe testweise die Methode "ChangeText" für diese auf.
Beim Kompilieren wird die gerade ausgeführte Assembly referenziert, so dass der "Scriptcode" Zugriff auf alle public-Elemente hat. Im Beispiel kann z.B. die Methode ChangeText auf eine statische Methode ChangeText( string ) zugreifen, um den Text eines Labels zu verändern.
Der Code sieht folgendermaßen aus (Auszug, ist ein Button-Click Handler):
using Microsoft.CSharp;
using System.CodeDom.Compiler;
using System.Reflection;
// [...]
{
CSharpCodeProvider codeProv = new CSharpCodeProvider();
ICodeCompiler icodecomp = codeProv.CreateCompiler();
CompilerParameters options = new CompilerParameters();
options.GenerateExecutable = false;
options.GenerateInMemory = true;
options.OutputAssembly = "Test";
options.ReferencedAssemblies.Add( Assembly.GetExecutingAssembly().Location );
CompilerResults results =
icodecomp.CompileAssemblyFromSource( options, textBox1.Text );
if ( results.Errors.HasErrors )
{
label1.Text = results.Errors[0].ErrorText;
return;
}
label1.Text = "";
object obj = results.CompiledAssembly.CreateInstance( results.CompiledAssembly.GetTypes()[0].FullName );
if( obj == null ) return;
obj.GetType().GetMethod( "ChangeText" ).Invoke( obj, null );
//Delegate mydel = MyDelegate.CreateDelegate( typeof(MyDelegate), obj, "ChangeText" );
//mydel.DynamicInvoke( null );
}
Mal abgesehen von der mangelhaften Fehlerbehandlung, ist der Code so "sauber"? Gibt es vielleicht bessere Wege eine Assembly zu erstellen und an die darin definierten Methoden ranzukommen?
Wie könnte ich den Code am besten "selbstregistrierend" machen, d.h. dass ich einen Event-Handler schreibe und dieser möglichst einfach an das vorgesehene Ereignis gebunden wird? Ich könnte ja im Scriptcode eine Methode Init() verwenden, die die entsprechenden Methoden an Ereignisse bindet und zu Anfang aufgerufen wird.
Und die letzten beiden, auskommentierten Zeilen zeigen ein Problem. Der Code funktioniert so, was ich aber haben will wäre eher sowas:
MyDelegate mydel = MyDelegate.CreateDelegate( typeof(MyDelegate), obj, "ChangeText" );
mydel();
CreateDelegate gibt aber nur eine Instanz vom Typ System.Delegate zurück, nicht von MyDelegate. Ist da eine Umwandlung möglich, so dass ich obj.ChangeText in ein MyDelegate bekomme?