向动态创建的程序集授予反射权限
我正在用 C# 编写一个简单的桌面客户端/服务器应用程序。出于自学目的,我为通过 tcp/ip 套接字连接在两个应用程序之间来回发送的消息(定义为类)构建了自己的序列化系统。系统在初始化时使用反射通过发出 IL 来为每种消息类型构造序列化/反序列化方法。
该系统的第一个版本使用 DynamicMethod,将 true 传递给构造函数,以允许生成的 IL(对消息类型中的任意字段进行操作)忽略访问权限。这奏效了,人们很高兴,但我对结果函数的调试是多么痛苦的不透明感到不满意。因此,我决定放弃 DynamicMethod 并使用 *Builder 类来构造动态程序集,我可以选择将其保存到磁盘并使用 .NET Reflector 等工具进行检查。
我重构了系统,然后碰壁了。每当新的序列化函数之一尝试访问我的一种消息类型中的私有字段或方法时,我都会收到 FieldAccessException 或 MethodAccessException。经过多次谷歌搜索和咬牙切齿后,我想我已经将问题缩小到权限之一;特别是我认为我动态创建的程序集缺少相对于调用/构造程序集(所有反射类型都位于其中)的 ReflectionPermissionFlag.MemberAccess 权限。
不幸的是,我似乎无法弄清楚如何修改动态程序集创建过程,以便程序集具有返回到创建程序集的反射权限。 DefineDynamicAssembly 的权限参数似乎与限制权限相关,而不是授予权限,这给我们留下了 Evidence 参数。证据似乎神奇地转化为一组权限,但我找不到任何有用的示例或解释来说明这是如何发生的。
所以我的问题是:
(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 指令上引发异常。
编辑:更详细地证明我所要求的应该是可能的。获取上面的代码片段,但将 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 ) );
该委托经过 JIT 处理并正确执行,没有错误:
CTestMessage test_message = new CTestMessage( 5 );
BinaryWriter writer = new BinaryWriter( some_stream );
test_delegate( test_message, writer );
I am writing a simple desktop client/server application in C#. For self-educational purposes, I built my own serialization system for the messages (defined as classes) sent back and forth between the two applications over a tcp/ip socket connection. The system uses reflection at initialization time to construct serialize/deserialize methods for each message type by emitting IL.
The first version of this system used DynamicMethod, passing in true to the the constructor to allow the generated IL (that's operating on arbitrary fields in the message type) to ignore access permissions. This worked and the people rejoiced, but I was dissatisfied with how painfully opaque debugging the resulting functions was. So I decided to ditch DynamicMethod and use the *Builder classes to construct a dynamic assembly which I could optionally save to disk and examine with a tool like .NET Reflector.
I refactored the system and then hit a bit of a brick wall. Any time one of the new serialization functions attempts to access a private field or method in one of my message types I get a FieldAccessException or MethodAccessException. After much googling and gnashing of teeth, I think I have narrowed the problem to one of permissions; in particular I think my dynamically created assembly is missing the ReflectionPermissionFlag.MemberAccess permission relative to the calling/constructing assembly (where all the reflected types are sitting).
Unfortunately, I can't seem to figure out how to modify the dynamic assembly creation process such that the assembly has reflection permission back into the creating assembly. The permissions parameters to DefineDynamicAssembly seem related to restricting permission, not granting it, which leaves us with the Evidence parameter. Evidence seems to magically translate into a set of permissions, but I can't find any useful examples or explanations for how this occurs.
So my questions are:
(1) Am I correct in my assumption that my problem is a lack of permission on my dynamically created assembly?
(2) If so, how do I, as the calling assembly, grant the necessary permission to my dynamic assembly?
The current dynamic assembly creation code:
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 );
Note that my project is targeting .NET 3.5; the documentation claims .NET 4.0 uses a different notion of security and deprecates the Evidence/PemissionSet-based methods in DefineDynamicAssembly.
To give a concrete example, suppose I had a class like:
[NetworkMessage]
public class CTestMessage
{
public CTestMessage( int cheeseburgers )
{
m_CheeseBurgers = cheeseburgers
}
private int m_CheeseBurgers = 0;
}
then my serialization system, upon encountering this during init reflection, would roughly end up doing (cut-and-paste isnt possible here) the following with 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 );
When the method is subsequently executed, the exception is thrown on the Ldfld instruction.
Edit: More detail demonstrating that what I'm asking for ought to be possible. Take the above code snippet, but replace MethodBuilder with DynamicMethod:
DynamicMethod serialization_builder = new DynamicMethod( "Serialize_" + type.Name, null, new [] { type, typeof( BinaryWriter ) }, true );
Now create a delegate from the DynamicMethod:
delegate void TestDelegate( CTestMessage, BinaryWriter );
TestDelegate test_delegate = serialization_builder.CreateDelegate( typeof( TestDelegate ) );
This delegate gets JITed and executes properly with no errors:
CTestMessage test_message = new CTestMessage( 5 );
BinaryWriter writer = new BinaryWriter( some_stream );
test_delegate( test_message, writer );
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
问题是该领域是私有的。如果将其公开,则外部方法可以正常工作。尽管 DynamicMethod 是私有的,但它仍然可以工作,因为 CLR 显然允许模块内私有字段访问 - 从 SSCLI,clsload.cpp@2659:
要从外部访问私有字段,您可能必须使用反射,这几乎违背了目的。
编辑只是为了澄清一下,您发布的内容使用反射来创建程序集,但是您生成的 IL 不使用反射来访问字段 - 这是一个普通的旧式直接字段访问,它会因为目标而爆炸字段是外部的和私有的。您必须发出 IL,它本身使用 Type.GetField().GetValue() ,这是毫无意义的。
The problem is that the field is private. If you make it public the external method works fine. The DynamicMethod works despite it being private because the CLR apparently allows intra-module private field access - from the SSCLI, clsload.cpp@2659:
To access private fields externally you probably have to use reflection which pretty much defeats the purpose.
Edit Just to clarify, what you posted uses reflection to create the assembly, but the IL you generate doesn't use reflection to access the field - that's a plain old direct field access, which explodes because the target field is external and private. You'd have to emit IL which itself uses Type.GetField().GetValue() which is pretty pointless.
是的,动态程序集不允许此类访问,无论是在 .NET 3.5 还是 4+ 中。我也有同样的问题。我的解决方法是将实际的 IL 发射代码分解为一个采用 ILGenerator 的函数,并使用其他相同的参数调用它两次,一次(可选)使用动态程序集中的方法中的 ILGenerator,我将保存到磁盘peverify/ildasm/等。一次使用来自 DynamicMethod 的 ILGenerator。这样,两种方法都会发出相同的 IL。
Yes, dynamic assemblies don't permit such access, whether in .NET 3.5 or 4+. I had the same problem. My workaround is to break out the actual IL-emitting code into a function taking an ILGenerator and to call it twice with other arguments the same, once (optionally) with an ILGenerator from a method in a dynamic assembly I'd save to disk to peverify/ildasm/etc. and once with an ILGenerator from a DynamicMethod. This way identical IL is emitted into both methods.