代码之家  ›  专栏  ›  技术社区  ›  natli

尝试使用Action.Method arg运行DynamicMethod时出现VerificationException

  •  2
  • natli  · 技术社区  · 11 年前

    我试图在事件发生时触发一个操作,忽略事件参数(至少目前是这样)。我通过反射找到事件,然后创建一个与预期签名匹配的动态方法(不能保证只有Sender/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项目文件:

    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){}
    }
    
    1 回复  |  直到 11 年前
        1
  •  3
  •   kvb    11 年前

    你不能使用 ldobj 就像那样。 ldobj公司 应该从堆栈中获取一个地址或托管引用,并将存储在该地址的值类型装箱到对象中。相反,你在打电话 ldobj公司 有一个空堆栈,并且您使用了错误的重载 Emit (第二个参数应该是值类型的类型,而不是任意对象实例)。

    相反,您应该使用 ldtoken (与 发出 重载,将操作的方法作为第二个参数传入),然后调用 MethodBase.GetMethodFromHandle 然后你 ExecuteEvent 方法请注意,您应该使用这样的呼叫 Emit(OpCodes.Call, exec) 在这里而不是 EmitCall ,其文档明确指出,它仅用于调用varargs方法,而不用于正常调用。这应该适用于非泛型方法的委托——对于泛型方法,您需要跳过一些额外的关卡。

    这应该可以解决IL一代最明显的问题。然而,我认为你的方法至少有一个概念问题:如果 Action 委托指向非静态方法?然后你需要捕捉并使用动作 Target 同样,这将是不平凡的。