【问题标题】:How did Microsoft create assemblies that have circular references?Microsoft 如何创建具有循环引用的程序集?
【发布时间】:2010-11-21 22:30:50
【问题描述】:

在 .NET BCL 中存在以下循环引用:

  • System.dllSystem.Xml.dll
  • System.dllSystem.Configuration.dll
  • System.Xml.dllSystem.Configuration.dll

这是来自 .NET Reflector 的屏幕截图,说明了我的意思:

Microsoft 如何创建这些程序集对我来说是个谜。是否需要特殊的编译过程才能允许这样做?我想这里正在发生一些有趣的事情。

【问题讨论】:

  • 非常好的问题。我从来没有真正花时间检查这个,但我很想知道答案。事实上,Dykam 似乎提供了一个明智的选择。
  • 为什么这些 dll 没有合并为一个,如果它们都需要彼此?有什么实际原因吗?
  • 有趣的问题...我想知道 Eric Lippert 对此的回答!正如安德烈亚斯所说,我想知道他们为什么不把所有东西都放在同一个程序集中......
  • 好吧,如果一个程序集需要更新,他们就不需要接触其他程序集。这是我看到的唯一原因。不过很有趣的问题
  • 看看这个演示文稿(asmmeta 文件):msakademik.net/academicdays2005/Serge_Lidin.ppt

标签: .net assemblies circular-reference base-class-library


【解决方案1】:

同意。 asmmeta.exe 类似于 ildasm,但省略了所有的 IL(只是 ret)和一些私人信息,尽管有时需要私人信息,例如结构大小。

更一般的想法是多通道构建,Microsoft 一直非常依赖它。

剥离的 ildasm 输出可以被视为“头”文件,在一个没有它们的系统中。

首先访问每个运行 ilasm 的目录(具有大量并行性!)。 然后访问每个运行 csc 的目录(同样具有大量并行性)。 在 csc 之后,在同一遍中,运行 like-ildasm 工具,输出回原始的“标题”。 比较它们。如果有任何不匹配,则构建被破坏。 开发人员未能更新标头。 如果不重新构建,直接打补丁就太晚了(也许有合适的依赖图,大多数目录都不会受到影响)。

这也是一种轻松升级版本的方法。 like-ilasm 代码可以有版本号的名称。 虽然这实际上是多通道构建的次要结果。

【讨论】:

    【解决方案2】:

    只要不使用项目引用,在 Visual Studio 中就很容易做到……试试这个:

    1. 打开视觉工作室
    2. 创建 2 个类库项目“ClassLibrary1”和“ClassLibrary2”。
    3. 构建
    4. 从 ClassLibrary1 通过浏览到在步骤 3 中创建的 dll 添加对 ClassLibrary2 的引用。
    5. 从 ClassLibrary2 添加对 ClassLibrary1 的引用,方法是浏览到在步骤 3 中创建的 dll。
    6. 再次构建(注意:如果您在两个项目中都进行了更改,则需要构建两次以使两个引用都“新鲜”)

    所以这就是你的做法。但说真的......你不要在一个真正的项目中这样做!如果你这样做了,今年圣诞老人就不会给你送礼物了。

    【讨论】:

    • 唯一的例外是在 12 月 26 日至 31 日之间并且礼物已经得到保护
    【解决方案3】:

    从技术上讲,它们可能根本没有编译,而是手工组装的。毕竟,这些都是低级库。

    【讨论】:

    • 并非如此。里面没有很多低级的东西,只有基本的。是什么让你认为它会是低水平的?运行时和 corlib 是低级别的。相对地。仍然是普通的 C 或 C++,认为 JIT 包含低级的东西。
    【解决方案4】:

    我只能说出 Mono 项目是如何做到这一点的。这个定理很简单,虽然代码很混乱。

    他们首先编译 System.Configuration.dll,不需要引用 System.Xml.dll 的部分。在此之后,他们以正常方式编译 System.Xml.dll。现在魔法来了。他们重新编译 System.configuration.dll,其中需要引用 System.Xml.dll 的部分。现在使用循环引用成功编译。

    简而言之:

    • A 编译时没有代码 需要 B 和对 B 的引用。
    • B 已编译。
    • A 被重新编译。

    【讨论】:

    • 它被 Visual Studio 阻止,但可以直接使用命令行编译器 (csc.exe) 完成。看我的回答。
    • 我知道。 Mono 的主要构建系统不是 Visual Studio。我猜微软也不是。
    【解决方案5】:

    嗯,我从来没有在 Windows 上做过,但我在很多 compile-link-rtl 环境中做过,这些环境是它的实际祖先。您要做的是首先制作没有交叉引用的存根“目标”然后链接,然后添加循环引用,然后重新链接。链接器通常不关心循环引用或跟随引用链,他们只关心能够自行解析每个引用。

    因此,如果您有两个库 A 和 B 需要相互引用,请尝试以下操作:

    1. 将不带任何引用的 A 链接到 B。
    2. 链接 B with 引用到 A。
    3. 链接 A,将引用添加到 B。

    Dykam 提出了一个很好的观点,它是可编译的,而不是 .Net 中的链接,但原理保持不变:使用它们导出的入口点创建您的交叉引用源,但除了其中一个之外,其他所有源都有自己的引用其他人都被淘汰了。像这样建造它们。然后,取消存根外部引用并重建它们。即使没有任何特殊工具,这也应该可以工作,事实上,这种方法适用于我曾经尝试过的每个操作系统(大约 6 个)。虽然很明显,自动化它会有很大的帮助。

    【讨论】:

    • 这个定理是对的。但是在.Net 世界中,链接是动态完成的,不是问题。这是需要此解决方案的编译步骤。
    • 很抱歉再次修复您:P。但是编译时的引用(链接)发生在 .Net 世界中,这是从特定的 ECMA 规范派生的所有内容。因此 Mono、dotGnu 和 .Net。不是 Windows 本身。
    【解决方案6】:

    可以按照 Dykam 描述的方式完成,但 Visual Studio 会阻止您这样做。

    您必须直接使用命令行编译器 csc.exe。

    1. csc /target:library ClassA.cs

    2. csc /target:library ClassB.cs /reference:ClassA.dll

    3. csc /target:library ClassA.cs ClassC.cs /reference:ClassB.dll

    
    //ClassA.cs
    namespace CircularA {
        public class ClassA {
        }
    }
    
    
    //ClassB.cs
    using CircularA;
    namespace CircularB {
        public class ClassB : ClassA  {
        }
    }
    
    
    //ClassC.cs
    namespace CircularA {
        class ClassC : ClassB {
        }
    }
    

    【讨论】:

    • 您也可以在 Visual Studio 中执行此操作,尽管它也很苛刻,基本方法是使用#if 并使用解决方案资源管理器删除引用,在第三步中将其反转。我正在考虑的另一种方式是第三个项目文件,包括相同的文件但不同的引用。这将起作用,因为您可以指定构建顺序。
    • 据我所知,这里无法测试。
    • 我真的很想看看。根据我在这里的实验,当您尝试添加引用时,IDE 会阻止您。
    • 我知道。但是第三个项目没有该引用和#if 符号,并被第二个引用,第二个被第一个引用。没有循环。但是第三个使用第一个的代码并输出到第一个装配位置。一个组件可以很容易地被另一个具有相同规格的组件替换。但我认为强命名可能会导致这种方法出现问题。
    • 这有点像 Srdjan 的回答,虽然方法不同。
    【解决方案7】:

    RBarryYoung 和 Dykam 正在做某事。 Microsoft 使用内部工具,该工具使用 ILDASM 来反汇编程序集,剥离所有内部/私有内容和方法体,并再次(使用 ILASM)将 IL 重新编译为所谓的“脱水程序集”或元数据程序集。每次更改程序集的公共接口时都会执行此操作。

    在构建期间,使用元数据程序集而不是真实的。这样循环就被打破了。

    【讨论】:

    • 有趣的答案,有链接吗?
    • 我正在尝试查找对该工具的外部引用。我不认为它是在 Microsoft 之外发布的,但概念很简单:disassemble-strip internals-reassemble。
    • 同意 - 有趣的答案。一些支持这一点的链接会很好。
    • 是的,确实是这样做的(根据个人经验)。
    • 它们在构建之后才进行强签名(它们是延迟签名的),因此脱水程序集没有签名。
    【解决方案8】:

    一种可能的方法是使用条件编译 (#if) 首先编译一个不依赖于其他程序集的 System.dll,然后编译其他程序集,最后重新编译 System.dll 以包含依赖于这些程序集的部分关于 Xml 和配置。

    【讨论】:

    • 不幸的是,这不允许您有条件地引用程序集(我希望它是可能的,它对我的​​一个项目真的有帮助......)
    • 条件引用可以通过编辑 .csproj 文件轻松完成。只需将 Condition 属性添加到 元素。
    【解决方案9】:

    我想这可以通过从一组非循环程序集开始并使用 ILMerge 将较小的程序集合并为逻辑相关组来完成。

    【讨论】:

      猜你喜欢
      • 2017-09-16
      • 1970-01-01
      • 2013-02-09
      • 1970-01-01
      • 2019-08-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多