2013-03-28 47 views
1

我想只要有一個事件不觸發的操作,忽略事件的參數(至少現在是)。我發現通過反射的情況下,然後創建一個匹配預期簽名的動態方法(不能保證它只有Sender/EventArgs),並從那裏嘗試調用該操作。VerificationException精氨酸

/// <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項目文件模仿真實的情景:

Download

更新(修正):

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 2013-03-28 00:56:51

+0

@svick它說'所有類和方法已驗證' - 我在我的問題中添加了一個項目文件的下載鏈接,可以檢查嗎? – natli 2013-03-28 12:54:18

回答

2

不能使用ldobj這樣。 ldobj應該從棧中獲取地址或託管引用,並將存儲在該地址的值類型複製到對象中。相反,你打電話ldobj與空棧和你使用Emit錯誤的過載(第二個參數應該是值類型的類型,而不是任意的對象實例)。

相反,你應該(與Emit超載你已經在使用,此操作的方法傳遞的第二個參數)使用ldtoken,然後調用MethodBase.GetMethodFromHandle,然後你ExecuteEvent方法。請注意,您應該在此處使用類似Emit(OpCodes.Call, exec)的呼叫,而不是EmitCall,其文檔明確指出它僅用於調用可變長度方法,而不用於正常呼叫。這應該適用於非泛型方法的代表 - 對於需要通過一些額外的循環來跳轉的泛型方法。

這應該清理與IL一代人最明顯的問題。但是,我認爲您的方法至少存在一個概念性問題:如果Action委託指向非靜態方法會怎樣?然後你需要捕捉並使用動作的Target,這將是不平凡的。

+0

感謝您的詳細解釋。在你最後的筆記中,你有沒有更好的方法建議?我試圖做的唯一事情就是在事件發生時觸發一個動作,並不認爲這會造成很大的麻煩。什麼時候認爲Action是非靜態的?在我的情況下,它是一個lambda,它在一個實例上執行一個方法,所以我會繼續前進,並假設即使在您的建議修復之後也不會工作。 – natli 2013-03-29 10:51:05

+0

@natli - 修復可能不太難。我認爲你可以檢查action的'Target'是否爲非null,如果是這樣的話,請向'paramTypes'集合中添加一個附加的初始參數,它的類型與它相同。然後,您可以使用[不同](http://msdn.microsoft.com/en-us/library/74x8f551.aspx)'CreateDelegate'重載來創建「關閉」目標的委託。 – kvb 2013-03-29 12:15:44

+0

@kvp非常好,按預期工作。非常感謝您的時間! – natli 2013-03-29 21:42:40

相關問題