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

如何动态附加到WinRT事件?

  •  7
  • driis  · 技术社区  · 12 年前

    我想在WinRT(Windows 8)应用程序中使用MVVM,我的要求之一是能够将事件连接到命令( ICommand )。这意味着我必须动态地向WinRT事件添加一个处理程序。有一个很好的 explanation of how to do that here ,但我的问题是,在编译时处理程序类型是未知的(即,它并不总是 RoutedEventHandler 如在该示例中)。

    我开始编写该代码的通用实现,在那里我使用表达式树构建委托。那部分有效。我的问题是调用 WindowsRuntimeMarshal.AddEventHandler 动态失败:

    var rtMarshalType = typeof (WindowsRuntimeMarshal);
    var eventHandlerMethod = rtMarshalType.GetRuntimeMethods().Single(x => x.IsStatic && x.Name == "AddEventHandler");
    MethodInfo closedAddMethod = eventHandlerMethod.MakeGenericMethod(handlerType);
    closedAddMethod.Invoke(null, new object[] {add, remove, handler});
    

    此操作在 Invoke 调用并引发InvalidOperationException,并返回消息:

    API 'System.Runtime.InteropServices.WindowsRuntimeMarshal.AddEventHandler[ItemClickEventHandler](System.Func 2[Windows.UI.Xaml.Controls.ItemClickEventHandler,System.Runtime.InteropServices.WindowsRuntime.EventRegistrationToken], System.Action 1[System.Runtime.InteropServices.WindowsRuntime.EventRegistrationToken], Windows.UI.Xaml.Controls.ItemClickEventHandler)'不能用于 当前平台。看见 http://go.microsoft.com/fwlink/?LinkId=248273 了解更多信息。

    我知道我有正确的类型,因为当我用这个替换上面的4行代码时(对事件处理程序类型进行硬编码,这不是我想要的),那么代码就可以工作了,事件也会按预期附加:

    WindowsRuntimeMarshal.AddEventHandler<ItemClickEventHandler>(add, remove, handler);
    

    作为参考,这三个参数定义如下(可以肯定的是,这对我的表达式树构建代码来说不是问题,即使在尝试动态调用时,我目前也在使用这些委托的显式定义 AddEventHandler ,仍然失败):

    Func<ItemClickEventHandler, EventRegistrationToken> add =
        a => (EventRegistrationToken) eventInfo.AddMethod.Invoke(instance, new object[] {a});
    Action<EventRegistrationToken> remove = a => eventInfo.RemoveMethod.Invoke(instance, new object[] {a});
    ItemClickEventHandler handler = (s, args) => command.Execute(args);
    

    为什么通过反射调用而不是直接调用时调用会失败?

    当编译时事件处理程序的类型未知时,是否有其他解决方案可以动态附加WinRT事件?

    3 回复  |  直到 12 年前
        1
  •  5
  •   Ark-kun    12 年前

    为什么通过反射调用而不是直接调用时调用会失败?

    答案: WindowsRuntimeMarshal.AddEventHandler 标记为 [SecurityCritical] 属性

    从…起 反射的安全注意事项 以下为: http://msdn.microsoft.com/en-us/library/stfy7tfc.aspx

    根据必要的权限,代码可以使用反射执行以下类型的访问: 通道 平民的 成员 非安全关键型

    因此,您不能使用反射安全关键方法,如 AddEventHandler 即使它们是公开的。您可以在不使用反射的情况下自由调用此类方法,也可以自由反映自己的方法。这就是您的解决方案有效的原因。

        2
  •  0
  •   driis    12 年前

    经过一番修改,我找到了一种方法:

    首先,我声明了这个方法:

        private static void AddEventHandler<T>(Func<T,EventRegistrationToken> add, Action<EventRegistrationToken> remove, T handler)
        {
            WindowsRuntimeMarshal.AddEventHandler(add, remove, handler);            
        }
    

    然后我可以通过反思来称之为:

        var closedAddMethod = GetAddEventHandlerMethodInfo(handlerType);
        closedAddMethod.Invoke(null, new[] {add, remove, actualDelegate});
    

    哪里 GetAddEventHandlerMethodInfo 是:

        private static MethodInfo GetAddEventHandlerMethodInfo(Type handlerType)
        {
            var rtMarshalType = typeof (AttachedCommand);
            var eventHandlerMethod = rtMarshalType.GetRuntimeMethods().Single(x => x.IsStatic && x.Name == "AddEventHandler");
            MethodInfo closedAddMethod = eventHandlerMethod.MakeGenericMethod(handlerType);
            return closedAddMethod;
        }
    

    这是有效的。正如问题中提到的 不起作用 在我自己的类上没有中间方法(即使用WindowsRunTimeMarshal.AddEventHandler作为 MethodInfo )。我无法解释为什么会出现这种情况,但这是一个可行的解决方法,所以如果其他人有同样的问题,我将把它留在这里。

    我不知道发生这种情况是因为微软试图防止一些意外的使用,还是其他什么——如果有人真的知道为什么原问题中描述的前一种方法不起作用; 留下答案。

        3
  •  0
  •   dmportella Balazs Tihanyi    12 年前

    编辑:由于平台安全性的原因,反射的方法不起作用,就像Silverlight确保应用程序在沙盒中运行一样,不会影响.net核心框架或利用该框架来推翻现有的安全措施。不幸的是,这会影响其他事情,比如功能的真正使用。Microsoft不会让您反映可能被利用的平台类和方法。

    不幸的是,像Silverlight一样,WinRT的某些部分被锁定,以将应用程序保存在沙盒中,但这并不是全部原因,但一些API仍然没有开发。

    Dris你是对的,你最好的办法是创建一个封装AddEventHandler的包装器方法。

    您可以在下面看到一个工作示例,创建对AssignEvent的写反射调用非常简单。本例假设您在屏幕上有一个名为“hellow”的文本块和一个名“button”的按钮。

    public MainPage()
    {
        this.InitializeComponent();
    
        RoutedEventHandler handler = (a, b) => this.Hellow.Text = "MUAHAHAH";
        // you can quite easily reflect the method below.
        this.AssignEvent<RoutedEventHandler>(this.button, "Click", handler);
    }
    
    private void AssignEvent<T1>(object instance, string eventName, T1 handler)
    {
        var runtimeEvent = instance.GetType().GetRuntimeEvent(eventName);
        var handlerType = runtimeEvent.EventHandlerType;
        Func<T1, EventRegistrationToken> add = (a) => { return (EventRegistrationToken)runtimeEvent.AddMethod.Invoke(instance, new object[] { a }); };
        Action<EventRegistrationToken> remove = (a) => { runtimeEvent.RemoveMethod.Invoke(runtimeEvent, new object[] { a }); };
    
        WindowsRuntimeMarshal.AddEventHandler<T1>(add, remove, handler);
    }
    

    方舟坤的答案是正确的,因为方法是用 Security Critical Attribute 它不能通过正常方式反映出来。这又是为了保护平台。

    正如他所说 security considerations for reflection 解释一切。