Microsoft如何创建具有循环引用的程序集?


107

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

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

这是.NET Reflector的屏幕截图,显示了我的意思:

在此处输入图片说明

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


2
很好的问题。我从未真正花时间检查此问题,但我很好奇知道答案。确实,似乎Dykam提供了一个明智的选择。
Noldorin

3
如果这些dll相互需要,为什么它们不合并为一个?有什么实际原因吗?
安德里亚斯·彼得森

1
有趣的问题...我想知道埃里克·利珀特对此的回答!正如安德里亚斯所说,我想知道为什么他们没有将所有东西都放在同一大会上……
托马斯·列维斯克

好吧,如果一个程序集需要更新,则无需接触其他程序集。那就是我看到的唯一原因。有趣的问题,虽然
Atmocreations

2
看一下这个演示文稿(asmmeta文件):msakademik.net/academicdays2005/Serge_Lidin.ppt
Mehrdad Afshari,2009年

Answers:


58

我只能说出Mono项目是如何做到的。该定理很简单,尽管它使代码混乱。

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

简而言之:

  • A的编译不需要代码和B的引用。
  • B被编译。
  • A重新编译。

1
它已被Visual Studio阻止,但可以直接使用命令行编译器(csc.exe)完成。看我的答案。
阿尔弗雷德·迈尔斯

14
我知道。Mono的主要构建系统不是Visual Studio。猜猜微软也不是。
Dykam,

35

RBarryYoung和Dykam陷入了困境。Microsoft使用内部工具,该工具使用ILDASM分解程序集,剥离所有内部/私有内容和方法体,然后再次将IL重新编译(使用ILASM)成“脱水程序集”或元数据程序集。每次更改装配的公共接口时都将执行此操作。

在构建期间,将使用元数据程序集代替实际的元数据程序集。这样循环就坏了。


1
有趣的答案,您有任何链接吗?
亨克·霍尔特曼

我正在尝试找到对该工具的外部参考。我不认为它是在Microsoft之外发布的,但是概念很简单:disassemble-strip internals-reassemble。
Srdjan Jovcic 09年

同意-有趣的答案。一些支持此操作的链接会很好。
Drew Noakes

是的,确实是这样(根据个人经验)。
2009年

1
直到生成后才对它们进行强签名(延迟签名),因此不会对脱水程序集进行签名。
Srdjan Jovcic,2009年

26

可以按照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并使用解决方案资源管理器删除引用,在第三步中将其撤消。我想的另一种方式是第三个项目文件,其中包括相同的文件,但引用不同。这可以工作,因为您可以指定构建顺序。
Dykam

据我所知,不能在这里进行测试。
Dykam

我真的很想看看。根据我在这里所做的实验,当您尝试添加引用时,IDE会阻止您。
阿尔弗雷德·迈尔斯

我知道。但是第三个项目没有该引用和#if符号,并且被第二个引用,而第一个引用了。没有周期。但是第三个使用第一个的代码并将其输出到第一个汇编位置。一个组件可以很容易地用相同规格的另一个组件替换。但是我认为强命名可能会导致这种方法出现问题。
Dykam

有点像Srdjan的答案,尽管方法不同。
Dykam

18

只要不使用项目引用,在Visual Studio中就很容易做到这一点...尝试以下操作:

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

这就是您的操作方式。但是认真的……您永远不会在一个真实的项目中做到这一点!如果您愿意,圣诞老人今年不会为您带来任何礼物。


1
唯一的例外是,如果它是在12月26日至31日之间并且礼物已经得到保护
-Jesse Hufstetler '19年

6

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


4

好吧,我从来没有在Windows上做到过,但是我已经在许多compile-link-rtl环境中做到了这一点,这些环境是它的实际产生者。您要做的是首先使存根为“目标”而没有交叉引用,然后链接,然后添加循环引用,然后重新链接。链接程序通常不关心循环引用或跟随引用链,它们只关心能够自己解析每个引用。

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

  1. 链接A而不引用B。
  2. 带有引用的B链接到A。
  3. 链接A,将引用添加到B。

Dykam很好说,它是可编译的,而不是.Net中的链接,但是原理仍然是相同的:使交叉引用的源具有其导出的入口点,但除了其中一个以外的所有源均具有自己对其他源的引用出来。这样构建它们。然后,取消外部引用的存根并重建它们。即使没有任何特殊工具,该方法也应能起作用,实际上,这种方法已经在我尝试过的每个操作系统上起作用(大约有6个)。尽管显然可以实现自动化的东西还是有很大帮助的。


该定理是正确的。但是,在.Net世界中,链接是动态完成的,而不是问题。这是需要此解决方案的编译步骤。
Dykam,

很抱歉再次修复您的问题:P。但是,编译时的引用(链接)发生在.Net世界中,这一切都源于该特定ECMA规范。因此,Mono,dotGnu和.Net。不是Windows本身。
Dykam,

1

一种可能的方法是使用条件编译(#if)首先编译不依赖于那些其他程序集的System.dll,然后编译其他程序集,最后重新编译System.dll以包括取决于Xml和组态。


1
不幸的是,这不允许您有条件地引用程序集(我希望有可能,它确实对我的项目之一有所帮助...)
Thomas Levesque 2009年

1
通过编辑.csproj文件,可以轻松完成条件引用。只需将Condition属性添加到<Reference>元素。
丹尼尔(Daniel)2009年

0

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


并不是的。其中没有很多低级的东西,只有基本的东西。是什么让您认为这将是低水平?运行时和corlib是低级的。相对。仍然纯C或C ++,以为JIT包含低级的东西。
Dykam
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.