使用Mono.Cecil替换对类型/命名空间的引用
背景(不必要,令人困惑,只为好奇)
我正在使用Unity3D for Mobile的免费版本,它不允许我在移动设备上使用System.Net.Sockets
命名空间。 问题是我正在使用引用System.Net.Sockets
已编译的.dll
库(即IKVM)。 我实际上并没有使用引用System.Net.Sockets
IKVM中的类,因此我没有购买3000美元的Unity Pro移动许可证,而是创建了一个名为dudeprgm.Net.Sockets
的Sockets
命名空间的存根库, dudeprgm.Net.Sockets
替换了所有存根的类和方法(我使用Mono源代码完成此操作)。
我的问题
我需要将dll中的所有System.Net.Sockets.*
引用替换为dudeprgm.Net.Sockets.*
。 我知道这样的事情是可能的并且由其他人完成 (参见下面的编辑 ,在页面底部) 。 我想知道自己该怎么做。
我能够使用Mono.Cecil提出以下代码。
它遍历所有IL指令,检查操作数是否为InlineType
,然后检查内联类型是否是System.Net.Sockets
一部分,然后将其重命名为dudeprgm.Net.Sockets
并写入。 **我不确定这是否是在Mono.Cecil中进行“查找和替换”的正确方法。 问题是,这并没有捕获所有Sockets
使用(见下文)。
private static AssemblyDefinition stubsAssembly; static void Main(string[] args) { AssemblyDefinition asm = AssemblyDefinition.ReadAssembly(args[0]); stubsAssembly = AssemblyDefinition.ReadAssembly("Socket Stubs.dll"); // ... // Call ProcessSockets on everything // ... asm.Write(args[1]); } /* * This will be run on every property, constructor and method in the entire dll given */ private static void ProcessSockets(MethodDefinition method) { if (method.HasBody) { Mono.Collections.Generic.Collection instructions = method.Body.Instructions; for (int i = 0; i < instructions.Count; i++) { Instruction instruction = instructions[i]; if (instruction.OpCode.OperandType == OperandType.InlineType) { string operand = instruction.Operand.ToString(); if (operand.StartsWith("System.Net.Sockets")) { Console.WriteLine(method.DeclaringType + "." + method.Name + "(...) uses type " + operand); Console.WriteLine("\t(Instruction: " + instruction.OpCode.ToString() + " " + instruction.Operand.ToString() + ")"); instruction.Operand = method.Module.Import(stubsAssembly.MainModule.GetType("dudeprgm.Net.Sockets", operand.Substring(19))); Console.WriteLine("\tReplaced with type " + "dudeprgm.Net.Sockets" + operand.Substring(18)); } } } } }
它工作正常,但只捕获“简单”指令。 用ildasm
反编译,我可以看到它取代了这里的类型:
box ['Socket Stubs'/*23000009*/]dudeprgm.Net.Sockets.SocketOptionLevel/*01000058*/
但它没有抓住这些“复杂”的指示:
callvirt instance void [System/*23000003*/]System.Net.Sockets.Socket/*0100003F*/::SetSocketOption(valuetype [System/*23000003*/]System.Net.Sockets.SocketOptionLevel/*01000055*/, valuetype [System/*23000003*/]System.Net.Sockets.SocketOptionName/*01000056*/, int32) /* 0A000094 */
现在.dll
是dudeprgm.Net.Sockets
和System.Net.Sockets
引用的混乱。
我很确定这种情况正在发生,因为我只是在改变OperandType.InlineType
,但我不确定如何做到这一点。 我试过到处寻找,但在我看来,Mono.Cecil无法将操作数设置为string
,所有内容似乎都必须使用Cecil API完成( https://stackoverflow.com/a/ 7215711/837703 )。
(对不起,如果我使用的是不正确的术语,我对IL一般都是新手。)
题
如何替换System.Net.Sockets
出现在Mono.Cecil中的所有位置,而不仅仅是操作数是InlineType
? 我真的不想查看Cecil中的每一个OperandType
,我只是在Cecil中寻找一些查找和替换方法,我不必自己使用普通的IL。
编辑:(也没必要,令人困惑,只为好奇)
这个人能够以25美元的价格做类似的事情: http : //www.reddit.com/r/Unity3D/comments/1xq516/good_ol_sockets_net_sockets_for_mobile_without/ 。
自动修补程序工具,用于检测和修复脚本和.dll中的套接字使用情况。
…
“DLL使用Mono.Cecil进行修补。…
您可以查看https://www.assetstore.unity3d.com/en/#!/content/13166上的第二个屏幕截图,看看它是否可以替换命名空间。
该库不符合我的需要,因为1)它不会重命名为我想要的命名空间( dudeprgm.Net.Sockets
),2)它重命名的库不支持所有的System.Net.Sockets
类IKVM需要,因为IKVM几乎使用每个套接字类而且3)它的成本是25美元而且我真的不想购买我不会使用的东西。 我只想表明在Mono.Cecil中替换名称空间/类型引用是可能的。
[01]类似的问题
用另一个dll(以及其中的类型)替换对dll(和其中的类型)的引用的问题在技术上类似于已知的问题
谷歌:“c#为第三方组装添加强名”
在这个问题中,您希望您的应用程序由强名称签名并可能安装到GAC或Ngen-ed中,但您的应用程序依赖于在编译时没有添加强名称的旧版第三方库,这打破了要求一个强大的命名程序集只能使用强名称程序集。 您没有第三方库的源代码,只有二进制文件,因此您无法重新编译它(==“简化描述”)
有几种可能的解决方案,最常见的是:
[02]类似问题的解决方案#1
您可以使用ildasm
/ ilasm
round trip
,将所有二进制文件转换为文本forms,将所有引用更改为强名称等效(递归)并将文本转换回代码。 示例: http : //buffered.io/posts/net-fu-signing-an-unsigned-assembly-without-delay-signing/和https://stackoverflow.com/a/6546134/2626313
[03]类似问题的解决方案#2
您可以使用已编写的工具来解决此问题,例如: http : //brutaldev.com/post/2013/10/18/NET-Assembly-Strong-Name-Signer
[04]类似问题的解决方案#3
您可以创建一个精心设计的工具来满足您的确切需求。 有可能,我已经完成了,花了几个星期,代码重达几千行代码。 对于我已经重复使用的最脏的工作(稍作修改)主要来自(无序)源代码:
- ApiChange.Api.Introspection.CorFlagsReader.cs
- GACManagerApi.Fusion
- brutaldev / StrongNameSigner
- icsharpcode / ILSpy
- Mono.Cecil.Binary
- Mono.Cecil.Metadata
- Mono.ResGen
- Ricciolo.StylesExplorer.MarkupReflection
- 并阅读http://referencesource.microsoft.com/
[05]你的问题
虽然您所描述的问题看起来只是我上面描述的问题的一部分,但如果您想使用GAC安装,则可能会出现同样的问题,而GAC安装又需要强名称签名。
我给你的建议是
[06]你的问题的解决方案#1
提供最简单的解决方案[02]尝试使用Mono软件包中的ilasm / ildasm工具,而不是微软.NET Framework提供的工具(.NET Framework 4.5中的Microsoft Resgen已损坏且不能往返resx格式, Ildasm输出不能正确处理非ASCII字符等。虽然你无法修复微软破解的闭源,你可以修复Mono的开源,但我没有。)
[07]你的问题的解决方案#2
如果[06]不起作用,那么研究(调试)→ ILSpy ←并研究各种命令行工具的Mono文档,做你需要的东西及其来源 – 你会看到他们如何使用Mono.Cecil库
如果您需要validation强名称或甚至签名的程序集(篡改它们将使签名无效)或删除签名等。您将深入研究代码的时间超过简单的Stack Overflow应答可以描述的内容。
[08]你的问题的解决方案#3
潜伏在ILMerge的周围,以及如何为您提供更简单的解决方案
[09]你的问题的解决方案#4
另一个更简单的解决方案可能是(如果IKVM支持它)挂钩AssemblyResolve
事件,您可以将dll名称重新映射到物理dll,例如从完全不同的文件或资源流等加载。如旧的Stack Overflow问题嵌入DLL的几个答案中所示在已编译的可执行文件
(编辑#1:评论后)
[10]你的问题的解决方案#5
如果您或多或少的一般性问题实际上归结为“如何使IKVM.dll使用我的套接字类而不是命名空间System.Net.Sockets中的那些”,那么非常简单的解决方案可能是:
使用http://www.ikvm.net/download.html上提供的源代码编译和部署您自己的自定义版本的IKVM.dll – 不需要二进制Mono.Cecil魔术。
由于所有代码都是开放的,因此应该可以找到并将指向命名空间System.Net
所有引用重定向到dudeprgm.Net
- [10.1]获取IKVM源代码和所有其他先决条件,并确保您可以编译工作IKVM.dll
- [10.2]将
dudeprgm.Net.cs
项目添加到解决方案中 - [10.3]在所有源文件中查找并删除所有
using System.Net
- [10.4]在所有源文件中,全文使用
dudeprgm.Net
查找并替换看起来像System.Net
的dudeprgm.Net
- [10.5]编译。 当编译器抱怨缺少符号(之前在System.Net命名空间中)时,然后将其添加到存根文件中。 转到[10.5]
- [10.6]如果上述步骤在2小时后没有解决为“构建正常”,那么考虑另一种解决方案(或者睡一觉)
- [10.7]检查IKVM许可证( http://sourceforge.net/p/ikvm/wiki/License/ )是否有必须更改/声明/确认的内容,因为原始源代码已被修改
(编辑#2:评论后)
[11]你的问题的解决方案#6
如果您选择track [04]并使用文本文件和ilasm/ildasm
工具(样式[02] )似乎效率不高,那么下面是我的自动强名称签名者的关键相关部分,它使用Mono将程序集引用更改为其他引用。塞西尔。 代码按原样粘贴(没有代码行之前,之后和周围)以适合我的forms。 读取键: a is Mono.Cecil.AssemblyDefinition
, b implements Mono.Cecil.IAssemblyResolver
, b
实例中的键方法是AssemblyDefinition Resolve(AssemblyNameReference name)
,它将所需的DLL名称转换为对AssemblyDefinition.ReadAssembly(..)
调用。 我不需要解析指令流,重新映射汇编引用就足够了(如果需要,我可以在这里粘贴我的代码中的其他几个部分)
/// /// Fixes references in assembly pointing to other assemblies to make their PublicKeyToken-s compatible. Returns true if some changes were made. /// Inspiration comes from https://github.com/brutaldev/StrongNameSigner/blob/master/src/Brutal.Dev.StrongNameSigner.UI/MainForm.cs /// see call to SigningHelper.FixAssemblyReference /// /// public static bool FixStrongNameReferences(IEngine engine, string assemblyFile, string keyFile, string password) { var modified = false; assemblyFile = Path.GetFullPath(assemblyFile); var assemblyHasStrongName = GetAssemblyInfo(assemblyFile, AssemblyInfoFlags.Read_StrongNameStatus) .StrongNameStatus == StrongNameStatus.Present; using (var handle = new AssemblyHandle(engine, assemblyFile)) { AssemblyDefinition a; var resolver = handle.GetAssemblyResolver(); a = handle.AssemblyDefinition; foreach (var reference in a.MainModule.AssemblyReferences) { var b = resolver.Resolve(reference); if (b != null) { // Found a matching reference, let's set the public key token. if (BitConverter.ToString(reference.PublicKeyToken) != BitConverter.ToString(b.Name.PublicKeyToken)) { reference.PublicKeyToken = b.Name.PublicKeyToken ?? new byte[0]; modified = true; } } } foreach (var resource in a.MainModule.Resources.ToList()) { var er = resource as EmbeddedResource; if (er != null && er.Name.EndsWith(".resources", StringComparison.OrdinalIgnoreCase)) { using (var targetStream = new MemoryStream()) { bool resourceModified = false; using (var sourceStream = er.GetResourceStream()) { using (System.Resources.IResourceReader reader = new System.Resources.ResourceReader(sourceStream)) { using (var writer = new System.Resources.ResourceWriter(targetStream)) { foreach (DictionaryEntry entry in reader) { var key = (string)entry.Key; if (entry.Value is string) { writer.AddResource(key, (string)entry.Value); } else { if (key.EndsWith(".baml", StringComparison.OrdinalIgnoreCase) && entry.Value is Stream) { Stream newBamlStream = null; if (FixStrongNameReferences(handle, (Stream)entry.Value, ref newBamlStream)) { writer.AddResource(key, newBamlStream, closeAfterWrite: true); resourceModified = true; } else { writer.AddResource(key, entry.Value); } } else { writer.AddResource(key, entry.Value); } } } } } if (resourceModified) { targetStream.Flush(); // I'll swap new resource instead of the old one a.MainModule.Resources.Remove(resource); a.MainModule.Resources.Add(new EmbeddedResource(er.Name, resource.Attributes, targetStream.ToArray())); modified = true; } } } } } if (modified) { string backupFile = SigningHelper.GetTemporaryFile(assemblyFile, 1); // Make a backup before overwriting. File.Copy(assemblyFile, backupFile, true); try { try { AssemblyResolver.RunDefaultAssemblyResolver(Path.GetDirectoryName(assemblyFile), () => { // remove previous strong name https://groups.google.com/forum/#!topic/mono-cecil/5If6OnZCpWo a.Name.HasPublicKey = false; a.Name.PublicKey = new byte[0]; a.MainModule.Attributes &= ~ModuleAttributes.StrongNameSigned; a.Write(assemblyFile); }); if (assemblyHasStrongName) { SigningHelper.SignAssembly(assemblyFile, keyFile, null, password); } } catch (Exception) { // Restore the backup if something goes wrong. File.Copy(backupFile, assemblyFile, true); throw; } } finally { File.Delete(backupFile); } } } return modified; }
[12]轮到你了
这实际上是@ xmojmer答案的延伸。
我写了一个小的bash
脚本来自动化xmojmer的选项[02] :
# vsilscript.sh # Usage: # Open Cygwin # . vsilscript.sh # output in "Sockets_Patched" subdirectory nodosfilewarning=true # just in case cygwin complains ILASM_PATH="/cygdrive/c/Windows/Microsoft.NET/Framework64/" # modify for your needs ILASM_PATH="$ILASM_PATH"$(ls "$ILASM_PATH" -v | tail -n 1) ILDASM_PATH="/cygdrive/c/Program Files (x86)/Microsoft SDKs/Windows/v8.1A/bin/NETFX 4.5.1 Tools" # modify for your needs PATH="$ILDASM_PATH:$ILASM_PATH:$PATH" base=$(echo $1 | sed "s/\.dll//g") ildasm /out=$base".il" /all /typelist $base".dll" cp $base".il" "tempdisassembled.il" cat "tempdisassembled.il" | awk ' BEGIN { print ".assembly extern socketstubs { }"; } { gsub(/\[System[^\]]*\]System\.Net\.Sockets/, "[socketstubs]dudeprgm.Net.Sockets", $0); print $0; } END { print "\n"; } ' 1> $base".il" #change the awk program to swap out different types rm "tempdisassembled.il" mkdir "Sockets_Patched" # read -p "Press Enter to assemble..." ilasm /output:"Sockets_Patched/"$base".dll" /dll /resource:$base".res" $base".il"
如果您的计算机是32位或在不同位置具有ilasm
和ildasm
,请修改ILASM_PATH
和ILDASM_PATH
变量。 此脚本假定您在Windows上使用Cygwin 。
修改发生在awk
命令中。 它通过添加添加对我的存根库(称为socketstubs.dll
,并存储在同一目录中)的引用。
.assembly extern sockectstubs { }
在反汇编的IL代码的开头。 然后,对于每一行,它查找[System]System.Net.Sockets
并用[socketstubs]dudeprgm.Net.Sockets
替换它们。 它在IL代码的末尾添加了换行符,否则ilasm
将不会根据文档重新组合它:
如果.il源文件中的最后一行代码没有尾随空格或行尾字符,则编译可能会失败。
最终修补的.dll将放在一个名为“Sockets_Patched”的新目录中。
您可以修改此脚本以交换任何dll中的任何两个名称空间:
- 将
[System[^\]]
的System
替换为包含旧命名空间的任何程序集 - 用您的旧命名空间替换
System\.Net\.Sockets
并在每个句点前放置一个反斜杠 - 将脚本中的所有
socketstubs
替换为包含新命名空间的库 - 用您的新命名空间替换
dudeprgm.Net.Sockets
您可以将套接字对象包装在一个接口中,然后dependency injection您真正想要的实现。 代码可能有点乏味,但您可以在不直接引用System.Net.Sockets的情况下将套接字交换到您想要的任何内容。