【问题标题】:Runtime code injection using DynamicMethod?使用 DynamicMethod 的运行时代码注入?
【发布时间】:2011-05-20 08:15:40
【问题描述】:

考虑以下简单代码:

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

我想做的是向 Foo 委托中注入一些调试代码,这将是等效的:

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

问题在于我必须能够在运行时执行此操作,因此编译时和后处理方法是不可能的。

我最初的方法是使用 Delegate.Combine() 将 prologue 和 epilogue 方法添加到 Foo 委托。唉,这行不通,因为它会处理返回值。

我目前的想法是使用 System.Reflection.Emit 和 DynamicMethod 作为潜在的解决方案。据我所知,我需要获取 FooImplementation 的 MethodInfo,获取它的 MethodBody,将其转换为 DynamicMethod 并将我的 try-finally 块注入其中。

不幸的是,我完全不知道该怎么做。有人愿意伸出援手吗?还是您有其他(最好更简单)的想法?

编辑:这里的用例是调试 OpenGL 绑定 (http://www.opentk.com)。我们必须注入 2226 个参数差异很大的方法,所以需要一个通用的方法。

【问题讨论】:

    标签: c# code-injection reflection.emit dynamicmethod opentk


    【解决方案1】:

    请注意您正在尝试做什么,但如果它只是重​​定向指向Foo 字段的FooImplementation,那么您可以简单地执行以下操作:

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

    当然,Inject 不必在同一个类中,但是如果字段/属性不可公开访问,则必须使用 Reflection 来执行此操作,但这并不难。

    【讨论】:

    • 此解决方案非常适用于可处理的多种注入方法。不幸的是,在这种特定情况下,我必须注入 2226 种具有完全不同参数的方法(这是一个 OpenGL 绑定,我将更新原始问题以反映这一点)。
    【解决方案2】:

    您是否考虑过使用类似的东西: 后锐化? http://www.sharpcrafters.com/postsharp

    还是企业库策略注入? http://msdn.microsoft.com/en-us/library/ff650672.aspx

    甚至是 Mono.Cecil? http://www.mono-project.com/Cecil

    【讨论】:

    • Mono.Cecil 和 postsharp 在编译时运行,这在此处不起作用(我需要能够在运行时禁用/启用它)。我以前没见过 Enterprise Library,谢谢,我去看看。
    【解决方案3】:

    TypeMock 也可能是您的问题的可行解决方案: http://www.typemock.com/

    您还可以使用 RealProxy 类 (http://msdn.microsoft.com/en-us/library/system.runtime.remoting.proxies.realproxy.aspx) 在运行时生成您自己的代理,该代理将首先调用你注入的代码,然后调用实际的方法。

    【讨论】:

      【解决方案4】:

      你可以使用表达式。

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

      这样,您可以在运行时为序言和尾声构建表达式。

      【讨论】:

      • 谢谢,非常简洁优雅。让我测试一下这个解决方案,我认为它应该可以工作!
      • 仅供参考,编译后的表达式在幕后使用 DynamicMethod。
      【解决方案5】:

      您还可以在 codeplex 上试用 CInject,轻松将代码注入托管代码(C# 或 VB.NET)。

      【讨论】:

        【解决方案6】:

        我最终做的是使用 Mono.Cecil 实现我自己的重写解决方案。简单,快速,而且效果很好。不幸的是,这必须作为构建后事件来完成,所以这实际上不是运行时代码注入。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2018-11-02
          • 2011-09-08
          • 2010-09-06
          • 2012-02-05
          • 2020-04-13
          • 1970-01-01
          • 1970-01-01
          • 2015-05-03
          相关资源
          最近更新 更多