zoukankan      html  css  js  c++  java
  • [C#] 解决Silverlight反射安全关键(SecuritySafeCritical)时报“System.MethodAccessException: 安全透明方法 XXX 无法使用反射访问”的问题

    作者: zyl910

    一、缘由

    在Silverlight中使用反射动态访问时,经常遇到“System.MethodAccessException: 安全透明方法 XXX 无法使用反射访问……”等错误。
    其中最常见的情况,是因为这些成员具有 安全关键(SecuritySafeCritical)的特性(SecuritySafeCriticalAttribute)。但是这个现象是不对劲的——Silverlight里编写的代码都是透明代码(SecurityTransparent),按照规则不能调用 关键代码(SecurityCritical),但规则允许它调用安全关键代码(SecuritySafeCritical)。
    而且后来测试了硬编码来调用,发现此时(硬编码)是能够正常调用的。且在.NET framework等平台中,是能正常调用的。

    虽然硬编码能调用,但是很多时候是需要反射动态访问的。故需要想办法解决。

    二、以Assembly.FullName为例进行尝试

    2.1 硬编码

    例如 Assembly 的 FullName属性就行这种情况。在硬编码的情况下是能成功调用的,源码如下。

    Assembly assembly = typeof(Environment).Assembly;
    Debug.WriteLine(string.Format("#FullName:	{0}", assembly.FullName));
    

    2.2 用Type.InvokeMember进行反射调用

    可是当使用反射来访问该属性时,便会遇到异常了。例如通过 Type.InvokeMember 来反射获取FullName属性值,源码如下。

    Assembly assembly = typeof(Environment).Assembly;
    String membername = "FullName";
    Object obj = assembly;
    Type typ = obj.GetType();
    Object rt = typ.InvokeMember(membername, BindingFlags.GetProperty, null, obj, null);
    Debug.WriteLine(rt);
    

    详细的异常内容是——

    System.MethodAccessException: 安全透明方法 System.Reflection.Assembly.get_FullName() 无法使用反射访问 LibShared.LibSharedUtil.GetPropertyValue(System.Type, System.Object, System.String, Boolean ByRef)。
      位于 System.RuntimeMethodHandle.PerformSecurityCheck(Object obj, RuntimeMethodHandleInternal method, RuntimeType parent, UInt32 invocationFlags)
      位于 System.RuntimeMethodHandle.PerformSecurityCheck(Object obj, IRuntimeMethodInfo method, RuntimeType parent, UInt32 invocationFlags)
      位于 System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture, Boolean skipVisibilityChecks)
      位于 System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
      位于 System.RuntimeType.InvokeMember(String name, BindingFlags bindingFlags, Binder binder, Object target, Object[] providedArgs, ParameterModifier[] modifiers, CultureInfo culture, String[] namedParams)
      位于 System.Type.InvokeMember(String name, BindingFlags invokeAttr, Binder binder, Object target, Object[] args)
      位于 LibShared.LibSharedUtil.GetPropertyValue(Type typ, Object obj, String membername, Boolean & ishad)
    

    可见是 RuntimeMethodHandle.PerformSecurityCheck 在做安全检查时,抛出异常的。

    2.3 用 PropertyInfo.GetValue 进行反射调用

    既然Type.InvokeMember,那就再试试用 PropertyInfo.GetValue 进行反射调用吧。源码如下。

    Assembly assembly = typeof(Environment).Assembly;
    String membername = "FullName";
    Object obj = assembly;
    Type typ = obj.GetType();
    PropertyInfo pi = typ.GetProperty(membername);
    rt = pi.GetValue(obj, null);
    Debug.WriteLine(rt);
    

    发现该办法也是报异常。详细的异常内容是——

    System.MethodAccessException: 安全透明方法 System.Reflection.RuntimeAssembly.get_FullName() 无法使用反射访问 LibShared.LibSharedUtil.GetPropertyValue(System.Type, System.Object, System.String, Boolean ByRef)。
      位于 System.RuntimeMethodHandle.PerformSecurityCheck(Object obj, RuntimeMethodHandleInternal method, RuntimeType parent, UInt32 invocationFlags)
      位于 System.RuntimeMethodHandle.PerformSecurityCheck(Object obj, IRuntimeMethodInfo method, RuntimeType parent, UInt32 invocationFlags)
      位于 System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture, Boolean skipVisibilityChecks)
      位于 System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
      位于 System.Reflection.RuntimePropertyInfo.GetValue(Object obj, BindingFlags invokeAttr, Binder binder, Object[] index, CultureInfo culture)
      位于 System.Reflection.RuntimePropertyInfo.GetValue(Object obj, Object[] index)
      位于 LibShared.LibSharedUtil.GetPropertyValue(Type typ, Object obj, String membername, Boolean & ishad)
    

    它也是 RuntimeMethodHandle.PerformSecurityCheck 在做安全检查时,抛出异常的。
    还发现 System.Reflection.Assembly.get_FullName 实际是派生类(RuntimeAssembly)里覆写的方法。

    2.4 用 MethodInfo.Invoke 进行反射调用

    最后还可尝试用 MethodInfo.Invoke 进行反射调用吧。源码如下。

    Assembly assembly = typeof(Environment).Assembly;
    String membername = "FullName";
    Object obj = assembly;
    Type typ = obj.GetType();
    PropertyInfo pi = typ.GetProperty(membername);
    MethodInfo mi = pi.GetGetMethod();
    rt = mi.Invoke(obj, null);
    Debug.WriteLine(rt);
    

    发现该办法仍是报异常。详细的异常内容是——

    System.MethodAccessException: 安全透明方法 System.Reflection.Assembly.get_FullName() 无法使用反射访问 LibShared.LibSharedUtil.GetPropertyValue(System.Type, System.Object, System.String, Boolean ByRef)。
      位于 System.RuntimeMethodHandle.PerformSecurityCheck(Object obj, RuntimeMethodHandleInternal method, RuntimeType parent, UInt32 invocationFlags)
      位于 System.RuntimeMethodHandle.PerformSecurityCheck(Object obj, IRuntimeMethodInfo method, RuntimeType parent, UInt32 invocationFlags)
      位于 System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture, Boolean skipVisibilityChecks)
      位于 System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
      位于 System.Reflection.MethodBase.Invoke(Object obj, Object[] parameters)
      位于 LibShared.LibSharedUtil.GetPropertyValue(Type typ, Object obj, String membername, Boolean& ishad)
    

    它也是 RuntimeMethodHandle.PerformSecurityCheck 在做安全检查时,抛出异常的。

    三、反编译的分析

    这种不能反射是很奇怪的,于是决定反汇编看看代码。

    这些类型是在mscorlib.dll里定义的。可用ILSpy等工具进行反编译分析。

    // C:Program Files (x86)Reference AssembliesMicrosoftFrameworkSilverlightv5.0mscorlib.dll
    // mscorlib, Version=5.0.5.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e
    

    3.1 Assembly.FullName

    Assembly.FullName 反编译后的代码为 ——

    // System.Reflection.Assembly
    public virtual string FullName
    {
        get
        {
            throw new NotImplementedException();
        }
    }
    

    发现一个疑点——它没有安全关键(SecuritySafeCritical)的特性,那么它应该是透明(SecurityTransparent)的啊。怎么会报MethodAccessException异常呢?

    3.2 RuntimeAssembly.FullName

    RuntimeAssembly.FullName 反编译后的代码为 ——

    // System.Reflection.RuntimeAssembly
    public override string FullName
    {
        [SecuritySafeCritical]
        get
        {
            if (this.m_fullname == null)
            {
                string value = null;
                RuntimeAssembly.GetFullName(this.GetNativeHandle(), JitHelpers.GetStringHandleOnStack(ref value));
                Interlocked.CompareExchange<string>(ref this.m_fullname, value, null);
            }
            return this.m_fullname;
        }
    }
    

    原来安全关键(SecuritySafeCritical)的特性是在这里出现的。

    3.3 System.RuntimeMethodHandle

    System.RuntimeMethodHandle 反编译后的代码为 ——

    // System.RuntimeMethodHandle
    [SecurityCritical]
    [MethodImpl(MethodImplOptions.InternalCall)]
    internal static extern void PerformSecurityCheck(object obj, RuntimeMethodHandleInternal method, RuntimeType parent, uint invocationFlags);
    
    [SecurityCritical]
    internal static void PerformSecurityCheck(object obj, IRuntimeMethodInfo method, RuntimeType parent, uint invocationFlags)
    {
        RuntimeMethodHandle.PerformSecurityCheck(obj, method.Value, parent, invocationFlags);
        GC.KeepAlive(method);
    }
    

    它调了CLR中的内部代码(MethodImplOptions.InternalCall),无法反编译。不能再跟踪下去了。

    四、动态生成代码

    所有的反射用法都试过了,均无法成功访问这些属性。此时没路了吗?
    不,此时还可尝试动态生成代码的办法。因为之前硬编码时能调通。
    C#有2种动态生成代码的办法,分别是 IL Emit 与 Expression Tree。考虑到代码的可读性与可维护性,一般用Expression Tree比较好。

    4.1 初试用Expression Tree 动态访问属性

    以下是一个辅助函数,利用Expression Tree技术,将属性访问操作封装为一个委托。

            /// <summary>
            /// 根据 PropertyInfo , 创建 Func 委托.
            /// </summary>
            /// <param name="pi">属性信息.</param>
            /// <returns>返回所创建的 Func 委托.</returns>
            public static Func<object, object> CreateGetFunction(PropertyInfo pi) {
                MethodInfo getMethod = pi.GetGetMethod();
                ParameterExpression target = Expression.Parameter(typeof(object), "target");
                UnaryExpression castedTarget = getMethod.IsStatic ? null : Expression.Convert(target, pi.DeclaringType);
                MemberExpression getProperty = Expression.Property(castedTarget, pi);
                UnaryExpression castPropertyValue = Expression.Convert(getProperty, typeof(object));
                return Expression.Lambda<Func<object, object>>(castPropertyValue, target).Compile();
            }
    

    随后便可使用该函数,来动态调用属性。

    Assembly assembly = typeof(Environment).Assembly;
    String membername = "FullName";
    Object obj = assembly;
    Type typ = obj.GetType();
    PropertyInfo pi = typ.GetProperty(membername);
    Func< object, object> f = CreateGetFunction(pi);
    rt = f(obj);
    Debug.WriteLine(rt);
    

    可是,该办法还是遇到了异常。

    System.TypeAccessException: 方法“DynamicClass.lambda_method(System.Runtime.CompilerServices.Closure, System.Object)”访问类型“System.Reflection.RuntimeAssembly”的尝试失败。
       位于 lambda_method(Closure, Object)
       位于 LibShared.LibSharedUtil.GetPropertyValue(Type typ, Object obj, String membername, Boolean & ishad)
    

    4.2 解决Expression Tree法的问题

    难道真的没办法了吗?
    这时突然想起 RuntimeAssembly 是一个内部类(internal),而内部类应该是不能在程序集外访问的。
    于是修改了访问代码,通过基类 Assembly 来访问 FullName。

    Assembly assembly = typeof(Environment).Assembly;
    String membername = "FullName";
    Object obj = assembly;
    Type typ = typeof(Assembly);    // 强制指定基类。不能用 `obj.GetType()` ,因它返回的是 RuntimeAssembly 这个内部类。 
    PropertyInfo pi = typ.GetProperty(membername);
    Func< object, object> f = CreateGetFunction(pi);
    rt = f(obj);
    Debug.WriteLine(rt);
    

    此时终于能正常的读出FullName属性的值了。

    随后尝试反射时也强制指定基类(Assembly),但仍是报异常。可能是它内部限制了。
    而且还尝试了用Expression Tree访问关键代码(SecurityCritical)的内容,希望能突破安全限制。可是还是遇到了MethodAccessException异常,看来安全性很严格,没有漏洞。

    五、经验总结

    • 在 Silverlight中,安全关键(SecuritySafeCritical)的成员无法反射。而且当实际类中有安全关键(SecuritySafeCritical)特性时(如RuntimeAssembly),即使是通过安全透明的基类(如Assembly)来反射,也是不行的。
    • 当无法通过反射动态获取属性时,可考虑动态生成代码的方案(IL Emit、Expression Tree)。
    • 对于内部类(internal),是无法突破安全限制来访问的,即使是反射、动态生成代码也不行。此时可考虑通过基类来访问。
    • 对于关键代码(SecurityCritical),是无法突破安全限制来访问的,即使是反射、动态生成代码也不行。

    源码地址:

    https://github.com/zyl910/vscsprojects/tree/master/vs14_2015/Silverlight/TestSilverlight

    参考文献

  • 相关阅读:
    7.21 高博教育 数组 内存
    【基础扎实】Python操作Excel三模块
    PAT 甲级 1012 The Best Rank
    PAT 甲级 1011  World Cup Betting
    PAT 甲级 1010 Radix
    链式线性表——实验及提升训练
    循环程序设计能力自测
    链表应用能力自测
    PAT 甲级 1009 Product of Polynomials
    1008 Elevator (20分)
  • 原文地址:https://www.cnblogs.com/zyl910/p/cs_silverlight_reflect_safecritical.html
Copyright © 2011-2022 走看看