VerificationException при попытке запустить DynamicMethod с аргументом Action.Method

Я пытаюсь запустить действие всякий раз, когда происходит событие, игнорируя параметры событий (по крайней мере, на данный момент). Я нахожу событие с помощью отражения, затем создаю динамический метод, соответствующий ожидаемой подписи (нет гарантии, что это только отправитель /EventArgs) и оттуда попытаться вызвать действие.

/// <summary>
/// Execute an action when an event fires, ignoring it's parameters.
/// 'type' argument is element.GetType()
/// </summary>
bool TryAsEvent(Type type, UIElement element, string actionStr, Action act)
{
    try
    {
        //Get event info
        var eventInfo = type.GetEvent(actionStr); //Something like 'Click'
        var eventType = eventInfo.EventHandlerType;

        //Get parameters
        var methodInfo = eventType.GetMethod("Invoke");
        var parameterInfos = methodInfo.GetParameters();
        var paramTypes = parameterInfos.Select((info => info.ParameterType)).ToArray();

        //Create method
        var dynamicMethod = new DynamicMethod("", typeof(void), paramTypes);

        //Static method that will invoke the Action (from our parameter 'act')
        //Necessary because the action itself wasn't recognized as a static method
        // which caused an InvalidProgramException
        MethodInfo exec = typeof(ThisClass).GetMethod("ExecuteEvent");

        //Generate IL
        var il = dynamicMethod.GetILGenerator();
        il.DeclareLocal(typeof(MethodInfo));

        //MethodInfo miExecuteAction = act.Method;
        //Commented out some trial and failure stuff
        il.Emit(OpCodes.Ldobj, act.Method);
        //il.Emit(OpCodes.Stloc, lc);
        //il.Emit(OpCodes.Ldloc, lc);
        //il.Emit(OpCodes.Ldobj, act.Method);
        //il.Emit(OpCodes.Ldarg_0);
        //il.Emit(OpCodes.Pop);
        il.EmitCall(OpCodes.Call, exec, null);
        il.Emit(OpCodes.Ret);

        //Test the method (the event under test has a handler taking 2 args):
        //var act2 = dynamicMethod.CreateDelegate(eventType);
        //act2.DynamicInvoke(new object[]{null, null});

        //Add handler
        var handler = dynamicMethod.CreateDelegate(eventType);
        eventInfo.AddEventHandler(element, handler);

        return true;
    }
    catch
    {
        return false;
    }
}

public static void ExecuteEvent(MethodInfo i)
{
    i.Invoke(null, null);
}

Может ли кто-нибудь сказать мне, как этого добиться?

ОБНОВЛЕНИЕ: Вот краткий файл проекта VS11, имитирующий реальный сценарий:

Загрузить

Обновление (исправление):

public class Program
{
    public static void Main(string[] args)
    {
        var x = new Provider();

        new Program().TryAsEvent(
            x.GetType(),
            x,
            "Click",
            new Program().TestInstanceMethod);
            //Use this lambda instead to test a static action
            //() => Console.WriteLine("Action fired when event did."));

        x.Fire();
        Console.ReadLine();
    }

    public void TestInstanceMethod()
    {
        Console.WriteLine("Action fired when event did.");
    }

    /// <summary>
    /// Execute an action when an event fires, ignoring it's parameters.
    /// </summary>
    bool TryAsEvent(Type type, object element, string actionStr, Action act)
    {
        try
        {
            var getMFromH = typeof(MethodBase)
                .GetMethod("GetMethodFromHandle", 
                BindingFlags.Public | BindingFlags.Static, 
                null, 
                new[] { typeof(RuntimeMethodHandle) }, null);

            //Get event info
            var eventInfo = type.GetEvent(actionStr);
            var eventType = eventInfo.EventHandlerType;

            //Get parameters
            var methodInfo = eventType.GetMethod("Invoke");
            var parameterInfos = methodInfo.GetParameters();
            var paramTypes = parameterInfos.Select((info => info.ParameterType)).ToArray();

            //Non-static action?
            var target = act.Target;
            if (target != null) //Prepend instance object
                paramTypes = new[] {target.GetType()}.Union(paramTypes).ToArray();

            //Create method
            var dynamicMethod = new DynamicMethod("", typeof(void), paramTypes);

            //Static method that will invoke the Action (from our parameter 'act')
            var exec = typeof (Program).GetMethod
                (target != null // Call proper method based on scope of action
                     ? "ExecuteEvent"
                     : "ExecuteEventStati");

            //Generate IL
            var il = dynamicMethod.GetILGenerator();
            if (target != null) //Push object instance on stack if working with non-static action
                il.Emit(OpCodes.Ldarg_0);
            il.Emit(OpCodes.Ldtoken, act.Method);
            il.Emit(OpCodes.Call, getMFromH);
            il.Emit(OpCodes.Call, exec);
            il.Emit(OpCodes.Ret);

            //Add handler
            var handler =
                target != null
                //Call with target obj if we're working with a non-static action
                ? dynamicMethod.CreateDelegate(eventType, target)
                : dynamicMethod.CreateDelegate(eventType);
            eventInfo.AddEventHandler(element, handler);

            return true;
        }
        catch (Exception ex)
        {
            Console.WriteLine("Exception: " + ex);
            return false;
        }
    }

    public static void ExecuteEventStati(MethodInfo i)
    {
        i.Invoke(null, null);
    }
    public static void ExecuteEvent(object o, MethodInfo i)
    {
        i.Invoke(o, null);
    }
}

И вот несвязанный код для этого примера (на случай, если кто-то захочет скопировать и вставить):

public class Provider
{
    public event MyRoutedEventHandler Click;

    public void Fire()
    {
        if (Click != null)
            Click(this, new MyRoutedEventArgs());
    }
}

public delegate void MyRoutedEventHandler(object sender, MyRoutedEventArgs e);

public class MyRoutedEventArgs : RoutedEventArgs
{
    public MyRoutedEventArgs()
    {
    }

    public MyRoutedEventArgs(RoutedEvent routedEvent)
        : this(routedEvent, (object)null)
    {
    }

    public MyRoutedEventArgs(RoutedEvent routedEvent, object source)
        : base(routedEvent, source){}
}

person natli    schedule 28.03.2013    source источник
comment
Вы пытались сохранить свой метод в сборке, а затем запустить на ней PEVerify?   -  person svick    schedule 28.03.2013
comment
@svick Он сказал: «Все классы и методы проверены» - я добавил ссылку для загрузки в файл проекта в своем вопросе, не могли бы вы проверить, пожалуйста?   -  person natli    schedule 28.03.2013


Ответы (1)


Вы не можете использовать ldobj таким образом. Предполагается, что ldobj берет адрес или управляемую ссылку из стека и упаковывает тип значения, хранящийся по этому адресу, в объект. Вместо этого вы вызываете ldobj с пустым стеком и используете неправильную перегрузку Emit (второй аргумент должен быть типом типа значения, а не произвольным экземпляром объекта).

Вместо этого вы должны использовать ldtoken (с перегрузкой Emit, которую вы уже используете, передавая метод действия в качестве второго аргумента), за которым следуют вызовы MethodBase.GetMethodFromHandle, а затем ваш метод ExecuteEvent. Обратите внимание, что здесь вы должны использовать такой вызов, как Emit(OpCodes.Call, exec), а не EmitCall, в документации которого явно указано, что он предназначен только для вызова методов с переменными аргументами, а не для обычных вызовов. Это должно работать для делегатов неуниверсальных методов - для общих методов вам нужно прыгать через некоторые дополнительные обручи.

Это должно устранить наиболее очевидные проблемы с поколением IL. Однако я думаю, что в вашем подходе есть по крайней мере одна концептуальная проблема: что, если делегат Action указывает на нестатический метод? Затем вам нужно также захватить и использовать Target действия, что будет нетривиально.

person kvb    schedule 28.03.2013
comment
Спасибо за подробное объяснение. Что касается вашей последней заметки, у вас есть предложение по лучшему подходу? Единственное, что я пытаюсь сделать, это запускать действие всякий раз, когда происходит событие, не думал, что это будет так много проблем. Когда действие считается нестатичным? В моем случае это лямбда, которая выполняет метод для экземпляра, поэтому я собираюсь пойти дальше и предположить, что это не сработает даже после предложенных вами исправлений. - person natli; 29.03.2013
comment
@natli - это не так сложно исправить. Я думаю, вы можете проверить, является ли действие Target ненулевым, и если это так, добавьте дополнительный начальный параметр в свою коллекцию paramTypes, которая имеет тот же тип, что и она. Затем вы можете использовать другую CreateDelegate перегрузку для создания закрывающего делегата. над целью. - person kvb; 29.03.2013
comment
@kvp Отлично, все сработало, как и ожидалось. Большое спасибо за ваше время! - person natli; 30.03.2013