Erstellen von Dynamic Aktion <T> Anweisungen


1

Ich spiele mit Dynamic um und zielen darauf ab, Folgendes zu tun:

Ich habe eine Aktion aus dem ich den IL-Code erhalten als Bytes GetILAsByteArray() verwenden. Aus diesen Bytes möchte ich eine Dynamische Methode erstellen und ausführen ist. Hier ist ein Beispiel dafür, was ich zu tun versucht:

class Program 
{ 
    static void Main(string[] args) 
    { 
     //Create action and execute 
     Action<string> myAction = s => 
     { 
      Console.WriteLine("Hello " + s); 
     }; 
     myAction("World"); 
     //Get IL bytes 
     byte[] ilBytes = myAction.GetMethodInfo().GetMethodBody().GetILAsByteArray(); 
     DynamicMethod dynamicCallback = new DynamicMethod("myAction", typeof(void), new Type[] { typeof(string) }); 
     DynamicILInfo dynamicIlInfo = dynamicCallback.GetDynamicILInfo(); 
     dynamicIlInfo.SetCode(ilBytes, 100); 
     dynamicCallback.Invoke(null, new object[] { "World" }); 
    } 
} 

Wenn eine dynamicCallback.Invoke(null, new object[] { "World" }) rufen wir „Exception geworfen:‚System.BadImageFormatException‘in mscorlib.dll“ erhalten.

Eine Sache, die ich keine Ahnung habe, ist, was ich als zweites Argument für SetCode() verwenden sollte, was sollte als 'maxStackSize' verwendet werden? Wie kann ich den gleichen Wert wie für die erste Aktion festlegen? Aber ich nehme an, das ist nicht der Grund für die Ausnahme.

Wie kann ich aus den IL-Bytes eine dynamische Methode erstellen?


Lösung

Hier würde Ich mag die komplette Lösung von Dudi Keleti bereitgestellt zusammenfassen:

static void Main(string[] args) 
{ 
    Action<string> myAction = s => 
    { 
     Console.WriteLine("Hello " + s); 
    }; 
    MethodInfo method = myAction.GetMethodInfo(); 
    object target = myAction.Target; 

    DynamicMethod dm = new DynamicMethod(
     method.Name, 
     method.ReturnType, 
     new[] {method.DeclaringType}. 
      Concat(method.GetParameters(). 
       Select(pi => pi.ParameterType)).ToArray(), 
     method.DeclaringType, 
     skipVisibility: true); 

    DynamicILInfo ilInfo = dm.GetDynamicILInfo(); 
    var body = method.GetMethodBody(); 
    SignatureHelper sig = SignatureHelper.GetLocalVarSigHelper(); 
    foreach (LocalVariableInfo lvi in body.LocalVariables) 
    { 
     sig.AddArgument(lvi.LocalType, lvi.IsPinned); 
    } 
    ilInfo.SetLocalSignature(sig.GetSignature()); 
    byte[] code = body.GetILAsByteArray(); 
    ILReader reader = new ILReader(method); 
    DynamicMethodHelper.ILInfoGetTokenVisitor visitor = new DynamicMethodHelper.ILInfoGetTokenVisitor(ilInfo, code); 
    reader.Accept(visitor); 
    ilInfo.SetCode(code, body.MaxStackSize); 

    dm.Invoke(target, new object[] { target, "World" }); 

    Console.ReadLine(); //Just to see the result 
} 

Hinweis: DynamicMethodHelper Klasse von Haibo Luo entwickelt und beschrieben in einem blog post aber kann auch direkt heruntergeladen werden here.

  0

Ich denke, dass Sie den maxStackSize-Wert nicht mithilfe von Reflektion abrufen können. Aber das ist hier nicht das Problem. Das Problem besteht darin, dass der Aufruf "Console.WriteLine" als Metadatentoken (wahrscheinlich MethodRef) codiert ist und Metadatentoken nur im Rahmen des Deklarationsmoduls gültig sind. Sehen Sie sich die 'DynamicILInfo.GetTokenFor'-Funktionen an, diese werden andere Metadaten importieren und Token erzeugen, die für die' DynamicMethod' gültig sind. 20 okt. 162016-10-20 09:22:28

  0

@thehennyy Ich habe es ohne Erfolg versucht, siehe meine Bearbeitung. 20 okt. 162016-10-20 09:36:14

  0

Sie müssen das alte Token im IL-Byte-Array durch das neu erstellte ersetzen, das die 'GetTokenFor'-Methode an Sie zurückgibt. 20 okt. 162016-10-20 09:42:02

  0

Ich dachte schon, es ergibt keinen Sinn was ich codiert habe, ich habe es nicht sofort verstanden. Danke. Gibt es einen allgemeinen Weg, dies zu tun, ohne zu wissen, was in der Aktion genannt wurde? Z.B. Gibt es am Ende dynamisch einen Weg dazu? 20 okt. 162016-10-20 09:45:45

  0

Ja, Sie können das Methodenkörper-Bytearray analysieren und dann alle Token mit den 'Module.Resolvexxx'-Methoden auflösen. 20 okt. 162016-10-20 09:55:57

  0

Warum versuchen Sie das zu tun? Wie von thehennyy erklärt, wird der einfache Ansatz aufgrund von Metadaten-Tokens nicht funktionieren. Vielleicht gibt es einen anderen Weg, um das zu erreichen, was Sie versuchen zu tun. 20 okt. 162016-10-20 14:15:30

  0

Die Frage wurde aus Neugier gestellt. Was damit gemacht werden könnte, ist eine Art von Serialisierung von Methoden, die sich nach einer interessanten Sache anhört, mit der man herumspielen kann. Aber ich weiß, dass dies in realen Anwendungen nicht unbedingt sinnvoll ist. 21 okt. 162016-10-21 07:27:24

1

Sie können es wie folgt tun:

byte[] code = body.GetILAsByteArray(); 
ILReader reader = new ILReader(method); 
ILInfoGetTokenVisitor visitor = new ILInfoGetTokenVisitor(ilInfo, code); 
reader.Accept(visitor); 
ilInfo.SetCode(code, body.MaxStackSize); 

ILReader ist eine Klasse, die für Sie die harte Arbeit tun. Sie können es von here kopieren.

Beispiel:

MethodInfo method = ... 
DynamicMethod dm = new DynamicMethod(
    method.Name, 
    method.ReturnType, 
    method.GetParameters.Select(pi => pi.ParameterType).ToArray(), 
    method.DeclaringType, 
    skipVisibility: true\fasle - depends of your need); 

DynamicILInfo ilInfo = dm.GetDynamicILInfo(); 
var body = method.GetMethodBody(); 
SignatureHelper sig = SignatureHelper.GetLocalVarSigHelper(); 
foreach(LocalVariableInfo lvi in body.LocalVariables) 
{ 
    sig.AddArgument(lvi.LocalType, lvi.IsPinned); 
} 
ilInfo.SetLocalSignature(sig.GetSignature()); 
byte[] code = body.GetILAsByteArray(); 
ILReader reader = new ILReader(method); 
ILInfoGetTokenVisitor visitor = new ILInfoGetTokenVisitor(ilInfo, code); 
reader.Accept(visitor); 
ilInfo.SetCode(code, body.MaxStackSize); 

Wenn Ihre Methode eine einfache Methode (nicht generisch und ohne Ausnahme behandelt), soll thid arbeiten.

Wenn Ihre Methode ein Generikum ist, müssen Sie diese an den Eigentümer Typen für die Weitergabe an der Dynamic Konstruktor tun:

var owner = method.DeclaringType.MakeGenericType(
      method.DeclaringType.GetGenericArguments()); 

Eine weitere Sache, wenn sie noch nicht, und Ihre Methode arbeitet, ist eine Instanz Methode übergeben Sie den Instacne-Typ der Methode in der ersten Zelle des Parameter-Arrays des DynamicMethod Konstruktors.

aktualisieren

Sie nicht null hier dm.Invoke(**null**, new object[] { "World" }); weil myAction keine statische Methode ist, passieren kann.

myAction (Action<string>) ist eigentlich eine Methode in einer neu generierten Klasse, die diese Methode halten.

Aber ich überprüft und die Ausnahme wirft, auch wenn ich myAction.Target oder eine neue Instanz dieses Typs übergeben. Die Ausnahme (CLR dedect ein ungültiges Programm) sagt Ihnen, dass die IL nicht genau richtig ist.Ich kann dir jetzt nicht genau sagen, was das Problem ist, aber wenn es dir wichtig ist, kann ich es nächste Woche überprüfen, wenn ich wieder an die Arbeit gehe.

Wie auch immer, wenn Sie wollen einfach nur die DynamicIlInfo.SetCode in Aktion sehen Sie Ihren Code verwenden können, wie aber nur die Methode Informationen aus dieser Änderung:

class Program 
{   
    static void Main(string[] args) 
    { 
     Action<string> myAction = s => 
     { 
      Console.WriteLine("Hello " + s); 
     }; 
     MethodInfo method = myAction.GetMethodInfo(); 

     //Rest of your code 
    } 
} 

dazu:

class Program 
{ 
    static void M(string s) 
    { 
     Console.WriteLine("Hello " + s); 
    } 

    static void Main(string[] args) 
    { 
     MethodInfo method = typeof (Program).GetMethod("M", BindingFlags.Static | BindingFlags.NonPublic); 

     //Rest of your code 
    } 
} 

Update 2:

Anscheinend war ich gestern sehr müde, ich habe Ihren Fehler nicht bemerkt.

Wie ich bereits in meiner ersten Antwort geschrieben,

Eine weitere Sache, wenn ich immer noch nicht funktioniert, und Ihre Methode ist eine Instanzmethode, übergeben Sie die Instanz zu Art des Verfahrens in der ersten Zelle der paramters Array des DynamicMethod Konstruktors.

So müssen Sie dies tun:

DynamicMethod dm = new DynamicMethod(
    method.Name, 
    method.ReturnType, 
    new[] {method.DeclaringType}. 
     Concat(method.GetParameters(). 
     Select(pi => pi.ParameterType)).ToArray(), 
    method.DeclaringType, 
    skipVisibility: true); 

Und die dynamische Methode wie folgt aufrufen:

dm.Invoke(myAction.Target, new object[] { myAction.Target, "World" }); 

Jetzt ist es Arbeit perfekt.

  0

Wo wird ILInfoGetTokenVisitor deklariert? 26 okt. 162016-10-26 12:39:58

  0

@ Sjoerd222888 [Hier.] (Https://msdnshared.blob.core.windows.net/media/MSDNBlogsFS/prod.evol.blogs.msdn.com/CommunityServer.Components.PostAttachments/00/01/02/35/ 08/DynamicMethodHelper.cs) Und es gibt einen Blogpost darüber [hier] (https://blogs.msdn.microsoft.com/haibo_luo/2006/11/07/turn-methodinfo-to-dynamicmethod/) 26 okt. 162016-10-26 12:53:36

  0

ich sofort Erhalte eine 'System.Reflection.TargetInvocationException' mit der Nachricht: "{" Bad binary signature. (Ausnahme von HRESULT: 0x80131192) "}" beim Versuch, die dynamische Methode aufzurufen. Ich glaube, ich vermisse etwas Grundlegendes. 26 okt. 162016-10-26 13:35:35

  0

@ Sjoerd222888 Siehe mein Update Beispiel bitte 26 okt. 162016-10-26 14:49:14

  0

Siehe mein Update. Ich bekomme immer noch eine 'TargetInvocationException'. Wo gehe ich falsch? 27 okt. 162016-10-27 12:05:32

  0

@ Sjoerd222888 Überprüfen Sie es jetzt 27 okt. 162016-10-27 23:40:20

+1

@ Sjoerd222888 seine Arbeit jetzt 28 okt. 162016-10-28 08:39:43

  0

Vielen Dank für die detaillierten Erklärungen. Ich bin gerade dabei, zu lernen, mit 'DynamicMethod' zu arbeiten und Ihre Antwort ist eine große Hilfe! 28 okt. 162016-10-28 09:14:50