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

在.NET BCL 中有下列循环引用:

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

下面是.NET Refector 的一个截图,显示了我的意思:

enter image description here

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

11590 次浏览

I guess it could be done by starting with an acyclic set of assemblies and using ILMerge to then coalesce the smaller assemblies into logically related groups.

One possible approach is to use conditional compilation (#if) to first compile a System.dll that doesn't depend on those other assemblies, then compile the other assemblies, and at last recompile System.dll to include the parts depending on Xml and Configuration.

I can only tell how the Mono Project does this. The theorem is quite simple, though it gives a code mess.

They first compile System.Configuration.dll, without the part needing the reference to System.Xml.dll. After this, they compile System.Xml.dll the normal way. Now comes the magic. They recompile System.configuration.dll, with the part needing the reference to System.Xml.dll. Now there's a successful compilation with the circular reference.

In short:

  • A is compiled without the code needing B and the reference to B.
  • B is compiled.
  • A is recompiled.

Well, I've never done it on Windows, but I have done it on a lot of the compile-link-rtl environments that served as the practical progenitors for it. What you do is first make stub "targets" without the cross-references then link, then add the circular references, then re-link. The linkers generally do not care about circular refs or following ref chains, they only care about being able to resolve each reference on it's own.

So if you have two libraries, A and B that need to reference each other, try something like this:

  1. Link A without any refs to B.
  2. Link B with refs to A.
  3. Link A, adding in the refs to B.

Dykam makes a good point, It's compile, not link in .Net, but the principle remains the same: Make your cross-referenced sources, with their exported entry points, but with all but one of them having their own references to the others stubbed out. Build them like that. Then, unstub the external references and rebuild them. This should work even without any special tools, in fact, this approach has worked on every operating system that I have ever tried it on (about 6 of them). Though obviously something that automates it would be a big help.

It can be done the way Dykam described but Visual Studio blocks you from doing it.

You'll have to use the command-line compiler csc.exe directly.

  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 {
}
}

RBarryYoung and Dykam are onto something. Microsoft uses internal tool which uses ILDASM to disassemble assemblies, strip all internal/private stuff and method bodies and recompile IL again (using ILASM) into what is called 'dehydrated assembly' or metadata assembly. This is done every time public interface of assembly is changed.

During the build, metadata assemblies are used instead of real ones. That way cycle is broken.

Its pretty easy to do in Visual Studio as long as you don't use project references... Try this:

  1. Open visual studio
  2. Create 2 Class Library projects "ClassLibrary1" & "ClassLibrary2".
  3. Build
  4. From ClassLibrary1 add a reference to ClassLibrary2 by browsing to the dll created in step 3.
  5. From ClassLibrary2 add a reference to ClassLibrary1 by browsing to the dll created in step 3.
  6. Build again (Note: if you make changes in both projects you would need to build twice to make both references "fresh")

So this is how you do it. But seriously... Don't you EVER do it in a real project! If you do, Santa wont bring you any presents this year.

Technically, it's possible that these were not compiled at all, and assembled by hand. These are low level libraries, after all.

Agreed. asmmeta.exe is like ildasm, but omits all the IL (just ret) and some privates, though sometimes privates are needed like for struct sizes.

The more general idea is that of a multi-pass build, which Microsoft has relied on heavily forever.

The stripped down ildasm output can be thought as as "header" file, in a system that does not really have them.

First visit each directory (with massive parallelism!) running ilasm. Then visit each directory (again with massive parallelism) running csc. After csc, in the same pass, run the like-ildasm tool, output back the original "headers". Compare them. If there are any mismatches, the build is broken. A developer failed to update the header. It is too late to just patch it up, without restarting the build (perhaps with a proper dependency graph, most directories will not be affected).

This is also a way to upgrade versions easily. The like-ilasm code can have names for version numbers. Though this is really a minor outcome of a multi-pass build.