使用Mono.Cecil替换对类型/名称空间的引用 [英] Replace references to a type/namespace using Mono.Cecil

查看:144
本文介绍了使用Mono.Cecil替换对类型/名称空间的引用的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我使用的是Unity 3D for Mobile的免费版本,它不允许我使用移动设备上的 System.Net.Sockets 命名空间。问题是我正在使用引用 System.Net.Sockets .dll 库(即IKVM)。 c>。我实际上并未在IKVM中使用引用 System.Net.Sockets 的类,因此我制作了一个存根库,而不是购买$ 3000 Unity Pro移动许可证。 Sockets 命名空间 dudeprgm.Net.Sockets 的命名空间仅用存根替换了所有类和方法(我使用Mono代码)。


I'm using the free version of Unity3D for Mobile and it doesn't allow me to use the System.Net.Sockets namespace on mobile devices. The problem is that I'm using a compiled .dll library (namely, IKVM) that references the System.Net.Sockets. I'm not actually using the classes in IKVM that references that references System.Net.Sockets, so instead of buying the $3000 Unity Pro mobile licenses, I made a stub library of the Sockets namespace called dudeprgm.Net.Sockets that just replaces all the classes and methods with stubs (I did this using the Mono source code).


我需要替换所有<$ c我的dll中的$ c> System.Net.Sockets。* 引用 dudeprgm.Net.Sockets。* 我知道这样的事情是可能的,并且可以由其他人完成 (请参阅下面的 EDIT ,在页面底部)。我想知道自己怎么做。

I need to replace all System.Net.Sockets.* references in my dlls to dudeprgm.Net.Sockets.*. I know that something like this is possible and done by other people(See EDIT below, at the bottom of the page). I would like to know how to do it myself.

我能够使用Mono.Cecil给出以下代码。

它遍历所有IL指令,检查操作数是否为 InlineType ,然后检查内联类型是否为 System.Net.Sockets ,然后将其重命名为 dudeprgm.Net.Sockets 并将其写入。 **我不确定这是否是在Mono.Cecil中进行查找和替换的正确方法。问题是,这不能捕获所有 Sockets 的用法(见下文)。

I was able to come up with the following code using Mono.Cecil.
It goes through all the IL instructions, checks if the operand is an InlineType, then checks if the inline type is part of System.Net.Sockets, then renames it to dudeprgm.Net.Sockets and writes it. **I'm not sure if this is the right way to go about "finding-and-replacing" in Mono.Cecil. Problem is, this doesn't catch all Sockets usages (see below).

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<Instruction> 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 反编译后,我可以看到它替换了以下类型:

It works fine, but only catches "simple" instructions. Decompiled with ildasm, I can see where it replaced the types like here:

box        ['Socket Stubs'/*23000009*/]dudeprgm.Net.Sockets.SocketOptionLevel/*01000058*/

但是没有捕获到这些复杂指令:

But it didn't catch these "complex" instructions:

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 s,但是我不确定其他方法。我曾尝试到处环顾四周,但在我看来像Mono.Cecil无法将操作数设置为 string ,所有事情似乎都必须使用Cecil完成仅限API( https://stackoverflow.com/a/7215711/837703 )。

I'm pretty sure that this is happening because I'm only changing OperandType.InlineTypes, but I'm not sure on how else to do this. I've tried looking around everywhere, but it seems to me like Mono.Cecil has no way to set operands to a string, everything seems to have to be done using the Cecil API only (https://stackoverflow.com/a/7215711/837703).

(很抱歉,如果我使用的术语不正确,我一般对IL还是陌生的。)

(Sorry if I'm using incorrect terms, I'm pretty new to IL in general.)

如何替换 all 中出现 System.Net.Sockets 的位置Mono.Cecil,而不仅仅是操作数是 InlineType 的地方吗?我真的不想遍历Cecil中的每个 OperandType ,我只是在Cecil中寻找一些 findand-replace 方法

How can I replace all places where System.Net.Sockets appear in Mono.Cecil, rather than just where the operand is an InlineType? I don't really want to go through every OperandType there is in Cecil, I was just looking for some find-and-replace method in Cecil where I wouldn't have to work with plain IL myself.

编辑:(也是不必要的,令人困惑的,并且仅出于好奇)

这个人花了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 并看到它说可以替换名称空间。

You can go look at the second screenshot at https://www.assetstore.unity3d.com/en/#!/content/13166 and see that it says that it can replace namespaces.

该库不符合我的需要,因为1)它没有重命名为我想要的名称空间( dudeprgm.Net.Sockets ),2)它重命名的库不支持IKVM需要的所有 System.Net.Sockets 类,因为IKVM几乎使用了每个Sockets类,并且3)它的价格为25美元,真的不想买我不会用的东西。我只是想表明可以在Mono.Cecil中替换名称空间/类型引用。

That library doesn't fit my needs, because 1) it doesn't rename to the namespace I want (dudeprgm.Net.Sockets), 2) the library that it is renaming to does not support all the System.Net.Sockets classes that IKVM needs, because IKVM uses pretty much every Sockets class and 3) it costs $25 and I don't really want to buy something that I'm not going to use. I just wanted to show that replacing namespace/type references in Mono.Cecil is possible.

推荐答案

[01]类似的问题



您用另一个dll(及其内部类型)替换对dll(及其内部类型)的引用的问题在技术上类似于称为

[01] Similar problem

Your problem with replacing references to a dll (and types within) with another dll (and types within) is technically similar to problem known as


Google: c#为第三方聚会添加强名

Google: "c# add strong name to 3rd party assembly"

您想要使用强名签名的应用程序并可能安装到GAC或Ngen-ed中的问题,但是您的应用程序依赖于遗留的第三方库,该库在编译时没有添加强名,这打破了要求:强命名程序集只能使用强命名程序集。您没有第三方库的源代码,只有二进制文件,因此您无法重新编译(==简化描述)

In this problem you want to have your application signed by strong name and possibly installed into GAC or Ngen-ed, but your application depends on a legacy 3rd party library which does not have a strong name added at compile time, which breaks the requirement saying that a strong named assembly can use only strong named assemblies. You don't have source code for the 3rd party library, only binaries, so you can't recompile it (== "simplified description")

有几种解决方案可能,最典型的3个是:

There are several solutions possible, 3 most typical being:

您可以使用 ildasm / ilasm 往返,将所有二进制文件转换为文本形式,将所有引用更改为它们的强名称等效项(递归),然后将文本转换回代码。示例: http://buffered.io / posts / net-fu-signing-an-unsigned-assembly-without-delay-signing / https:// stackoverflow.com/a/6546134/2626313

You can use ildasm/ilasm round trip, convert all binaries into text form, change all references into their strong name equivalents (recursively) and turn text back into code. Examples: http://buffered.io/posts/net-fu-signing-an-unsigned-assembly-without-delay-signing/ and https://stackoverflow.com/a/6546134/2626313

您可以使用已经编写的工具来完全解决此问题,例如: http://brutaldev.com/post/2013/10/18/NET-Assembly-Strong-Name-Signer

You can use tools already written to solve exactly this problem, example: http://brutaldev.com/post/2013/10/18/NET-Assembly-Strong-Name-Signer

您可以创建旨在满足自己确切需求的工具。我可能这样做,花了几周时间,代码重了几千行代码。对于最肮脏的工作,我重用了(进行了一些细微修改)主要来自(无序)的源代码:

You can create a tool crafted to match your exact needs. It is possible, I have done it, it took several weeks and the code weighs several thousand lines of code. For the most dirty work I have reused (with some slight modifications) mainly source code from (unordered):


  • 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 /

  • ApiChange.Api.Introspection.CorFlagsReader.cs
  • GACManagerApi.Fusion
  • brutaldev/StrongNameSigner
  • icsharpcode/ILSpy
  • Mono.Cecil.Binary
  • Mono.Cecil.Metadata
  • Mono.ResGen
  • Ricciolo.StylesExplorer.MarkupReflection
  • and reading http://referencesource.microsoft.com/

尽管您描述的问题只是我上面描述的一个子集,但是如果您要使用GAC安装,而这又需要强大的名称签名,那么它很可能是同样的问题。

Although the problem you have described looks like just a subset of what I'm describing above, it may well turn out to be the same problem if you want to use GAC installation which in turn requires strong name signing.

我对您的建议是

给最简单的解决方案 [02] ,尝试使用Mono包中的ilasm / ildasm工具(而不是Microsoft .NET Framework提供的工具)来最大程度地减少麻烦(Microsoft .NET Framework 4.5中的Resgen损坏了,不能四舍五入。 Trip resx格式,Ildasm输出不能正确处理非ASCII字符等。虽然无法修复Microsoft损坏的封闭源代码,但可以修复Mono的开源代码,但我不必这样做。)

Give the easiest solution [02] a tryand to get into least trouble use ilasm/ildasm tools from the Mono package not the ones provided by Microsoft's .NET Framework (Microsoft's Resgen in .NET Framework 4.5 is broken and cannot round-trip resx format, Ildasm output does not handle non-ASCII characters correctly etc. While you can't fix Microsoft's broken closed source, you can fix Mono's open source but I did not have to.)

如果 [06] 对您不起作用,则进行研究(调试) → ILSpy ←并研究Mono文档,了解各种命令行工具的用途以及其来源-您将了解如何确切地说,他们使用Mono.Cecil库

If [06] does not work for you then study (debug) → ILSpy ← and study Mono documentation for various command line tools doing what you need and their sources - you'll see how exactly they use the Mono.Cecil library

如果您需要验证强命名或签名的程序集(篡改它们将使签名无效)或删除签名等。您进入代码的时间比

If you face the need to validate strong named or even signed assemblies (tampering them will invalidate the signatures) or remove the signatures etc. You are going to dive into code longer than a simple Stack Overflow answer can describe.

潜伏着什么 ILMerge 可以实现,以及如何为您提供更简单的解决方案

Lurking around what ILMerge does and how can point you to an easier solution

另一个更简单的解决方案是(如果IKVM支持)挂钩 AssemblyResolve 事件,您可以在其中将dll名称重新映射为物理dll,例如来自完全不同的文件或资源流等。如旧的堆栈溢出问题的几个答案所示在已编译的可执行文件中嵌入DLL

Another easier solution might be (if IKVM supports it) hooking the AssemblyResolve event where you can remap dll name into physical dll, loaded e.g. From totally different file or from a resource stream etc. As shown in several answers of older Stack Overflow question Embedding DLLs in a compiled executable

(编辑#1:注释后)

(EDIT #1: after comments)

如果您的一般性问题实际上归结为如何解决我使IKVM.dll使用我的套接字类,而不使用命名空间System.Net.Sockets中的套接字类,那么很简单的解决方案可能是:

If your more or less general question actually boils down into "How can I make IKVM.dll to use my socket classes instead of those from namespace System.Net.Sockets" then quite straightforward solution might be:

编译并部署您自己的自定义版本使用 http://www.ikvm.net/download.html-不需要二进制Mono.Cecil魔术。

Compile and deploy your own customized version of IKVM.dll using source code available at http://www.ikvm.net/download.html - no binary Mono.Cecil magic needed.

打开所有代码后,应该可以找到并重定向指向命名空间的所有引用。 System.Net 进入 dudeprgm.Net

As all code is open it should be possible to find and redirect all references pointing to namespace System.Net into dudeprgm.Net by


  • [10.1]获取IKVM源代码,并所有其他先决条件,并确保您可以编译可正常运行的IKVM.dll

  • [10.2]将 dudeprgm.Net.cs 项目添加到解决方案中

  • [10.3]在所有源文件中,使用System.Net查找和删除类似于的所有内容

  • 所有源文件中的[10.4]全文查找并用 dudeprgm.Net System.Net 的所有内容>

  • [10.5]进行编译。当编译器抱怨缺少符号(之前在System.Net名称空间中)时,请将其添加到存根文件中。 goto [10.5]

  • [10.6]如果上述步骤在2小时后仍未解决为 build ok,请考虑其他解决方案(或睡一觉)

  • [10.7]检查IKVM许可证( http://sourceforge.net/ p / ikvm / wiki / License / ),如果您必须对原始源代码进行修改,则必须更改/声明/确认。

  • [10.1] get IKVM source code and all other prerequisites and make sure you can compile working IKVM.dll
  • [10.2] add dudeprgm.Net.cs project to the solution
  • [10.3] in all source files find and remove everything looking like using System.Net
  • [10.4] in all source files full text find and replace everything that looks like System.Net with dudeprgm.Net
  • [10.5] compile. When compiler complains about a missing symbol (that was before in the System.Net namespace) then add it to your stub file. goto [10.5]
  • [10.6] if the above step does not settle down as "build ok" after 2 hours then think about another solution (or get some sleep)
  • [10.7] check IKVM license (http://sourceforge.net/p/ikvm/wiki/License/) if there is something you must change/claim/acknowledge as the original source code was modified

(编辑#2:评论后)

(EDIT #2: after comments)

如果您选择曲目 [04] 并使用文本文件和 ilasm / ildasm 工具(样式 [02] )似乎效率不高,然后下面是我的自动强名称签名器的关键相关部分,该部分使用Mono.Cecil将程序集引用更改为其他引用。代码按对我有用的形式按原样粘贴(之前,之后和周围没有代码行)。读取键: a是Mono.Cecil.AssemblyDefinition b实现Mono.Cecil.IAssemblyResolver ,<$中的键方法c $ c> b 实例是方法 AssemblyDefinition Resolve(AssemblyNameReference name),它将所需的DLL名称转换为对 AssemblyDefinition的调用.ReadAssembly(..)。我不需要解析指令流,重新映射程序集引用就足够了(如果需要,我可以在这里粘贴代码中的其他片段)

If you choose track [04] and working with text files and ilasm/ildasm tools (style [02]) would not seem productive then below is the key relevant part of my automatic strong name signer which changes assembly references into other references using Mono.Cecil. The code is pasted as is (without lines of code before, after and all around) in a form that works for me. Reading keys: a is Mono.Cecil.AssemblyDefinition, b implements Mono.Cecil.IAssemblyResolver, key method in b instance is the method AssemblyDefinition Resolve(AssemblyNameReference name) which translates required DLL name into call to AssemblyDefinition.ReadAssembly(..). I did not need to parse the instruction stream, remapping assembly references was enough (I can paste here few other pieces from my code if needed)

/// <summary>
/// Fixes references in assembly pointing to other assemblies to make their PublicKeyToken-s compatible. Returns true if some changes were made.
/// <para>Inspiration comes from https://github.com/brutaldev/StrongNameSigner/blob/master/src/Brutal.Dev.StrongNameSigner.UI/MainForm.cs
/// see call to SigningHelper.FixAssemblyReference
/// </para>
/// </summary>
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;
}

这篇关于使用Mono.Cecil替换对类型/名称空间的引用的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆