【问题标题】:Granting reflection permission to a dynamically created assembly向动态创建的程序集授予反射权限
【发布时间】:2011-07-18 22:37:57
【问题描述】:

我正在用 C# 编写一个简单的桌面客户端/服务器应用程序。出于自学目的,我为通过 tcp/ip 套接字连接在两个应用程序之间来回发送的消息(定义为类)构建了自己的序列化系统。系统在初始化时使用反射,通过发出IL,为每种消息类型构造序列化/反序列化方法。

该系统的第一个版本使用 DynamicMethod,将 true 传递给构造函数以允许生成的 IL(对消息类型中的任意字段进行操作)忽略访问权限。这奏效了,人们很高兴,但我对调试结果函数的不透明程度感到不满意。所以我决定放弃 DynamicMethod 并使用 *Builder 类来构造一个动态程序集,我可以选择将其保存到磁盘并使用 .NET Reflector 之类的工具进行检查。

我重构了系统,然后碰了壁。每当新的序列化函数之一尝试访问我的一种消息类型中的私有字段或方法时,我都会收到 FieldAccessException 或 MethodAccessException。经过多次谷歌搜索和咬牙切齿后,我想我已经将问题缩小到权限之一;特别是我认为我动态创建的程序集缺少相对于调用/构造程序集(所有反射类型所在的位置)的 ReflectionPermissionFlag.MemberAccess 权限。

不幸的是,我似乎无法弄清楚如何修改动态程序集创建过程,以使程序集具有反射回创建程序集的权限。 DefineDynamicAssembly 的权限参数似乎与限制权限有关,而不是授予权限,这给我们留下了证据参数。证据似乎神奇地转化为一组权限,但我找不到任何有用的例子或解释如何发生这种情况。

所以我的问题是:

(1) 我的假设是否正确,即我的问题是我的动态创建的程序集缺少权限?

(2) 如果是这样,作为调用程序集,我如何为我的动态程序集授予必要的权限?

当前动态程序集创建代码:

        AssemblyName assembly_name = new AssemblyName( "LCSerialization" );
        assembly_name.Version = new Version( 1, 0, 0, 0 );

        m_SerializationAssembly = current_domain.DefineDynamicAssembly( assembly_name, AssemblyBuilderAccess.RunAndSave );  // Fix me
        m_SerializationModule = m_SerializationAssembly.DefineDynamicModule( "MainModule", "LCSerialization.dll" );
        m_SerializationWrapperClass = m_SerializationModule.DefineType( "CSerializationWrapper", TypeAttributes.Public );

请注意,我的项目面向 .NET 3.5;文档声称 .NET 4.0 使用了不同的安全概念,并弃用了 DefineDynamicAssembly 中基于 Evidence/PemissionSet 的方法。

举个具体的例子,假设我有一个类:

[NetworkMessage]
public class CTestMessage
{
    public CTestMessage( int cheeseburgers )
    {
        m_CheeseBurgers = cheeseburgers
    }

    private int m_CheeseBurgers = 0;
}

然后我的序列化系统在初始化反射期间遇到这种情况时,大致最终会使用 type = typeof 执行以下操作(此处无法剪切和粘贴) (CTestMessage):

MethodBuilder serialization_builder = m_SerializationWrapperClass.DefineMethod( "Serialize_" + type.Name, 
                                                                                MethodAttributes.Public | MethodAttributes.Static, 
                                                                                null,
                                                                                new [] { type, typeof( BinaryWriter ) } );

ILGenerator s_il_gen = serialization_builder.GetILGenerator();
BindingFlags binding_flags_local_non_static = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly;

s_il_gen.Emit( OpCodes.Ldarg_1 ); // Eval Stack: BinaryWriter
s_il_gen.Emit( OpCodes.Ldarg_0 ); // Eval Stack: BinaryWriter, testmessage
s_il_gen.Emit( OpCodes.Ldfld, type.GetField( "m_CheeseBurgers", binding_flags_local_non_static ) ); // Eval Stack: BinaryWriter, int
s_il_gen.Emit( OpCodes.Callvirt, typeof( BinaryWriter ).GetMethod( "Write", new Type[] { typeof( Int32 ) } ) ); // Eval Stack:
s_il_gen.Emit( OpCodes.Ret );

后续执行该方法时,在Ldfld指令上抛出异常。

编辑:更多细节表明我所要求的应该是可能的。取上面的代码sn-p,但是把MethodBuilder换成DynamicMethod:

DynamicMethod serialization_builder = new DynamicMethod( "Serialize_" + type.Name, null, new [] { type, typeof( BinaryWriter ) }, true );

现在从 DynamicMethod 创建一个委托:

delegate void TestDelegate( CTestMessage, BinaryWriter );

TestDelegate test_delegate = serialization_builder.CreateDelegate( typeof( TestDelegate ) );

这个委托被 JITed 并正确执行,没有错误:

CTestMessage test_message = new CTestMessage( 5 );
BinaryWriter writer = new BinaryWriter( some_stream );
test_delegate( test_message, writer );

【问题讨论】:

    标签: c# .net reflection cil


    【解决方案1】:

    问题是该字段是私有的。如果您将其公开,则外部方法可以正常工作。尽管 DynamicMethod 是私有的,但它仍然有效,因为 CLR 显然允许模块内私有字段访问——来自 SSCLI,clsload.cpp@2659:

    // pCurrentClass can be NULL in the case of a global function
    // pCurrentClass it the point from which we're trying to access something
    // pTargetClass is the class containing the member we are trying to access
    // dwMemberAccess is the member access within pTargetClass of the member we are trying to access
    BOOL ClassLoader::CheckAccess(EEClass *pCurrentClass,
                                  Assembly *pCurrentAssembly,
                                  EEClass *pTargetClass,
                                  Assembly *pTargetAssembly,
                                  DWORD dwMemberAccess)
    {
        // we're trying to access a member that is contained in the class pTargetClass, so need to 
        // check if have access to pTargetClass itself from the current point before worry about 
        // having access to the member within the class
        if (! CanAccessClass(pCurrentClass,
                             pCurrentAssembly, 
                             pTargetClass, 
                             pTargetAssembly))
            return FALSE;
    
        if (IsMdPublic(dwMemberAccess))
            return TRUE;
    
        // This is module-scope checking, to support C++ file & function statics.
        if (IsMdPrivateScope(dwMemberAccess)) {
            if (pCurrentClass == NULL)
                return FALSE;
    
            _ASSERTE(pTargetClass);
    
            return (pCurrentClass->GetModule() == pTargetClass->GetModule());
        }
    

    要从外部访问私有字段,您可能必须使用反射,这几乎无法达到目的。

    编辑只是为了澄清一下,您发布的内容使用反射来创建程序集,但您生成的 IL 不使用反射来访问该字段 - 这是一个普通的旧直接字段访问,它会爆炸因为目标字段是外部和私有的。您必须发出本身使用 Type.GetField().GetValue() 的 IL,这毫无意义。

    【讨论】:

    • 感谢您的回答。在深入挖掘之后,我发现了这个 MSDN 链接:link,这似乎直截了当地说动态程序集不能具有非公共反射权限。无赖。
    【解决方案2】:

    是的,动态程序集不允许此类访问,无论是在 .NET 3.5 还是 4+ 中。我有同样的问题。我的解决方法是将实际发出 IL 的代码分解为一个采用 ILGenerator 的函数,并使用相同的其他参数调用它两次,一次(可选)使用来自动态程序集中的方法的 ILGenerator 我将保存到磁盘peverify/ildasm/等。一次使用来自 DynamicMethod 的 ILGenerator。这种方式相同的 IL 被发射到两种方法中。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-12-21
      • 1970-01-01
      • 2016-09-09
      • 2021-02-05
      • 1970-01-01
      • 1970-01-01
      • 2012-04-29
      相关资源
      最近更新 更多