通过Reflection.Emit生成便携式类库

9
我正在编写一个编译器,使用System.Reflection.Emit API在磁盘上生成.NET程序集。编译器本身是构建在.NET 4.5上的,但生成的代码仅引用可移植类库中的类型。然而,当试图从Windows Phone 8项目引用生成的程序集时,Visual Studio会提示“无法将对高版本或不兼容的程序集的引用添加到项目中”。
打开反编译器中生成的程序集后,我发现它引用了两个PCLs以及mscorlib 4.0.0.0,而我理解PCL应该引用mscorlib 2.0.5.0。
是否有办法让System.Reflection.Emit API生成PCLs,或者我的唯一选择是迁移到Mono.Cecil?

你可能还想看看 IKVM.Reflection,它允许你针对不同版本的框架进行目标设置,而不仅仅是当前版本。 - svick
@svick 谢谢,我不知道 IKVM 有一个公共 Emit API。乍一看,Mono.Cecil 设计得更好。但是,我应该选择 IKVM.Reflection 的原因是什么? - Trillian
IKVM Reflection的API故意与普通Reflection非常相似。因此,如果您已经了解它,应该能够很快开始使用它。 - svick
1个回答

5
好的,我来回答这个问题。
我没有找到任何证据表明System.Reflection.Emit API可以生成引用另一个版本的mscorlib的程序集,而不是当前进程使用的版本。事实上,那些接受System.Type参数和其他反射对象的API,可能会添加对Type.Assembly属性查询结果的引用,该属性对应于正在使用的mscorlib的版本。
然而,可移植类库与System.Reflection.Emit生成的内容并没有太大的区别,因此可以在事后修补程序集以“使它们可移植”。免责声明:这需要熟悉PE文件格式,并可能具有未预料的副作用,但对我来说有效。
  • When generating the assembly, use AssemblyBuilder.SetCustomAttribute to add this attribute to the assembly:

    [System.Runtime.Versioning.TargetFrameworkAttribute(".NETPortable,Version=v4.0,Profile=Profile136", FrameworkDisplayName = ".NET Portable Subset")]
    
  • This is where it gets sketchy: after having called AssemblyBuilder.Save, open a read/write file stream to the generated assembly, walk through the PE, COFF, COM, CLI and metadata table headers to locate the AssemblyRef table row for mscorlib. Modify the referenced version of mscorlib to 2.0.5.0, add 0x100 to its flags ("retargetable") and update its public key token blob to 0x7CEC85D7BEA7798E.

请注意,如果您使用其他框架程序集,您可能也需要修补它们的引用(我没有测试过)。否则,就完成了!该程序集现在是可移植的,可以在Windows Phone项目中使用,例如。

(...或者只需使用Mono.Cecil / IKVM.Reflection ...)

编辑:我使用的代码可以在github上找到。这是一个巨大的黑客攻击,因此通常的免责声明适用,风险自负。


既然看起来你已经编写了修改汇编代码的程序,那么分享一下可能是有意义的。 - svick
@svick 我想分享,但这个东西有300多行,还有一堆结构体和枚举。不过,如果在stackoverflow上有私信功能的话,我很乐意把它分享给任何询问我的人。 - Trillian
1
你可以将它发布到Pastebin上。 - zneak
你只需要修改 mscorlib 的引用吗?难道不需要修改所有引用的系统程序集吗? - jbtule
@jbtule 嗯,说得好。在我的情况下,我只使用 mscorlib,所以我不必担心这个问题。我会更新帖子的。 - Trillian

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接