Simplificación de la implementación y resolución de dll hell con .NET Framework
Steven Pratschner
Microsoft Corporation
Actualizado en noviembre de 2001
Resumen: En este artículo se presenta el concepto de ensamblado y se describe cómo .NET Framework usa ensamblados para resolver problemas de control de versiones e implementación. (16 páginas impresas)
Contenido
Introducción
Declaración del problema
Características de la solución
Ensamblados: los bloques de creación
Control de versiones y uso compartido
Directiva de versión
Implementación
Resumen
Introducción
Microsoft® .NET Framework presenta varias características nuevas destinadas a simplificar la implementación de aplicaciones y resolver DLL Hell. Tanto los usuarios finales como los desarrolladores están familiarizados con los problemas de control de versiones e implementación que pueden surgir con los sistemas basados en componentes actuales. Por ejemplo, prácticamente todos los usuarios finales han instalado una nueva aplicación en su máquina, solo para encontrar que una aplicación existente deja de funcionar misteriosamente. La mayoría de los desarrolladores también han dedicado tiempo a Regedit, intentando mantener todas las entradas del Registro necesarias coherentes con el fin de activar una clase COM.
Las directrices de diseño y las técnicas de implementación que se usan en .NET Framework para resolver DLL Hell se basan en el trabajo realizado en Microsoft Windows® 2000, como se describe por Rick Anderson en El final del infierno dll, y por David D'Souza, BJ Whalen y Peter Wilson en La implementación del uso compartido de componentes en paralelo en aplicaciones (expandidos). .NET Framework amplía este trabajo anterior proporcionando características como el aislamiento de aplicaciones y los componentes en paralelo para las aplicaciones compiladas con código administrado en la plataforma .NET. Además, tenga en cuenta que Windows XP proporciona las mismas características de aislamiento y control de versiones para código no administrado, incluidas las clases COM y los archivos DLL de Win32 (consulte How To Build and Service Isolated Applications and Side-by-Side Assemblies for Windows XP para obtener más información).
En este artículo se presenta el concepto de ensamblado y se describe cómo .NET usa ensamblados para resolver problemas de control de versiones e implementación. En concreto, analizaremos cómo se estructuran los ensamblados, cómo se denominan y cómo los compiladores y Common Language Runtime (CLR) usan ensamblados para registrar y aplicar dependencias de versión entre partes de una aplicación. También analizaremos cómo las aplicaciones y los administradores pueden personalizar el comportamiento de control de versiones a través de lo que llamamos directivas de versión.
Una vez introducidos y descritos los ensamblados, se presentarán varios escenarios de implementación, lo que proporciona un muestreo de las distintas opciones de empaquetado y distribución disponibles en .NET Framework.
Declaración del problema
Control de versiones
Desde la perspectiva del cliente, el problema de control de versiones más común es lo que llamamos DLL Hell. Simplemente indicado, DLL Hell hace referencia al conjunto de problemas causados cuando varias aplicaciones intentan compartir un componente común como una biblioteca de vínculos dinámicos (DLL) o una clase de modelo de objetos componentes (COM). En el caso más habitual, una aplicación instalará una nueva versión del componente compartido que no es compatible con la versión anterior ya en el equipo. Aunque la aplicación que acaba de instalarse funciona bien, es posible que las aplicaciones existentes que dependían de una versión anterior del componente compartido ya no funcionen. En algunos casos, la causa del problema es aún más sutil. Por ejemplo, considere el escenario en el que un usuario descarga un control Microsoft ActiveX® como efecto secundario de visitar algún sitio web. Cuando se descargue el control, reemplazará las versiones existentes del control que estaban presentes en el equipo. Si una aplicación que se ha instalado en la máquina usa este control, también podría dejar de funcionar.
En muchos casos, hay un retraso significativo antes de que un usuario descubra que una aplicación ha dejado de funcionar. Como resultado, a menudo es difícil recordar cuándo se realizó un cambio en la máquina que podría haber afectado a la aplicación. Un usuario puede recordar la instalación de algo hace una semana, pero no hay ninguna correlación obvia entre esa instalación y el comportamiento que están viendo. Para empeorar las cosas, hay pocas herramientas de diagnóstico disponibles hoy en día para ayudar al usuario (o a la persona de soporte técnico que los está ayudando) a determinar lo que está mal.
El motivo de estos problemas es que el sistema no registra ni aplica información de versión sobre los distintos componentes de una aplicación. Además, los cambios realizados en el sistema en nombre de una aplicación normalmente afectarán a todas las aplicaciones de la máquina; la compilación de una aplicación hoy en día que está completamente aislada de los cambios no es fácil.
Una razón por la que es difícil compilar una aplicación aislada es que el entorno en tiempo de ejecución actual normalmente permite la instalación de solo una versión de un componente o una aplicación. Esta restricción significa que los autores de componentes deben escribir su código de forma que siga siendo compatible con versiones anteriores; de lo contrario, corre el riesgo de interrumpir las aplicaciones existentes cuando instalan un nuevo componente. En la práctica, escribir código que es siempre compatible con versiones anteriores es extremadamente difícil, si no imposible. En .NET, la noción de en paralelo es fundamental para el caso de control de versiones. En paralelo, es la capacidad de instalar y ejecutar varias versiones del mismo componente en la máquina al mismo tiempo. Con los componentes que admiten en paralelo, los autores no están necesariamente vinculados al mantenimiento de una compatibilidad con versiones anteriores estrictas, ya que las diferentes aplicaciones pueden usar diferentes versiones de un componente compartido.
Implementación e instalación
La instalación de una aplicación en la actualidad es un proceso de varios pasos. Normalmente, la instalación de una aplicación implica copiar una serie de componentes de software en el disco y crear una serie de entradas del Registro que describen esos componentes en el sistema.
La separación entre las entradas del Registro y los archivos en el disco hace que sea muy difícil replicar aplicaciones y desinstalarlas. Además, la relación entre las distintas entradas necesarias para describir completamente una clase COM en el registro es muy flexible. Estas entradas suelen incluir entradas para coclases, interfaces, bibliotecas de tipos e identificadores de aplicación DCOM, no mencionar las entradas realizadas para registrar extensiones de documento o categorías de componentes. A menudo, termina manteniendo estos en sincronización manualmente.
Por último, esta superficie del Registro es necesaria para activar cualquier clase COM. Esto complica drásticamente el proceso de implementación de aplicaciones distribuidas porque cada máquina cliente debe tocarse para hacer las entradas del Registro adecuadas.
Estos problemas se deben principalmente a que la descripción de un componente se mantiene independiente del propio componente. En otras palabras, las aplicaciones no son autodescriptantes ni independientes.
Características de la solución
.NET Framework debe proporcionar las siguientes funcionalidades básicas para resolver los problemas descritos:
- Las aplicaciones deben describirse automáticamente. Las aplicaciones que se describen automáticamente eliminan la dependencia del registro, lo que permite la instalación sin impacto y simplifica la desinstalación y la replicación.
- La información de versión se debe registrar y aplicar. La compatibilidad con el control de versiones debe estar integrada en la plataforma para asegurarse de que la versión adecuada de una dependencia se carga en tiempo de ejecución.
- Debe recordar "último bien conocido". Cuando una aplicación se ejecuta correctamente, la plataforma debe recordar el conjunto de componentes (incluidas sus versiones) que funcionan conjuntamente. Además, se deben proporcionar herramientas que permitan a los administradores revertir fácilmente las aplicaciones a este estado "último correcto conocido".
- Compatibilidad con componentes en paralelo. Permitir que varias versiones de un componente se instalen y ejecuten en la máquina simultáneamente permite a los autores de llamadas especificar qué versión les gustaría cargar en lugar de una versión "forzada" sin saberlo. .NET Framework toma en paralelo un paso más lejos al permitir que varias versiones del propio marco coexistan en una sola máquina. Esto simplifica considerablemente el artículo de actualización, ya que un administrador puede optar por ejecutar diferentes aplicaciones en diferentes versiones de .NET Framework si es necesario.
- Aislamiento de la aplicación. .NET Framework debe facilitar y, de hecho, el valor predeterminado es escribir aplicaciones que no puedan verse afectadas por los cambios realizados en el equipo en nombre de otras aplicaciones.
Ensamblados: los bloques de creación
Los ensamblados son los bloques de creación que usa .NET Framework para resolver los problemas de control de versiones e implementación que acaba de describir. Los ensamblados son la unidad de implementación de tipos y recursos. De muchas maneras, un ensamblado equivale a un archivo DLL en el mundo actual; en esencia, los ensamblados son "archivos DLL lógicos".
Los ensamblados se describen automáticamente a través de metadatos denominados manifiestos. Al igual que .NET usa metadatos para describir tipos, también usa metadatos para describir los ensamblados que contienen los tipos.
Los ensamblados son mucho más que la implementación. Por ejemplo, el control de versiones en .NET se realiza en el nivel de ensamblado; no hay nada más pequeño, como un módulo o un tipo, tiene versiones. Además, los ensamblados se usan para compartir código entre aplicaciones. El ensamblado en el que se encuentra un tipo forma parte de la identidad del tipo.
El sistema de seguridad de acceso al código usa ensamblados en el núcleo de su modelo de permisos. El autor de un ensamblado registra en el manifiesto el conjunto de permisos necesarios para ejecutar el código y el administrador concede permisos al código en función del ensamblado en el que se encuentra el código.
Por último, los ensamblados también son fundamentales para el sistema de tipos y el sistema en tiempo de ejecución en que establecen un límite de visibilidad para los tipos y sirven como ámbito en tiempo de ejecución para resolver referencias a tipos.
Manifiestos de ensamblado
En concreto, un manifiesto incluye los siguientes datos sobre el ensamblado:
- Identidad La identidad de un ensamblado consta de cuatro partes: un nombre de texto simple, un número de versión, una referencia cultural opcional y una clave pública opcional si el ensamblado se creó para compartir (consulte la sección sobre ensamblados compartidos a continuación).
- Lista de archivos. Un manifiesto incluye una lista de todos los archivos que componen el ensamblado. Para cada archivo, el manifiesto registra su nombre y un hash criptográfico de su contenido en el momento en que se creó el manifiesto. Este hash se comprueba en tiempo de ejecución para asegurarse de que la unidad de implementación es coherente.
- Ensamblados a los que se hace referencia. Las dependencias entre ensamblados se almacenan en el manifiesto del ensamblado que llama. La información de dependencia incluye un número de versión, que se usa en tiempo de ejecución para asegurarse de que se carga la versión correcta de la dependencia.
- Tipos y recursos exportados. Las opciones de visibilidad disponibles para los tipos y recursos incluyen "visible solo dentro de mi ensamblado" y "visibles para los autores de llamadas fuera de mi ensamblado".
- Solicitudes de permisos. Las solicitudes de permiso para un ensamblado se agrupan en tres conjuntos: 1) las necesarias para que el ensamblado se ejecute, 2) las que se desean, pero el ensamblado seguirá teniendo alguna funcionalidad, incluso si no se conceden y 3) las que el autor nunca quiere que se conceda el ensamblado.
La herramienta del SDK de desensamblador de IL (Ildasm) es útil para examinar el código y los metadatos de un ensamblado. La figura 1 es un manifiesto de ejemplo tal como lo muestra Ildasm. La directiva .assembly identifica el ensamblado y las directivas extern .assembly contienen la información sobre otros ensamblados en los que depende este.
Figura 1. Manifiesto de ejemplo tal como se muestra en el desensamblador de IL
Estructura de ensamblados
Hasta ahora, los ensamblados se han descrito principalmente como un concepto lógico. Esta sección ayuda a hacer que los ensamblados sean más concretos mediante la descripción de cómo se representan físicamente.
En general, los ensamblados constan de cuatro elementos: los metadatos del ensamblado (manifiesto), los metadatos que describen los tipos, el código de lenguaje intermedio (IL) que implementa los tipos y un conjunto de recursos. No todos están presentes en cada ensamblado. Solo se requiere estrictamente el manifiesto, pero se necesitan tipos o recursos para proporcionar al ensamblado cualquier funcionalidad significativa.
Hay varias opciones para cómo se pueden "empaquetar" estos cuatro elementos. Por ejemplo, la figura 2 muestra un único archivo DLL que contiene todo el ensamblado: el manifiesto, los metadatos de tipo, el código IL y los recursos.
Ilustración 2. DLL que contiene todos los elementos de ensamblado
Como alternativa, el contenido de un ensamblado se puede distribuir entre varios archivos. En la figura 3, el autor ha elegido separar algún código de utilidad en un archivo DLL diferente y mantener un archivo de recursos grande (en este caso, un JPEG) en su archivo original. Una razón por la que esto puede hacerse es optimizar la descarga de código. .NET Framework descargará un archivo solo cuando se haga referencia a él, por lo que si el ensamblado contiene código o recursos a los que se accede con poca frecuencia, dividirlos en archivos individuales aumentará la eficacia de descarga. Otro escenario común en el que se usan varios archivos es compilar un ensamblado que consta de código a partir de más de un lenguaje. En este caso, compilaría cada archivo (módulo) por separado y, a continuación, los agruparía en un ensamblado mediante la herramienta Assembly Linker proporcionada en el SDK de .NET Framework (al.exe).
Figura 3. Elementos de ensamblado distribuidos entre varios archivos
Control de versiones y uso compartido
Una de las principales causas de DLL Hell es el modelo de uso compartido que se usa actualmente en sistemas basados en componentes. De forma predeterminada, varias aplicaciones comparten componentes de software individuales en la máquina. Por ejemplo, cada vez que un programa de instalación copia un archivo DLL en el directorio del sistema o registra una clase en el registro COM, ese código podría tener un efecto en otras aplicaciones que se ejecutan en el equipo. En concreto, si una aplicación existente usaba una versión anterior de ese componente compartido, esa aplicación empezará automáticamente a usar la nueva versión. Si el componente compartido es estrictamente compatible con versiones anteriores, esto puede ser correcto, pero en muchos casos mantener la compatibilidad con versiones anteriores es difícil, si no es imposible. Si no se mantiene la compatibilidad con versiones anteriores o no se puede mantener, esto suele dar lugar a que las aplicaciones que se interrumpen como un efecto secundario de otras aplicaciones que se instalan.
Una guía de diseño principal en .NET es la de componentes aislados (o ensamblados). Aislar un ensamblado significa que una aplicación solo puede acceder a un ensamblado, no es compartido por varias aplicaciones en la máquina y no puede verse afectada por los cambios realizados en el sistema por otras aplicaciones. El aislamiento proporciona a un desarrollador un control absoluto sobre el código que usa su aplicación. Los ensamblados aislados o privados de la aplicación son los predeterminados en las aplicaciones .NET. La tendencia hacia los componentes aislados se inició en Microsoft Windows 2000 con la introducción del archivo .local. Este archivo se usó para hacer que tanto el cargador del sistema operativo como COM busquen primero en el directorio de la aplicación al intentar localizar el componente solicitado. (Consulte el artículo relacionado de MSDN Library, Implementación del uso compartido de componentes en paralelo en aplicaciones).
Sin embargo, hay casos en los que es necesario compartir un ensamblado entre aplicaciones. Claramente, no tendría sentido que cada aplicación lleve su propia copia de System.Windowns.Forms, System.Web o un control de Web Forms común.
En .NET, compartir código entre aplicaciones es una decisión explícita. Los ensamblados que se comparten tienen algunos requisitos adicionales. En concreto, los ensamblados compartidos deben admitirse en paralelo para que se puedan instalar y ejecutar varias versiones del mismo ensamblado en el mismo equipo, o incluso dentro del mismo proceso, al mismo tiempo. Además, los ensamblados compartidos tienen requisitos de nomenclatura más estrictos. Por ejemplo, un ensamblado compartido debe tener un nombre que sea único globalmente.
La necesidad de aislamiento y uso compartido nos lleva a pensar en dos "tipos" de ensamblados. Se trata de una categorización bastante flexible en que no hay diferencias estructurales reales entre los dos, sino que la diferencia está en la forma en que se usarán: ya sea privada para una aplicación o compartida entre muchos.
ensamblados de Application-Private
Un ensamblado privado de aplicación es un ensamblado que solo es visible para una aplicación. Esperamos que sea el caso más común en .NET. Los requisitos de nomenclatura de los ensamblados privados son sencillos: los nombres de ensamblado solo deben ser únicos dentro de la aplicación. No es necesario un nombre único global. Mantener los nombres únicos no es un problema porque el desarrollador de aplicaciones tiene control completo sobre qué ensamblados están aislados para la aplicación.
Los ensamblados privados de aplicación se implementan dentro de la estructura de directorios de la aplicación en la que se usan. Los ensamblados privados se pueden colocar directamente en el directorio de la aplicación o en un subdirectorio. CLR busca estos ensamblados a través de un proceso denominado sondeo. El sondeo es simplemente una asignación del nombre del ensamblado al nombre del archivo que contiene el manifiesto.
En concreto, CLR toma el nombre del ensamblado registrado en la referencia del ensamblado, anexa ".dll" y busca ese archivo en el directorio de la aplicación. Hay algunas variantes en este esquema donde el runtime buscará en subdirectorios denominados por el ensamblado o en subdirectorios denominados por la referencia cultural del ensamblado. Por ejemplo, un desarrollador puede optar por implementar el ensamblado que contiene recursos localizados en alemán en un subdirectorio denominado "de" y en español en un directorio denominado "es". (Consulte la Guía del SDK de .NET Framework para obtener más información).
Como se acaba de describir, cada manifiesto de ensamblado incluye información de versión sobre sus dependencias. Esta información de versión no se aplica a los ensamblados privados porque el desarrollador tiene control total sobre los ensamblados que se implementan en el directorio de la aplicación.
Ensamblados compartidos
.NET Framework también admite el concepto de un ensamblado compartido. Un ensamblado compartido es uno que usan varias aplicaciones en la máquina. Con .NET, compartir código entre aplicaciones es una decisión explícita. Los ensamblados compartidos tienen algunos requisitos adicionales destinados a evitar los problemas de uso compartido que experimentamos hoy en día. Además de la compatibilidad con la descripción en paralelo anterior, los ensamblados compartidos tienen requisitos de nomenclatura mucho más estrictos. Por ejemplo, un ensamblado compartido debe tener un nombre único globalmente. Además, el sistema debe proporcionar "protección del nombre", es decir, impedir que alguien reutilice el nombre del ensamblado de otro. Por ejemplo, supongamos que es un proveedor de un control de cuadrícula y que ha publicado la versión 1 del ensamblado. Como autor, necesita asegurarse de que nadie más puede liberar un ensamblado que reclama que sea la versión 2 o el control de cuadrícula. .NET Framework admite estos requisitos de nomenclatura mediante una técnica denominada nombres seguros (que se describe con detalle en la sección siguiente).
Normalmente, un autor de la aplicación no tiene el mismo grado de control sobre los ensamblados compartidos usados por la aplicación. Como resultado, la información de versión se comprueba en cada referencia a un ensamblado compartido. Además, .NET Framework permite a las aplicaciones y administradores invalidar la versión de un ensamblado que usa la aplicación especificando directivas de versión.
Los ensamblados compartidos no se implementan necesariamente de forma privada en una aplicación, aunque ese enfoque sigue siendo viable, especialmente si la implementación de xcopy es un requisito. Además de un directorio de aplicación privado, un ensamblado compartido también se puede implementar en la caché global de ensamblados o en cualquier dirección URL siempre que un código base describa la ubicación del ensamblado se proporciona en el archivo de configuración de la aplicación. La caché global de ensamblados es un almacén de todo el equipo para ensamblados que usan más de una aplicación. Como se describe, la implementación en la memoria caché no es un requisito, pero hay algunas ventajas para hacerlo. Por ejemplo, el almacenamiento en paralelo de varias versiones de un ensamblado se proporciona automáticamente. Además, los administradores pueden usar el almacén para implementar correcciones de errores o revisiones de seguridad que quieren que usen todas las aplicaciones de la máquina. Por último, hay algunas mejoras de rendimiento asociadas a la implementación en la caché global de ensamblados. La primera implica la comprobación de firmas de nombre seguro, como se describe en la sección Nombre seguro siguiente. La segunda mejora del rendimiento implica un conjunto de trabajo. Si varias aplicaciones usan el mismo ensamblado simultáneamente, cargar ese ensamblado desde la misma ubicación en el disco aprovecha el comportamiento de uso compartido de código proporcionado por el sistema operativo. En cambio, cargar el mismo ensamblado desde varias ubicaciones diferentes (directorios de aplicación) dará lugar a que se carguen muchas copias del mismo código. La adición de un ensamblado a la memoria caché en la máquina de un usuario final suele realizarse mediante un programa de instalación basado en Windows Installer o en alguna otra tecnología de instalación. Los ensamblados nunca terminan en la memoria caché como un efecto secundario de ejecutar alguna aplicación o navegar a una página web. En su lugar, la instalación de un ensamblado en la memoria caché requiere una acción explícita por parte del usuario. Windows Installer 2.0, que se incluye con Windows XP y Visual Studio .NET, se ha mejorado para comprender completamente el concepto de ensamblados, la caché de ensamblados y las aplicaciones aisladas. Esto significa que podrá usar todas las características de Windows Installer, como la instalación a petición y la reparación de aplicaciones, con las aplicaciones .NET.
A menudo, no es práctico compilar un paquete de instalación cada vez que quiera agregar un ensamblado a la memoria caché en máquinas de desarrollo y pruebas. Como resultado, el SDK de .NET incluye algunas herramientas para trabajar con la memoria caché de ensamblados. La primera es una herramienta denominada gacutil que permite agregar ensamblados a la memoria caché y quitarlos más adelante. Use el modificador /i para agregar un ensamblado a la memoria caché:
gacutil /i:myassembly.dll
See the .NET Framework SDK documentation for a full description of the
options supported by gacutil.
Las otras herramientas son una extensión de Shell de Windows que permite manipular la memoria caché mediante el Explorador de Windows y la herramienta de configuración de .NET Framework. Para acceder a la extensión de Shell, vaya al subdirectorio "ensamblado" en el directorio de Windows. La herramienta de configuración de .NET Framework se puede encontrar en la sección Herramientas administrativas de la Panel de control.
En la figura 4 se muestra una vista de la caché global de ensamblados mediante la extensión shell.
Figura 4. Caché global de ensamblados
Nombres seguros
Los nombres seguros se usan para habilitar los requisitos de nomenclatura más estrictos asociados a los ensamblados compartidos. Los nombres seguros tienen tres objetivos:
- Unicidad del nombre. Los ensamblados compartidos deben tener nombres que sean únicos globalmente.
- Impedir la suplantación de nombre. Los desarrolladores no quieren que otra persona publique una versión posterior de uno de los ensamblados y que se lo indique falsamente, ya sea por accidente o intencionadamente.
- Proporcione la identidad a la referencia. Al resolver una referencia a un ensamblado, se usan nombres seguros para garantizar que el ensamblado cargado procede del publicador esperado.
Los nombres seguros se implementan mediante criptografía de clave pública estándar. En general, el proceso funciona de la siguiente manera: el autor de un ensamblado genera un par de claves (o usa uno existente), firma el archivo que contiene el manifiesto con la clave privada y hace que la clave pública esté disponible para los autores de llamadas. Cuando se realizan referencias al ensamblado, el autor de la llamada registra la clave pública correspondiente a la clave privada utilizada para generar el nombre seguro. En la figura 5 se describe cómo funciona este proceso en tiempo de desarrollo, incluido cómo se almacenan las claves en los metadatos y cómo se genera la firma.
El escenario es un ensamblado denominado "Main", que hace referencia a un ensamblado denominado "MyLib". MyLib tiene un nombre compartido. Los pasos importantes se describen de la siguiente manera.
Figura 5. Proceso para implementar nombres compartidos
El desarrollador invoca un compilador que pasa un par de claves y el conjunto de archivos de origen para el ensamblado. El par de claves se genera con una herramienta de SDK denominada SN. Por ejemplo, el siguiente comando genera un nuevo par de claves y lo guarda en un archivo:
Sn –k MyKey.snk The key pair is passed to the compiler using the custom attribute System.Reflection.AssemblyKeyFileAttribute as follows: <assembly:AssemblyKeyFileAttribute("TestKey.snk")>
Cuando el compilador emite el ensamblado, la clave pública se registra en el manifiesto como parte de la identidad del ensamblado. Incluir la clave pública como parte de la identidad es lo que proporciona al ensamblado un nombre único global.
Una vez emitido el ensamblado, el archivo que contiene el manifiesto se firma con la clave privada. La firma resultante se almacena en el archivo .
Cuando el compilador genera Main, la clave pública de MyLib se almacena en el manifiesto de Main como parte de la referencia a MyLib.
En tiempo de ejecución, hay dos pasos que realiza .NET Framework para asegurarse de que los nombres seguros dan al desarrollador las ventajas necesarias. En primer lugar, la firma de nombre seguro de MyLib solo se comprueba cuando el ensamblado se instala en la caché global de ensamblados; la firma no se vuelve a comprobar cuando una aplicación carga el archivo. Si el ensamblado compartido no se implementa en la caché global de ensamblados, la firma se comprueba cada vez que se carga el archivo. La comprobación de la firma garantiza que el contenido de MyLib no se haya modificado desde que se creó el ensamblado. El segundo paso consiste en comprobar que la clave pública almacenada como parte de la referencia de Main a MyLib coincide con la clave pública que forma parte de la identidad de MyLib. Si estas claves son idénticas, el autor de Main puede estar seguro de que la versión de MyLib que se cargó procede del mismo publicador que creó la versión de MyLib con la que se creó Main. Esta comprobación de equivalencia de claves se realiza en tiempo de ejecución, cuando se resuelve la referencia de Main a MyLib.
El término "firma" suele tener en cuenta Microsoft Authenticode®. Es importante comprender que los nombres seguros y Authenticode no están relacionados de ninguna manera. Las dos técnicas tienen objetivos diferentes. En concreto, Authenticode implica un nivel de confianza asociado a un publicador, mientras que los nombres seguros no. No hay certificados ni entidades de firma de terceros asociadas a nombres seguros. Además, el propio compilador suele realizar la firma de nombres seguros como parte del proceso de compilación.
Otra consideración que merece la pena tener en cuenta es el proceso de "retraso de la firma". A menudo, es el caso de que el autor de un ensamblado no tenga acceso a la clave privada necesaria para realizar la firma completa. La mayoría de las empresas mantienen estas claves en tiendas bien protegidas a las que solo pueden acceder algunas personas. Como resultado, .NET Framework proporciona una técnica denominada "firma de retraso" que permite al desarrollador compilar un ensamblado solo con la clave pública. En este modo, el archivo no está firmado porque no se proporciona la clave privada. En su lugar, el archivo se firma más adelante mediante la utilidad SN. Consulte Retraso en la firma de un ensamblado en el SDK de .NET Framework para obtener más información sobre cómo usar la firma retrasada.
Directiva de versión
Como se acaba de describir, cada manifiesto de ensamblado registra información sobre la versión de cada dependencia en la que se creó. Sin embargo, hay algunos escenarios en los que el autor o el administrador de la aplicación pueden querer ejecutarse con una versión diferente de una dependencia en tiempo de ejecución. Por ejemplo, los administradores deben poder implementar versiones de corrección de errores sin necesidad de que todas las aplicaciones se vuelvan a compilar para recoger la corrección. Además, los administradores deben poder especificar que nunca se use una versión determinada de un ensamblado si se encuentra un agujero de seguridad u otro error grave. .NET Framework permite esta flexibilidad en el enlace de versiones a través de directivas de versión.
Números de versión del ensamblado
Cada ensamblado tiene un número de versión de cuatro partes como parte de su identidad (es decir, la versión 1.0.0.0 de algún ensamblado y la versión 2.1.0.2 son identidades completamente diferentes en lo que respecta al cargador de clases). Incluir la versión como parte de la identidad es esencial para distinguir diferentes versiones de un ensamblado con fines en paralelo.
Las partes del número de versión son principales, secundarias, de compilación y revisión. No hay semántica aplicada a las partes del número de versión. Es decir, CLR no deduce la compatibilidad ni ninguna otra característica de un ensamblado en función de cómo se asigna el número de versión. Como desarrollador, puede cambiar cualquier parte de este número como considere oportuno. Aunque no hay semántica aplicada al formato del número de versión, es probable que las organizaciones individuales puedan resultar útiles para establecer convenciones sobre cómo se cambia el número de versión. Esto ayuda a mantener la coherencia en toda una organización y facilita la determinación de cosas como la creación de un ensamblado determinado. Una convención típica es la siguiente:
Mayor o menor. Los cambios realizados en la parte principal o secundaria del número de versión indican un cambio incompatible. En virtud de esta convención, la versión 2.0.0.0 consideraría incompatible con la versión 1.0.0.0. Algunos ejemplos de un cambio incompatible serían un cambio en los tipos de algunos parámetros de método o la eliminación de un tipo o método por completo.
Crea. El número de compilación se usa normalmente para distinguir entre compilaciones diarias o versiones compatibles más pequeñas.
Revisión. Los cambios en el número de revisión se reservan normalmente para una compilación incremental necesaria para corregir un error determinado. A veces, escuchará esto como el número de "corrección de errores de emergencia" en que la revisión es lo que a menudo se cambia cuando se envía una corrección a un error específico a un cliente.
Directiva de versión predeterminada
Al resolver referencias a ensamblados compartidos, CLR determina qué versión de la dependencia se va a cargar cuando se trata de una referencia a ese ensamblado en el código. La directiva de versión predeterminada en .NET es extremadamente sencilla. Al resolver una referencia, CLR toma la versión del manifiesto del ensamblado que realiza la llamada y carga la versión de la dependencia con el mismo número de versión. De este modo, el autor de la llamada obtiene la versión exacta con la que se creó y probó. Esta directiva predeterminada tiene la propiedad que protege las aplicaciones del escenario en el que una aplicación diferente instala una nueva versión de un ensamblado compartido del que depende una aplicación existente. Recuerde que antes de .NET, las aplicaciones existentes empezarían a usar el nuevo componente compartido de forma predeterminada. Sin embargo, en .NET, la instalación de una nueva versión de un ensamblado compartido no afecta a las aplicaciones existentes.
Directiva de versión personalizada
Puede haber ocasiones en que el enlace a la versión exacta con la que se envió la aplicación no es lo que desea. Por ejemplo, un administrador puede implementar una corrección de errores crítica en un ensamblado compartido y desea que todas las aplicaciones usen esta nueva versión independientemente de la versión con la que se compilaron. Además, el proveedor de un ensamblado compartido puede haber enviado una versión de servicio a un ensamblado existente y desea que todas las aplicaciones empiecen a usar la versión del servicio en lugar de la versión original. Estos escenarios y otros se admiten en .NET Framework mediante directivas de versión.
Las directivas de versión se indican en archivos XML y son simplemente una solicitud para cargar una versión de un ensamblado en lugar de otra. Por ejemplo, la siguiente directiva de versión indica a CLR que cargue la versión 5.0.0.1 en lugar de la versión 5.0.0.0 de un ensamblado denominado MarineCtrl:
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly
<assemblyIdentity name="MarineCtrl" publicKeyToken="9335a2124541cfb9" />
<bindingRedirect oldVersion="5.0.0.0" newVersion="5.0.0.1" />
</dependentAssembly>
</assemblyBinding>
Además de redirigir desde un número de versión específico a otro, también puede redirigir desde una variedad de versiones a otra versión. Por ejemplo, la siguiente directiva redirige todas las versiones de 0.0.0.0 a 5.0.0.0 de MarineCtrl a la versión 5.0.0.1:
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly
<assemblyIdentity name="MarineCtrl" publicKeyToken="9335a2124541cfb9" />
<bindingRedirect oldVersion="0.0.0.0-5.0.0.0" newVersion="5.0.0.1" />
</dependentAssembly>
</assemblyBinding>
Niveles de directiva de versión
Hay tres niveles en los que se puede aplicar la directiva de versión en .NET: directiva específica de la aplicación, directiva de publicador y directiva de toda la máquina.
Directiva específica de la aplicación. Cada aplicación tiene un archivo de configuración opcional que puede especificar el deseo de la aplicación de enlazar a una versión diferente de un ensamblado dependiente. El nombre del archivo de configuración varía en función del tipo de aplicación. En el caso de los archivos ejecutables, el nombre del archivo de configuración es el nombre del archivo ejecutable + una extensión ".config". Por ejemplo, el archivo de configuración de "myapp.exe" sería "myapp.exe.config". Los archivos de configuración de las aplicaciones de ASP.NET siempre son "web.config".
Directiva de publicador. Aunque la directiva específica de la aplicación la establece el desarrollador o el administrador de la aplicación, el proveedor del ensamblado compartido establece la directiva de publicador. La directiva de publicador es la declaración de compatibilidad del proveedor con respecto a las diferentes versiones de su ensamblado. Por ejemplo, supongamos que el proveedor de un control de Windows Forms compartido envía una versión de servicio que contiene una serie de correcciones de errores al control. El control original era la versión 2.0.0.0 y la versión de la versión del servicio es 2.0.0.1. Dado que la nueva versión solo contiene correcciones de errores (sin cambios importantes en la API), es probable que el proveedor de control emita la directiva de publicador con la nueva versión que hace que las aplicaciones existentes que usen 2.0.0.0 ahora empiecen a usar 2.0.0.1. La directiva de publicador se expresa en XML igual que la directiva de aplicación y de toda la máquina, pero a diferencia de los demás niveles de directiva, la directiva de publicador se distribuye como un ensamblado en sí. La razón principal de esto es asegurarse de que la organización que publica la directiva para un ensamblado determinado es la misma organización que publicó el propio ensamblado. Esto se logra al requerir que tanto el ensamblado original como el ensamblado de directiva tienen un nombre seguro con el mismo par de claves.
Directiva de toda la máquina. El nivel de directiva final es una directiva de toda la máquina (a veces denominada directiva de administrador). La directiva de todo el equipo se almacena en machine.config que se encuentra en el subdirectorio "config" en el directorio de instalación de .NET Framework. El directorio de instalación es %windir%\microsoft.net\framework\%runtimeversion%. Las instrucciones de directiva realizadas en machine.config afectan a todas las aplicaciones que se ejecutan en la máquina. Los administradores usan la directiva en toda la máquina para forzar que todas las aplicaciones de un equipo determinado usen una versión determinada de un ensamblado. El escenario más de comentarios en el que se usa es cuando se ha implementado una seguridad u otra corrección de errores críticos en la caché global de ensamblados. Después de implementar el ensamblado fijo, el administrador usaría la directiva de versión de toda la máquina para asegurarse de que las aplicaciones no usan la versión anterior y rota del ensamblado.
Evaluación de directiva
Lo primero que hace CLR cuando se enlaza a un ensamblado con nombre seguro es determinar a qué versión del ensamblado se va a enlazar. El proceso se inicia leyendo el número de versión del ensamblado deseado que se registró en el manifiesto del ensamblado que realiza la referencia. A continuación, se evalúa la directiva para determinar si alguno de los niveles de directiva contiene una redirección a otra versión. Los niveles de directiva se evalúan en orden a partir de la directiva de aplicación, seguidos por el publicador y, por último, el administrador.
Una redirección encontrada en cualquier nivel invalida cualquier instrucción realizada por un nivel anterior. Por ejemplo, supongamos que el ensamblado A hace referencia al ensamblado B. La referencia a B en el manifiesto de A es la versión 1.0.0.0. Además, la directiva de publicador enviada con el ensamblado B redirige la referencia de 1.0.0.0 a 2.0.0.0. Además, hay una directiva de versión es el archivo de configuración de toda la máquina que dirige la referencia a la versión 3.0.0.0. En este caso, la instrucción realizada en el nivel de máquina invalidará la instrucción realizada en el nivel de publicador.
Omitir la directiva de publicador
Debido al orden en que se aplican los tres tipos de directiva, el redireccionamiento de la versión del publicador (directiva de publicador) puede invalidar la versión registrada en los metadatos del ensamblado que llama y cualquier directiva específica de la aplicación que se estableció. Sin embargo, forzar a una aplicación a aceptar siempre la recomendación de un publicador sobre el control de versiones puede dar lugar a DLL Hell. Después de todo, DLL Hell se debe principalmente a la dificultad de mantener la compatibilidad con versiones anteriores en los componentes compartidos. Para evitar aún más el escenario en el que una aplicación está interrumpida por la instalación de una nueva versión de un componente compartido, el sistema de directivas de versión de .NET permite que una aplicación individual omita la directiva de publicador. Es decir, una aplicación puede rechazar la recomendación del publicador sobre qué versión usar. Una aplicación puede omitir la directiva de publicador mediante el elemento "publisherPolicy" en el archivo de configuración de la aplicación:
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<publisherPolicy apply="no"/>
</assemblyBinding>
Establecer directivas de versión con la herramienta de configuración de .NET
Afortunadamente, .NET Framework se incluye con una herramienta de administración gráfica, por lo que no tiene que preocuparse por la edición manual de archivos de directivas XML. La herramienta admite tanto la directiva de versión de la aplicación como la de toda la máquina. Encontrará la herramienta en la sección Herramientas administrativas de Panel de control. La pantalla inicial de la herramienta de administración es similar a la figura 6:
Figura 6. La herramienta Administración
En los pasos siguientes se describe cómo establecer una directiva específica de la aplicación:
Agregue la aplicación al nodo Aplicaciones en la vista de árbol. Haga clic con el botón derecho en el nodo Aplicaciones y haga clic en Agregar. El cuadro de diálogo Agregar muestra una lista de aplicaciones .NET entre las que elegir. Si la aplicación no está en la lista, puede agregarla haciendo clic en Otros.
Elija el ensamblado para el que desea establecer la directiva. Haga clic con el botón derecho en el nodo Ensamblados configurados y haga clic en Agregar. Una de las opciones es elegir un ensamblado de la lista de ensamblados a los que hace referencia la aplicación y muestra el siguiente cuadro de diálogo, como se muestra en la figura 7. Elija un ensamblado y haga clic en Seleccionar.
Ilustración 7. Elección de un ensamblado
En el cuadro de diálogo Propiedades , escriba la información de la directiva de versión. Haga clic en la pestaña Directiva de enlace y escriba los números de versión deseados en la tabla, como se muestra en la figura 8.
Figura 8. Pestaña Directiva de enlace
Implementación
La implementación implica al menos dos aspectos diferentes: empaquetar el código y distribuir los paquetes a los distintos clientes y servidores en los que se ejecutará la aplicación. Un objetivo principal de .NET Framework es simplificar la implementación (especialmente el aspecto de distribución) al hacer factible la instalación de impacto cero y la implementación de xcopy. La naturaleza autodescriptible de los ensamblados nos permite quitar nuestra dependencia en el Registro, lo que hace que la instalación, desinstalación y replicación sea mucho más sencilla. Sin embargo, hay escenarios en los que xcopy no es suficiente o adecuado como mecanismo de distribución. En estos casos, .NET Framework proporciona amplios servicios de descarga de código e integración con Windows Installer.
Packaging
Hay tres opciones de empaquetado disponibles en la primera versión de .NET Framework:
- Compilado (ARCHIVOS DLL y EXE). En muchos escenarios, no se requiere ningún empaquetado especial. Una aplicación se puede implementar en el formato generado por la herramienta de desarrollo. Es decir, una colección de archivos DLL y EXE.
- Archivos cab. Los archivos cab se pueden usar para comprimir la aplicación para descargas más eficaces.
- Paquetes de Windows Installer. Microsoft Visual Studio .NET y otras herramientas de instalación permiten compilar paquetes de Windows Installer (archivos .msi). Windows Installer permite aprovechar las ventajas de la reparación de aplicaciones, la instalación a petición y otras características de administración de aplicaciones de Microsoft Windows.
Escenarios de distribución
Las aplicaciones .NET se pueden distribuir de varias maneras, como xcopy, descarga de código y a través de Windows Installer.
Para muchas aplicaciones, incluidas las aplicaciones web y los servicios web, la implementación es tan sencilla como copiar un conjunto de archivos en el disco y ejecutarlos. La desinstalación y la replicación son tan fáciles, simplemente elimine los archivos o cópielos.
.NET Framework proporciona una amplia compatibilidad con la descarga de código mediante un explorador web. Se han realizado varias mejoras en esta área, entre las que se incluyen:
- Impacto cero. No se realizan entradas del Registro en la máquina.
- Descarga incremental. Los fragmentos de un ensamblado solo se descargan cuando se hace referencia a ellos.
- Descargue aislado en la aplicación. El código descargado en nombre de una aplicación no puede afectar a otros usuarios del equipo. Un objetivo principal de nuestra compatibilidad con la descarga de código es evitar el escenario en el que un usuario descarga una nueva versión de algún componente compartido como un efecto secundario de la exploración a un sitio web determinado y tener esa nueva versión afecta negativamente a otras aplicaciones.
- No hay diálogos Authenticode. El sistema de seguridad de acceso de código se usa para permitir que el código móvil se ejecute con un nivel de confianza parcial. Los usuarios nunca se mostrarán con cuadros de diálogo que les pidan que tomen una decisión sobre si confían en el código.
Por último, .NET está totalmente integrado con Windows Installer y las características de administración de aplicaciones de Windows.
Resumen
.NET Framework habilita la instalación de impacto cero y direcciona dll Hell. Los ensamblados son las unidades de implementación autodescriptibles que se usan para habilitar estas características.
La capacidad de crear aplicaciones aisladas es fundamental porque permite compilar aplicaciones que no se verán afectadas por los cambios realizados en el sistema por otras aplicaciones. .NET Framework fomenta este tipo de aplicación a través de ensamblados privados de aplicación que se implementan dentro de la estructura de directorios de la aplicación.
En paralelo, es una parte fundamental del artículo de uso compartido y control de versiones en .NET. En paralelo, permite instalar y ejecutar varias versiones de un ensamblado simultáneamente en la máquina, y permite que cada aplicación solicite una versión específica de ese ensamblado.
CLR registra información de versión entre partes de una aplicación y usa esa información en tiempo de ejecución para asegurarse de que se carga la versión adecuada de una dependencia. Tanto los desarrolladores de aplicaciones como los administradores pueden usar directivas de versión para proporcionar cierta flexibilidad en la elección de la versión de un ensamblado determinado.
Hay varias opciones de empaquetado y distribución proporcionadas por .NET Framework, como Windows Installer, descarga de código y xcopy simple.