Внедрение кода во время выполнения с использованием DynamicMethod? - PullRequest
5 голосов
/ 05 декабря 2010

Рассмотрим следующий тривиальный код:

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 ()методы пролога и эпилога делегату Foo.Увы, это не сработает, так как возвращает возвращаемые значения.

Моя текущая идея - использовать System.Reflection.Emit и DynamicMethod в качестве потенциального решения.Насколько я могу судить, мне нужно получить MethodInfo для FooImplementation, получить его MethodBody, преобразовать его в DynamicMethod и вставить в него мой блок try-finally.

К сожалению, я абсолютно не знаю, каксделай это.Кто-нибудь готов протянуть руку?Или у вас есть другая (желательно более простая) идея?

Редактировать: здесь используется вариант отладки привязки OpenGL (http://www.opentk.com). Мы должны внедрить 2226 методов с совершенно разными параметрами, поэтому необходим общий подход.

Ответы [ 6 ]

3 голосов
/ 05 декабря 2010

Вы можете использовать выражения.

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

Таким образом, вы можете создавать выражения для пролога и эпилога во время выполнения.

0 голосов
/ 24 декабря 2013

В итоге я решил реализовать собственное решение для переписывания с использованием Mono.Cecil. Просто, быстро и работает отлично. К сожалению, это нужно сделать как событие после сборки, так что на самом деле это не внедрение кода во время выполнения.

0 голосов
/ 03 сентября 2012

Вы также можете попробовать CInject на codeplex, чтобы с легкостью внедрить код в управляемый код (C # или VB.NET).

0 голосов
/ 05 декабря 2010

TypeMock также может быть жизнеспособным решением вашей проблемы: http://www.typemock.com/

Вы также можете использовать класс RealProxy (http://msdn.microsoft.com/en-us/library/system.runtime.remoting.proxies.realproxy.aspx) для генерации собственного прокси во время выполнения, которое сначала вызовет ваш введенныйкод, а затем вызвать фактический метод.

0 голосов
/ 05 декабря 2010

Рассматривали ли вы использовать что-то вроде: PostSharp? http://www.sharpcrafters.com/postsharp

или внедрение политики корпоративной библиотеки? http://msdn.microsoft.com/en-us/library/ff650672.aspx

или даже Mono.Cecil? http://www.mono -project.com / Cecil

0 голосов
/ 05 декабря 2010

Обратите внимание, что вы пытаетесь сделать, но если он просто перенаправляет FooImplementation, на который указывает поле Foo, тогда вы можете просто сделать:

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, чтобы сделать это, хотя это не так сложно.

...