Erstellen von Methode dynamisch und Ausführung
Hintergrund:
Möchte ich definieren, paar static
Methoden in C# , und erzeugen von IL code als byte-array, das aus einer dieser Methoden, die zur Laufzeit ausgewählt (auf dem client), und senden das byte-array über ein Netzwerk zu einem anderen Computer (server), wo es ausgeführt werden soll, nach der erneuten Generierung des IL-Codes, der aus dem byte-array.
Mein Versuch: (POC)
public static class Experiment
{
public static int Multiply(int a, int b)
{
Console.WriteLine("Arguments ({0}, {1})", a, b);
return a * b;
}
}
Und dann bekomme ich den IL-code der Methode Körper, als:
BindingFlags flags = BindingFlags.Public | BindingFlags.Static;
MethodInfo meth = typeof(Experiment).GetMethod("Multiply", flags);
byte[] il = meth.GetMethodBody().GetILAsByteArray();
Bisher habe ich nicht alles erstellen dynamisch. Aber ich habe IL-code als byte-array und ich möchte, um eine Baugruppe zu erstellen, dann ein Modul, dann ein Typ, der dann eine Methode - alle dynamisch. Bei der Erstellung der Methode, die Körper der dynamisch erstellten Methode, die ich verwenden den IL-code, die ich mit der spiegelung in den obigen code.
Code generation-code ist wie folgt:
AppDomain domain = AppDomain.CurrentDomain;
AssemblyName aname = new AssemblyName("MyDLL");
AssemblyBuilder assemBuilder = domain.DefineDynamicAssembly(
aname,
AssemblyBuilderAccess.Run);
ModuleBuilder modBuilder = assemBuilder.DefineDynamicModule("MainModule");
TypeBuilder tb = modBuilder.DefineType("MyType",
TypeAttributes.Public | TypeAttributes.Class);
MethodBuilder mb = tb.DefineMethod("MyMethod",
MethodAttributes.Static | MethodAttributes.Public,
CallingConventions.Standard,
typeof(int), //Return type
new[] { typeof(int), typeof(int) }); //Parameter types
mb.DefineParameter(1, ParameterAttributes.None, "value1"); //Assign name
mb.DefineParameter(2, ParameterAttributes.None, "value2"); //Assign name
//using the IL code to generate the method body
mb.CreateMethodBody(il, il.Count());
Type realType = tb.CreateType();
var meth = realType.GetMethod("MyMethod");
try
{
object result = meth.Invoke(null, new object[] { 10, 9878 });
Console.WriteLine(result); //should print 98780 (i.e 10 * 9878)
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
Aber statt Druck 98780
auf der Ausgabe-Fenster, wirft er eine exception, die sagen,
System.Reflexion.TargetInvocationException: Ausnahme wurde ausgelöst durch das Ziel für einen Aufruf. ---> System.TypeLoadException: Konnte nicht geladen werden Typ 'Invalid_Token.0x0100001E' aus assembly 'MyDLL, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null".
bei MyType.MyMethod(Int32 Wert1, Int32 Wert2)
[...]
Bitte helfen Sie mir herauszufinden, die Ursache des Fehlers, und wie es zu lösen ist.
- Sie können möglicherweise verwenden Sie Mono.Cecil für einige Sachen.
- Warum nicht die standard-Methode mit den plugins??? Ich meine zu definieren Schnittstelle für plugin und senden Sie die dll mit der Umsetzung für Ihre Anwendung nach dem laden der dll und der code ausgeführt.
Du musst angemeldet sein, um einen Kommentar abzugeben.
Laufen ildasm.exe auf eine beliebige Montage. Verwenden Sie View + Karte token-Werte und der Blick auf einige disassemblierten code. Sie werden sehen, dass die IL enthält Verweise zu anderen Methoden und Variablen durch eine Zahl. Die Nummer ist ein index in die Metadaten der Tabellen für eine Montage.
Vielleicht sehen Sie jetzt das problem, Sie kann nicht Transplantation ein Stück von IL aus einer assembly zu einem anderen, es sei denn, dass die target-Baugruppe hat die gleichen Metadaten. Oder es sei denn, Sie ersetzen Sie die Metadaten-token-Werte in der IL mit Werte, die mit den Metadaten der target-Baugruppe. Dies ist sehr unpraktisch, natürlich, Sie im wesentlichen am Ende der Duplizierung der Montage. Könnte genauso gut machen Sie sich eine Kopie der assembly. Oder für diese Angelegenheit, könnte genauso gut verwenden Sie die vorhandene IL in der ursprünglichen assembly.
Müssen Sie denken, dies durch ein bit, es ist ziemlich unklar, was Sie eigentlich versuchen zu erreichen. Das System.CodeDom und Reflexion.Emittieren von namespaces zur Verfügung, um dynamisch code zu erzeugen.
Wenn ich die folgende Methode:
kompilieren im Release-Modus und verwenden Sie es in Ihrem code, funktioniert alles einwandfrei. Und wenn ich überprüfen das
il
array, es enthält 4 bytes entsprechen genau den vier Anweisungen von Jon ' s Antwort (ldarg.0, ldarg.1, "mul", ret).Wenn ich es kompilieren im debug-Modus, der code ist 9 bytes. Und im Reflektor, sieht es wie folgt aus:
Der problematische Teil ist die lokale variable. Wenn Sie kopieren Sie einfach die Anweisung bytes, die Sie emittieren Anweisung, die verwendet eine lokale variable, aber man kann nie erklären.
Wenn Sie wurden mit
ILGenerator
könnten SieDeclareLocal()
. Es scheint, Sie werden in der Lage sein, lokale Variablen mitMethodBuilder.SetMethodBody()
in .Net 4.5 (das folgende funktioniert bei mir im VS 11 DP):Aber ich habe nicht gefunden, einen Weg, das zu tun,.Net 4, außer mit Reflexion zu setzen, einem privaten Feld von
MethodBuilder
enthält die lokalen Variablen nach dem AufrufCreateMethodBody()
:Bezug auf den ursprünglichen Fehler: Typen und Methoden von anderen Baugruppen (wie
System.Console
undSystem.Console.WriteLine
) verwiesen wird, mithilfe von tokens. Und diese tokens unterscheiden sich von Versammlung zu Versammlung. Das bedeutet, dass code, umConsole.WriteLine()
in einer Baugruppe unterscheidet sich von code, um die gleiche Methode, die in einer anderen assembly, wenn man sich an die Anweisung bytes.Was das bedeutet ist, dass Sie haben, um tatsächlich zu verstehen, was die IL bytes bedeuten und ersetzen alle Token, die einen Verweis Typen, Methoden, etc. für diejenigen, die gültig sind in der assembly, die Sie erstellen.
Ich denke, das problem ist mit der Verwendung IL-Typ/Baugruppe in einer anderen. Wenn du diese ersetzen:
mit dabei:
dann wird ausgeführt, die Methode richtig (keine
Console.WriteLine
, aber es gibt den richtigen Wert).Wenn Sie wirklich brauchen, um in der Lage zu schlürfen IL aus einer vorhandenen Methode, Sie benötigen, um weiter zu suchen - aber wenn man Sie brauchte einfach Bestätigung, dass der rest des Codes war, zu arbeiten, dies kann helfen.
Eine Sache, die Sie interessant finden, ist, dass der Fehler, änderungen im ursprünglichen code, wenn Sie die
Console.WriteLine
Anruf vonExperiment
. Es wird einInvalidProgramException
statt. Ich habe keine Ahnung, warum...IL
die ich mit der spiegelung. Gibt es eine Möglichkeit?using IL from one type/assembly in an another
. Vielmehr ist die IL, die ich mit der spiegelung enthält einige zusätzliche bytes, die wir benötigen, heraus zu filtern, bevor es zu CreateMethodBody(). Was genau die zusätzlichen bytes sind, und wie können wir filtern Sie raus, ich weiß es nicht. Zu sein scheint, dass meine nächste Aufgabe.Wenn ich gut verstehe Ihr problem, und Sie wollen einfach nur dynamisch generieren einige .NET-code, und führen Sie es auf dem remote-client, maeby werfen Sie einen Blick auf IronPython. Sie müssen nur zu erstellen string mit Skript dann schicken Sie es an den client und der client kann es ausführen, die zur Laufzeit durch Zugriff auf alle .NET Framework, sogar stören Ihre client-Anwendung in der runtime.
Es ist ein harter Weg, um den "kopieren" - Methode zu arbeiten, und dauert eine Zeit.
Werfen Sie einen Blick auf ILSpy, diese Anwendung wird verwendet, um die Ansicht und analyse der bestehenden code und ist open-source. Ziehen Sie den code aus dem Projekt, die bei der analyse der IL-ASM-code und verwenden Sie es, um kopieren Sie die Methode.
Diese Antwort ist ein klein wenig orthogonal - mehr über das problem als die Technik.
Könnten Sie Ausdruck Bäume - Sie sind nett, mit zu arbeiten und haben VS syntaktischen Zucker.
Für die Serialisierung müssen Sie diese Bibliothek (Sie brauchen, um es zu kompilieren, zu):
http://expressiontree.codeplex.com/
(Dies funktioniert mit Silverlight 4 zu, offenbar.)
Die Beschränkung der expression von Bäumen ist, dass Sie nur support für Lambda-Ausdrücke, d.h. keine Blöcke.
Dies ist nicht wirklich eine Einschränkung, da können Sie noch definieren, andere lambda-Methoden innerhalb eines lambda und geben Sie Sie in die funktionale Programmierung Helfer wie die Linq-API.
Habe ich eine einfache Erweiterung Methode zu zeigen, wie Sie gehen über das hinzufügen von anderen helper-Methoden für funktionale Sachen - der entscheidende Punkt ist, dass Sie brauchen, um die Baugruppen mit den Erweiterungen in der serializer.
Dieser code funktioniert: