PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : 'Scripting' mit C#


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?

Demirug
2003-02-17, 14:18:50
Das Verfahren wie du dir den Code über die Reflektion holst ist so richtig. Allerdings ist die GetMethod + Invoke Methode sehr lahm. Wenn man denn Code öfter aufruft ist das Verfahren mit dem Delegate schneller. Aus Performancesgründen ist aber die Benutzung eines Interfaces die beste Wahl.

Die Selbstregistrierung macht man am besten mit einer statischen Funktion oder falls man mehrer Objekte registrieren möchte mit einer eigenen Init-Klasse welche dann am besten ein Interface dafür hat. Mit Attributen kann man aber auch so einiges anstellen. Es hängt also sehr spezifisch vom genauen Anwednungsfall ab was man am besten macht.

Zu deinem Delegate Problem. Schon mal einen Cast versucht? Das sollte eigentlich gehen.

Xmas
2003-02-17, 14:49:03
Originally posted by Demirug
Das Verfahren wie du dir den Code über die Reflektion holst ist so richtig. Allerdings ist die GetMethod + Invoke Methode sehr lahm. Wenn man denn Code öfter aufruft ist das Verfahren mit dem Delegate schneller. Aus Performancesgründen ist aber die Benutzung eines Interfaces die beste Wahl.
GetMethod + Invoke war auch nur ein Test, prinzipiell wollte ich Delegates verwenden. Würde das Interface nicht voraussetzen, dass die entsprechenden Methoden bereits im Voraus bekannt sind? Wieso wären Interfaces schneller, und wie groß ist der Unterschied?

Die Selbstregistrierung macht man am besten mit einer statischen Funktion oder falls man mehrer Objekte registrieren möchte mit einer eigenen Init-Klasse welche dann am besten ein Interface dafür hat. Mit Attributen kann man aber auch so einiges anstellen. Es hängt also sehr spezifisch vom genauen Anwednungsfall ab was man am besten macht.
Da fällt mir gerade ein dass man ja den Konstruktor als Init-Funktion nehmen kann, der wird ja durch CreateInstance auch aufgerufen. Ein static-Konstruktor wäre vielleicht noch besser.
Ich bin mir ehrlich gesagt noch gar nicht sicher, ob ich den Code selbstregistrierend machen soll oder per "Eigenschaften"-Fenster eines Objekts den User die Ereignishandler auswählen lasse.

Zu deinem Delegate Problem. Schon mal einen Cast versucht? Das sollte eigentlich gehen.
:bonk:
Manchmal sieht man den Wald vor lauter Bäumen nicht...

Demirug
2003-02-17, 15:18:54
Originally posted by Xmas
GetMethod + Invoke war auch nur ein Test, prinzipiell wollte ich Delegates verwenden. Würde das Interface nicht voraussetzen, dass die entsprechenden Methoden bereits im Voraus bekannt sind? Wieso wären Interfaces schneller, und wie groß ist der Unterschied?

Interfaces sind nicht ganz doppelt so schnell wie delegates. Der Grund dafür ist das Interfaceaufrufe für die CLR einfach weniger aufwand darstellen aus Delegates.

Da fällt mir gerade ein dass man ja den Konstruktor als Init-Funktion nehmen kann, der wird ja durch CreateInstance auch aufgerufen. Ein static-Konstruktor wäre vielleicht noch besser.
Ich bin mir ehrlich gesagt noch gar nicht sicher, ob ich den Code selbstregistrierend machen soll oder per "Eigenschaften"-Fenster eines Objekts den User die Ereignishandler auswählen lasse.

Ja wenn deine InitFunktion ohne zusätzliche Infos auskommt ist der statische Konstruktor der beste Platz wenn es nur einmal pro Klasse notwendig ist. Wenn es pro Instanz sein muss eben der "normale" Konstruktor. Nach eine kleine Anmerkung zu den statischen Konstruktoren. Die Ausführung eines solchen wird solange verzögert bis die erste Instanz von der Klasse erzeugt wird. Und es reicht auch nicht innerhalb der Klasse selbst eine statische Instanz dieser Klasse anzulegen.