2013-03-28 1 views
1

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

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

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

UPDATE: Вот краткий файл VS11 проект имитируя реальный сценарий:

Download

Update (Fix):

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){} 
} 
+0

Вы пытались сохранить свой метод на сборку, а затем запустить PEVerify? – svick

+0

@svick Он сказал: «Все классы и методы проверены» - я добавил ссылку на файл проекта в свой вопрос, не могли бы вы проверить, пожалуйста? – natli

ответ

2

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

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

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

+0

Спасибо за подробное объяснение. На вашей последней ноте у вас есть предложение для лучшего подхода? Единственное, что я пытаюсь сделать, это активировать действие, когда это происходит, не думал, что это будет большой проблемой. Когда действие считается нестатичным? В моем случае это лямбда, которая выполняет метод на экземпляре, поэтому я собираюсь идти вперед и предположить, что это не сработает даже после ваших исправлений. – natli

+0

@natli - это может быть не так уж сложно исправить. Я думаю, вы можете проверить, не является ли действие «Target» недействительным, и если да, добавьте дополнительный начальный параметр в свою коллекцию 'paramTypes', которая имеет тот же тип, что и он. Затем вы можете использовать перегрузку [другой] (http://msdn.microsoft.com/en-us/library/74x8f551.aspx) 'CreateDelegate' для разбивки делегата, который« закрывает »цель. – kvb

+0

@ kvp Отлично, это сработало, как ожидалось. Большое спасибо за ваше время! – natli