【Emit】关于System.MethodAccessException解决方案

    最近学习Emit,在使用Emit动态生成对象时碰到一些“蛋疼”的问题,如下:
1、安全透明方法“XXX.XX()”尝试访问安全关键方法“YYY.YY()”失败。
2、方法“XXX(System.Object[])”尝试访问方法“YYY.ctor()”失败。
    上面两个都是System.MethodAccessException异常。刚开始一看,懵B了。这提示信息也太少了吧。没办法就这么残酷,只好硬着头皮,顺藤摸瓜找原因。功夫不负有心人,终于找到原因了,阿弥陀佛!
说道造成System.MethodAccessException异常的原因,就得先说一下反射安全
    从 .NET Framework 4 版开始,仅受信任的代码才能使用反射访问安全关键成员。而且,也只有受信任的代码才能使用反射访问非公共成员。最后,使用反射访问安全关键成员的代码必须具有安全关键成员要求的任何权限。
那什么是安全关键成员呢?满足下面一下条件之一的:具有 SecurityCriticalAttribute;属于一个具有 SecurityCriticalAttribute 的类型;或者位于一个安全关键程序集中;
    访问安全关键成员的规则如下所示:

  • 透明代码不能使用反射来访问安全关键成员,即使代码是完全受信任的也是如此。 将引发 MethodAccessException、FieldAccessException 或 TypeAccessException。
  • 以部分信任方式运行的代码将被视为透明的。

    无论安全关键成员是由已编辑代码直接访问,还是通过使用反射访问,这些规则都是相同的。 
    公共语言运行时将根据多个因素来确定类型或成员的透明度级别,这些因素包括程序集的信任级别和应用程序域的信任级别。 反射提供 IsSecurityCritical、IsSecuritySafeCritical 和 IsSecurityTransparent 属性以使您能够发现类型的透明度级别。 下表显示了这些属性的有效组合。

安全级别 IsSecurityCritical IsSecuritySafeCritical IsSecurityTransparent
安全级别高的 true false false
安全关键的 true true false
透明的 false false true

MethodBase 、FieldInfo、TypeBuilder、MethodBuilder 和 DynamicMethod 类都有相似的属性。
    若要使用反射来调用根据公共语言运行时的可访问性规则不可访问的成员,代码中必须授予带有 ReflectionPermissionFlag.MemberAccess 标志的 ReflectionPermission。需要注意的是默认情况下,.NET安全策略拒绝向源自 Internet 的代码授予此权限。 所以也不要向源自 Internet 的代码授予此权限。
    理解了上面的概念,System.MethodAccessException异常也就迎刃而解了,解决方案:
1、要Emit的代码中有非公开的成员。我查看一下代码,果然有一个类是internal的。这个简单把它改成public就可以了。
2、本来想加上ReflectionPermissionFlag.MemberAccess ,但总感觉不太好,破坏了程序集的安全性。经过不断的测试,找到了一个完美的解决方案,下面举例说明:
首先,我原先动态生成对象的方法时这样写的

  private static CreateOjectHandler CreateHandler(Type type, Type[] paramsTypes)
        {

            ConstructorInfo constructor = type.GetConstructor(paramsTypes);
            DynamicMethod method = new DynamicMethod("DynamicCreateOject", typeof(object),
               new Type[] { typeof(object[]) }, typeof(FastObjectCreater).Module);


            ILGenerator il = method.GetILGenerator();

            for (int i = 0; i < paramsTypes.Length; i++)
            {
                il.Emit(OpCodes.Ldarg_0);
                il.Emit(OpCodes.Ldc_I4, i);
                il.Emit(OpCodes.Ldelem_Ref);
                if (paramsTypes[i].IsValueType)
                {
                    il.Emit(OpCodes.Unbox_Any, paramsTypes[i]);
                }
                else
                {
                    il.Emit(OpCodes.Castclass, paramsTypes[i]);
                }
            }
            il.Emit(OpCodes.Newobj, constructor);
            il.Emit(OpCodes.Ret);

            return (CreateOjectHandler)method.CreateDelegate(typeof(CreateOjectHandler));
        }

上面的代码放到项目A中,然后在项目B中调用

    class Program
    {
        static void Main(string[] args)
        {
            var chinese = FastObjectCreater.CreateInstance<Chinese>();
            chinese.SayHello();

            Console.ReadKey();
        }

        public class Chinese
        {
            public Chinese()
            {
            }

            public Chinese(string name)
            {
            }

            public void SayHello()
            {
                Console.WriteLine("你好!!!");
            }

            public override string ToString()
            {
                return "中国人";
            }
        }

    }

    上面代码在项目B中定义。接着,运行项目B就会出现问题2的异常。
    请注意这段代码 DynamicMethod method = new DynamicMethod("DynamicCreateOject", typeof(object),new Type[] { typeof(object[]) }, typeof(FastObjectCreater).Module); 它就是罪魁祸首。因为DynamicCreateOject方法生成在项目A中,而且项目B的Program方法是私有的,Chinese类是它的内部类,所以在Main方法中访问Chinese类是私有的级别。.NET 4不允许非受信任的代码用反射访问非公共成员,所以在执行FastObjectCreater.CreateInstance<Chinese>();代码时就引发了异常。所以解决的方法很简单,只要把DynamicMethod类的module参数改为constructor.DeclaringType.Module。这就把原来的属于项目A的DynamicCreateOject方法变成项目B的DynamicCreateOject方法,也就不存在非信任代码的问题。
    OK,终于写完了,回家喽!

posted @ 2016-07-26 19:51  Khadron  阅读(2446)  评论(0编辑  收藏  举报