Créer DynamicMethod d'Action <T> instructions


1

Je jouais avec DynamicMethod et visent à faire ce qui suit:

J'ai une action dont j'obtenir le code IL en octets en utilisant GetILAsByteArray(). A partir de ces octets, je voudrais créer une méthode dynamique et exécuter est. Voici un exemple de ce que je suis en train de faire:

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

Lorsque vous appelez un dynamicCallback.Invoke(null, new object[] { "World" }) nous obtenons « Exception lancée: « System.BadImageFormatException » dans mscorlib.dll ».

Une chose que je n'ai aucune idée abut est ce que je devrais utiliser comme deuxième argument pour SetCode(), ce qui devrait être utilisé comme «maxStackSize»? Comment puis-je définir la même valeur que pour l'action initiale? Mais je suppose que ce n'est pas la raison de l'exception.

Comment puis-je créer correctement une méthode dynamique à partir des octets IL?


Solution

Ici, je voudrais résumer la solution complète fournie par Dudi Keleti:

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 
} 

Note: DynamicMethodHelper est classe développée par Haibo Luo et décrit dans un blog post mais peut également être téléchargé directement here.

  0

Je pense que vous ne pouvez pas obtenir la valeur maxStackSize en utilisant la réflexion. Mais en effet, ce n'est pas le problème ici. Le problème est que l'appel 'Console.WriteLine' est codé comme un jeton de métadonnées (MethodRef probablement) et que les jetons de métadonnées ne sont valides que dans la portée du module le déclarant. Jetez un oeil aux fonctions 'DynamicILInfo.GetTokenFor', elles vont importer d'autres éléments de métadonnées et créer des jetons valides pour' DynamicMethod'. 20 oct.. 162016-10-20 09:22:28

  0

@thehennyy J'ai essayé sans succès, voir mon édition. 20 oct.. 162016-10-20 09:36:14

  0

Vous devez remplacer l'ancien jeton du tableau d'octets IL par celui nouvellement créé que la méthode 'GetTokenFor' vous renvoie. 20 oct.. 162016-10-20 09:42:02

  0

Je pensais déjà que ça n'a pas de sens ce que j'ai codé, je ne l'ai pas compris tout de suite. THX. Existe-t-il un moyen général de le faire sans avoir besoin de savoir ce qui a été appelé dans l'action? Par exemple. Y a-t-il un moyen de le faire dynamiquement à la fin? 20 oct.. 162016-10-20 09:45:45

  0

Oui, vous pouvez analyser le tableau d'octets du corps de la méthode, puis résoudre tous les jetons à l'aide des méthodes 'Module.Resolvexxx'. 20 oct.. 162016-10-20 09:55:57

  0

Pourquoi essayez-vous de faire cela? Comme expliqué par thehennyy, l'approche simple ne fonctionnera pas à cause des jetons de métadonnées. Peut-être qu'il y a un autre moyen de réaliser ce que vous essayez de faire. 20 oct.. 162016-10-20 14:15:30

  0

La question a été affichée par curiosité. Ce qui pourrait être fait avec ceci est une sorte de sérialisation des méthodes, ce qui semble être une chose intéressante à jouer. Mais je sais que ce n'est peut-être pas une bonne chose à faire dans les applications réelles. 21 oct.. 162016-10-21 07:27:24

1

Vous pouvez le faire comme ceci:

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

ILReader est une classe qui font le travail pour vous. Vous pouvez le copier à partir du here.

Exemple:

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

Si votre méthode est une méthode simple (non générique et sans poignées exception), thid devrait fonctionner.

Si votre méthode est générique, vous devez le faire pour passer le type de propriétaire au constructeur DynamicMethod:

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

une chose, si elle ne fonctionne toujours pas, et votre méthode est une instance méthode, passez le type instacne de la méthode dans la première cellule du tableau de paramètres du constructeur DynamicMethod.

Mise à jour

Vous ne pouvez pas passer null ici dm.Invoke(**null**, new object[] { "World" }); parce myAction n'est pas une méthode statique.

myAction (Action<string>) est en fait une méthode dans une nouvelle classe générée qui contient cette méthode.

Mais j'ai vérifié et l'exception est lancée même si je passe myAction.Target ou une nouvelle instance de ce type. L'exception (CLR dédier un programme invalide) vous indique que l'IL n'est pas exactement correcte.Je ne peux pas vous dire maintenant exactement quel est le problème, mais si c'est important pour vous, je peux le vérifier la semaine prochaine quand je vais retourner au travail.

Quoi qu'il en soit si vous voulez juste pour voir le DynamicIlInfo.SetCode en action, vous pouvez utiliser votre code comme il est, mais il suffit de changer les informations de méthode de ceci:

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

     //Rest of your code 
    } 
} 

à ceci:

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

Mise à jour 2:

Apparemment, j'étais très fatigué hier, je n'ai pas réalisé votre erreur.

Comme je l'ai écrit dans ma première réponse,

une chose, si elle ne fonctionne toujours pas, et votre méthode est une méthode d'instance, passer le type instacne de la méthode dans la première cellule des paramters tableau du constructeur DynamicMethod.

donc vous devez faire ceci:

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

Et invoquez la méthode dynamique comme ceci:

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

Maintenant, il est un travail parfait.

  0

Où est déclaré 'ILInfoGetTokenVisitor'? 26 oct.. 162016-10-26 12:39:58

  0

@ Sjoerd222888 [ici.] (Https://msdnshared.blob.core.windows.net/media/MSDNBlogsFS/prod.evol.blogs.msdn.com/CommunityServer.Components.PostAttachments/00/01/02/35/ 08/DynamicMethodHelper.cs) Et il y a un billet de blog à ce sujet [ici] (https://blogs.msdn.microsoft.com/haibo_luo/2006/11/07/turn-methodinfo-to-dynamicmethod/) 26 oct.. 162016-10-26 12:53:36

  0

obtenez une 'System.Reflection.TargetInvocationException' avec le message suivant: "{" Mauvaise signature binaire. (Exception de HRESULT: 0x80131192) "}" lorsque vous essayez d'appeler la méthode dynamique. Je suppose que quelque chose me manque fondamentalement. 26 oct.. 162016-10-26 13:35:35

  0

@ Sjoerd222888 Voir mon exemple de mise à jour s'il vous plaît 26 oct.. 162016-10-26 14:49:14

  0

Voir ma mise à jour. J'ai toujours une 'TargetInvocationException'. Où vais-je mal? 27 oct.. 162016-10-27 12:05:32

  0

@ Sjoerd222888 Vérifier maintenant 27 oct.. 162016-10-27 23:40:20

+1

@ Sjoerd222888 son travail maintenant 28 oct.. 162016-10-28 08:39:43

  0

Merci beaucoup pour les explications détaillées. Je suis sur le point d'apprendre à travailler avec DynamicMethod et votre réponse est d'une grande aide! 28 oct.. 162016-10-28 09:14:50