2010-12-04 51 views
5

Consultez le code trivial suivant:L'injection de code d'exécution à l'aide de DynamicMethod?

using System; 
class Test 
{ 
    delegate int FooDelegate(int i); 
    FooDelegate Foo = FooImplementation; 
    static int FooImplementation(int i) 
    { 
     return i + 1; 
    } 

    public static void Main() 
    { 
     Foo(1); 
    } 
} 

Ce que je voudrais faire est d'injecter un code de débogage dans le délégué Foo, ce qui équivaudrait:

FooDelegate Foo = delegate(int i) 
{ 
    try 
    { 
     DebugPrologue(); 
     return FooImplementation(i); 
    } 
    finally 
    { 
     DebugEpilogue(); 
    } 
}; 

La torsion est que je dois être capable de le faire à runtime, donc les méthodes de compilation et de post-traitement sont hors de question.

Mon approche initiale a utilisé Delegate.Combine() pour ajouter les méthodes prologue et epilogue au délégué Foo. Hélas, cela ne fonctionnera pas car cela permet d'obtenir des valeurs de retour.

Mon idée actuelle est d'utiliser System.Reflection.Emit et DynamicMethod comme solution potentielle. Pour autant que je sache, j'ai besoin de MethodInfo pour FooImplementation, obtenir son MethodBody, le convertir en DynamicMethod et y injecter mon bloc try-finally.

Malheureusement, je n'ai absolument aucune idée de comment faire cela. Quelqu'un est-il prêt à donner un coup de main? Ou avez-vous une autre idée (de préférence plus simple)? Edit: le cas d'utilisation ici est le débogage d'une liaison OpenGL (http://www.opentk.com). Nous devons injecter 2226 méthodes avec des paramètres très différents, donc une approche générale est nécessaire.

Répondre

0

Ce que j'ai finalement fini par faire était d'implémenter ma propre solution de réécriture en utilisant Mono.Cecil.Simple, rapide et ça marche bien. Malheureusement, ceci doit être fait en tant qu'événement de post-construction, donc ce n'est pas réellement l'injection de code d'exécution.

0

Notez que ce que vous essayez de faire, mais si elle est redirigeant simplement le FooImplementation qui est pointé au Foo le terrain, vous pouvez simplement faire:

class Test 
{ 
    delegate int FooDelegate(int i); 
    static FooDelegate Foo = FooImplementation; 

    static int FooImplementation(int i) 
    { 
     return i + 1; 
    } 

    public static void Main() 
    { 
     Inject(); 
     Foo(1); 
    } 


    public static void Inject() 
    { 
     var oldImpl = Foo; 

     Foo = i => 
      { 
       try 
       { 
        BeginDebug(); 
        return oldImpl(i); 
       } 
       finally 
       { 
        EndDebug(); 
       } 
      }; 
    } 

    public static void BeginDebug() 
    { 
     Console.WriteLine("Begin Foo"); 
    } 

    public static void EndDebug() 
    { 
     Console.WriteLine("End Foo"); 
    } 
} 

Bien sûr, le Injecter n'a pas Soyez dans la même classe, cependant si le champ/propriété n'est pas accessible au public, vous devrez utiliser Reflection pour le faire, mais ce n'est pas si difficile.

+0

Cette solution fonctionne parfaitement pour un nombre traitable de méthodes à injecter. Malheureusement, dans ce cas précis, je dois injecter 2226 méthodes avec des paramètres extrêmement différents (c'est une liaison OpenGL, je vais mettre à jour la question originale pour refléter cela). –

0

Avez-vous envisagé d'utiliser quelque chose comme: Postsharp? http://www.sharpcrafters.com/postsharp

ou Stratégie de bibliothèque d'entreprise Injection? http://msdn.microsoft.com/en-us/library/ff650672.aspx

ou même Mono.Cecil? http://www.mono-project.com/Cecil

+0

Mono.Cecil et postsharp s'exécutent au moment de la compilation, ce qui ne fonctionnera pas ici (je dois pouvoir désactiver/activer ceci à l'exécution). Je n'ai pas encore consulté la bibliothèque d'entreprise, merci, je vais vérifier. –

0

Typemock aussi peut-être une solution viable à votre question: http://www.typemock.com/

Vous pouvez également utiliser la classe RealProxy (http://msdn.microsoft.com/en-us/library/system.runtime. remoting.proxies.realproxy.aspx) pour générer votre propre proxy lors de l'exécution qui appellera d'abord votre code injecté, puis appellera la méthode actuelle.

3

Vous pouvez utiliser des expressions.

var param = Expression.Parameter(typeof(int), "i"); 

Foo = Expression.Lambda(
     Expression.TryFinally(
      Expression.Block(
       <expression for DebugPrologue>, 
       Expression.Invoke(<expression for FooImplementation>, param)), 
      <expression for DebugEpilogue>), 
     param) 
     .Compile(); 

De cette façon, vous pouvez créer les expressions pour le prologue et l'épilogue en cours d'exécution.

+0

Merci, très simple et élégant. Laisse-moi tester cette solution, je pense que ça devrait marcher! –

+0

FYI, Expressions compilées utilisent DynamicMethod dans les coulisses. –

0

Vous pouvez également essayer CInject sur codeplex pour injecter du code en code managé (C# ou VB.NET) avec une grande facilité.