¿Cómo creó Microsoft ensamblajes que tienen referencias circulares?

107

En .NET BCL hay referencias circulares entre:

  • System.dll y System.Xml.dll
  • System.dll y System.Configuration.dll
  • System.Xml.dll y System.Configuration.dll

Aquí hay una captura de pantalla de .NET Reflector que muestra lo que quiero decir:

ingrese la descripción de la imagen aquí

La forma en que Microsoft creó estos ensamblajes es un misterio para mí. ¿Se requiere un proceso de compilación especial para permitir esto? Imagino que aquí está pasando algo interesante.

Dibujó Noakes
fuente
2
Muy buena pregunta. En realidad, nunca me he tomado el tiempo de inspeccionar esto, pero tengo curiosidad por saber la respuesta. De hecho, parece que Dykam ha proporcionado uno sensato.
Noldorin
3
¿Por qué esos dll no se fusionan en uno, si todos se requieren entre sí? ¿Hay alguna razón práctica para eso?
Andreas Petersson
1
Interesante pregunta ... ¡Me gustaría saber la respuesta de Eric Lippert a esta! Y como dijo Andreas, me pregunto por qué no pusieron todo en el mismo ensamblaje ...
Thomas Levesque
Bueno, si un ensamblaje necesita actualizarse, no necesitarán tocar los demás. Esa es la única razón por la que veo. Sin embargo, una pregunta interesante
Atmocreations
2
Eche un vistazo a esta presentación (archivos asmmeta): msakademik.net/academicdays2005/Serge_Lidin.ppt
Mehrdad Afshari

Respuestas:

58

Solo puedo decir cómo el Proyecto Mono hace esto. El teorema es bastante simple, aunque genera un lío de código.

Primero compilan System.Configuration.dll, sin que la parte necesite la referencia a System.Xml.dll. Después de esto, compilan System.Xml.dll de la forma habitual. Ahora viene la magia. Ellos recompilan System.configuration.dll, con la parte que necesita la referencia a System.Xml.dll. Ahora hay una compilación exitosa con la referencia circular.

En breve:

  • A se compila sin que el código necesite B y la referencia a B.
  • B está compilado.
  • A se vuelve a compilar.
Dykam
fuente
1
Visual Studio lo bloquea, pero se puede hacer usando el compilador de línea de comandos (csc.exe) directamente. Mira mi respuesta.
Alfred Myers
14
Lo sé. El sistema de compilación principal de Mono no es Visual Studio. Guess Microsofts tampoco lo es.
Dykam
35

RBarryYoung y Dykam están en algo. Microsoft usa una herramienta interna que usa ILDASM para desensamblar ensamblajes, quitar todo el material interno / privado y cuerpos de métodos y recompilar IL nuevamente (usando ILASM) en lo que se llama 'ensamblaje deshidratado' o ensamblaje de metadatos. Esto se hace cada vez que se cambia la interfaz pública de ensamblado.

Durante la compilación, se utilizan ensamblados de metadatos en lugar de los reales. De esa manera se rompe el ciclo.

Srdjan Jovcic
fuente
1
Interesante respuesta, ¿tiene algún enlace?
Henk Holterman
Estoy tratando de encontrar una referencia externa a la herramienta. No creo que se publique fuera de Microsoft, pero el concepto es simple: desensamblar-quitar partes internas-reensamblar.
Srdjan Jovcic
De acuerdo - respuesta interesante. Algunos enlaces para respaldar esto serían buenos.
Drew Noakes
Sí, de hecho así se hace (por experiencia personal).
Pavel Minaev
1
No están fuertemente firmados hasta después de la construcción (se firman con retraso), por lo que los ensamblajes deshidratados no se firman.
Srdjan Jovcic
26

Se puede hacer de la forma descrita por Dykam, pero Visual Studio le impide hacerlo.

Tendrá que usar el compilador de línea de comandos csc.exe directamente.

  1. csc / target: biblioteca ClassA.cs

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

  3. csc / target: biblioteca 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 {
    }
}
Alfred Myers
fuente
También puede hacer esto en Visual Studio, aunque también es bastante duro, la forma básica es usar # if's y eliminar la referencia usando el explorador de soluciones, invirtiendo eso en el tercer paso. Otra forma en la que estoy pensando es en un tercer archivo de proyecto que incluye los mismos archivos pero diferentes referencias. Esto funcionaría ya que puede especificar el orden de construcción.
Dykam
Hasta donde yo sé, no puedo probarlo aquí.
Dykam
Realmente me gustaría ver eso. Por lo que experimenté aquí, en el momento en que intentas Agregar referencia, el IDE te detiene.
Alfred Myers
Lo sé. Pero un tercer proyecto no tiene esa referencia Y el símbolo #if, y será referenciado por el segundo, al que hace referencia el primero. Sin ciclo. Pero el tercero usa el código del primero y envía a la primera ubicación de ensamblaje. un ensamblaje se puede reemplazar fácilmente por otro con las mismas especificaciones. Pero creo que los nombres fuertes pueden causar un problema en este método.
Dykam
Es un poco como la respuesta de Srdjan, aunque con un método diferente.
Dykam
18

Es bastante fácil de hacer en Visual Studio siempre que no use referencias de proyecto ... Pruebe esto:

  1. Estudio visual abierto
  2. Cree 2 proyectos de biblioteca de clases "ClassLibrary1" y "ClassLibrary2".
  3. Construir
  4. Desde ClassLibrary1 agregue una referencia a ClassLibrary2 navegando hasta el dll creado en el paso 3.
  5. Desde ClassLibrary2 agregue una referencia a ClassLibrary1 navegando hasta el dll creado en el paso 3.
  6. Vuelva a compilar (Nota: si realiza cambios en ambos proyectos, deberá compilar dos veces para que ambas referencias sean "nuevas")

Así que así es como lo haces. Pero en serio ... ¡No lo hagas NUNCA en un proyecto real! Si lo hace, Santa no le traerá ningún regalo este año.

JohannesH
fuente
1
La única excepción es si es entre el 26 y el 31 de diciembre y los regalos ya están asegurados
Jesse Hufstetler
6

Supongo que podría hacerse comenzando con un conjunto acíclico de ensamblajes y usando ILMerge para luego fusionar los ensamblajes más pequeños en grupos relacionados lógicamente.

Steve Gilham
fuente
4

Bueno, nunca lo he hecho en Windows, pero lo he hecho en muchos de los entornos compile-link-rtl que sirvieron como progenitores prácticos para ello. Lo que debe hacer primero es crear "destinos" de código auxiliar sin las referencias cruzadas, luego vincular, luego agregar las referencias circulares y luego volver a vincular. A los enlazadores generalmente no les importan las referencias circulares o las siguientes cadenas de referencias, solo les importa poder resolver cada referencia por sí misma.

Entonces, si tiene dos bibliotecas, A y B que necesitan hacer referencia entre sí, intente algo como esto:

  1. Enlace A sin referencias a B.
  2. Enlace B con referencias a A.
  3. Enlace A, agregando las referencias a B.

Dykam tiene un buen punto, se compila, no se vincula en .Net, pero el principio sigue siendo el mismo: haga sus fuentes con referencias cruzadas, con sus puntos de entrada exportados, pero con todas menos una que tengan sus propias referencias a las demás. fuera. Constrúyalos así. Luego, elimine las referencias externas y vuelva a generarlas. Esto debería funcionar incluso sin herramientas especiales, de hecho, este enfoque ha funcionado en todos los sistemas operativos en los que lo he probado (alrededor de 6 de ellos). Aunque obviamente algo que lo automatice sería de gran ayuda.

RBarryYoung
fuente
el teorema es correcto. Sin embargo, en el mundo .Net, la vinculación se realiza de forma dinámica y no es un problema. Es el paso de compilación donde se necesita esta solución.
Dykam
Perdón por arreglarte de nuevo: P. Pero la referencia (vinculación) en tiempo de compilación ocurre en el mundo .Net, que es todo lo que se deriva de esa especificación específica de ECMA. Por lo tanto, Mono, dotGnu y .Net. No Windows en sí.
Dykam
1

Un enfoque posible es usar la compilación condicional (#if) para compilar primero un System.dll que no dependa de esos otros ensamblados, luego compilar los otros ensamblajes y, por último, volver a compilar System.dll para incluir las partes que dependen de Xml y Configuración.

Daniel
fuente
1
Desafortunadamente, esto no le permite hacer referencia condicional a un ensamblado (desearía que fuera posible, realmente ayudaría en uno de mis proyectos ...)
Thomas Levesque
1
Las referencias condicionales se pueden hacer fácilmente editando el archivo .csproj. Simplemente agregue un atributo Condition al elemento <Reference>.
Daniel
0

Técnicamente, es posible que estos no se hayan compilado y ensamblado a mano. Estas son bibliotecas de bajo nivel, después de todo.

Tylermac
fuente
Realmente no. No hay muchas cosas de bajo nivel, solo básicas. ¿Qué te hizo pensar que sería de bajo nivel? El tiempo de ejecución y corlib es de bajo nivel. Relativamente. Todavía C o C ++ simple, pensó que el JIT contiene cosas de bajo nivel.
Dykam