Nota
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
In-Process Side-by-Side
Jesse Kaplan y Luiz Fernando Santos
Publique sus preguntas y comentarios en el blog del equipo de CLR.
Al crear .NET Framework 4, uno de los problemas más serios con los que nos encontramos fue mantener la compatibilidad con las versiones anteriores y al mismo tiempo agregar nuevas características y funcionalidades. Seguimos procesos estrictos que requieren aprobación para cualquier cambio que pueda generar problemas de compatibilidad (la mayoría fueron rechazados) y realizamos un laboratorio de compatibilidad con cientos de aplicaciones reales para encontrar cualquier error accidental.
Pero cada vez que resolvemos un error, existe la posibilidad de que alguna aplicación dependa de dicho comportamiento erróneo. A veces las aplicaciones establecen dependencias sobre las que hemos advertido, como el comportamiento de las API privadas o el texto de descripción de excepciones.
Desde que se presentó .NET Framework, hemos tenido una buena solución para la compatibilidad de aplicaciones: permitir que se instalen varias versiones de .NET Framework en el mismo equipo al mismo tiempo. Esto permite que dos aplicaciones diferentes, creadas en dos versiones diferentes e instaladas en un mismo equipo, puedan ejecutarse con la versión correspondiente.
El problema de los complementos
Esto funciona correctamente cuando cada aplicación tiene su propio proceso, pero los complementos son problemas mucho más complicados. Suponga que está ejecutando un programa como Outlook que hospeda complementos COM, incluidos complementos COM administrados, y que tiene dos versiones del tiempo de ejecución, y cada complemento creado en una versión diferente, instaladas en su equipo. ¿Qué tiempo de ejecución debe elegir? Está claro que cargar un complemento más reciente en un tiempo de ejecución anterior no funcionará.
Por otro lado, debido al alto nivel de compatibilidad, un complemento anterior generalmente se ejecutará correctamente en un tiempo de ejecución más reciente. Para que todos los complementos puedan funcionar, siempre elegimos el tiempo de ejecución más reciente para activaciones COM administradas. Aunque sólo tenga instalados complementos anteriores, no tenemos forma de saber cuándo se activa ese complemento, por lo que se carga el tiempo de ejecución más reciente.
Un efecto secundario de esta política de activación se presenta cuando un usuario instala una aplicación nueva con una versión más reciente del tiempo de ejecución, las aplicaciones no relacionadas que usan complementos COM administrados, creadas con versiones anteriores, empiezan a ejecutarse en un tiempo de ejecución más reciente y pueden generar errores.
Para .NET Framework 3.0 y 3.5, resolvimos este problema mediante una política muy estricta: cada versión fue una versión agregada y sólo agregaba ensamblados nuevos a la versión anterior con el mismo tiempo de ejecución subyacente. Esto evitó problemas de compatibilidad al instalarlos en un equipo en el que se ejecutara .NET Framework 2.0. Esto significa que al ejecutar una aplicación en .NET Framework 3.5, en realidad se está ejecutando en tiempo de ejecución 2.0, con ensamblados adicionales sobre el mismo. Sin embargo, esto también significa que no logramos innovar en los ensamblajes .NET 2.0, que incluyen funcionalidades clave, como el recolector de elementos no utilizados, “justo a tiempo” (JIT) y bibliotecas de clases base.
Con .NET Framework 4, hemos implementado un método que nos brinda un alto grado de compatibilidad y complementos existentes que nunca producen errores. Ahora podemos ejecutar ambos complementos de .NET 2.0 y .NET 4 en el mismo proceso y al mismo tiempo. A este método lo denominamos In-Process Side-by-Side o In-Proc SxS.
Si bien In-Proc SxS soluciona la mayoría de los problemas más comunes de compatibilidad, no resuelve todo. En esta columna explicaremos por qué decidimos crear In-Proc SxS, cómo funciona y qué problemas no soluciona. Para usuarios que escriben aplicaciones o complementos normales, In-Proc SxS funciona bien, automáticamente. Para usuarios que escriben hosts que pueden aprovechar In-Proc SxS, también describiremos las API de hospedaje actualizadas y brindaremos pautas para usarlas.
Una visita a la oficina de Ray Ozzie
A fines de 2005, casi todos los principales ejecutivos de alto nivel de Microsoft repentinamente no podían tener acceso a su correo electrónico en ninguno de sus equipos principales. Sin razón aparente, cada vez que abrían Outlook, se bloqueaba, se reiniciaba y se volvía a bloquear en forma continua. No había actualizaciones recientes de Outlook u otro motivo que pudieran estar ocasionando esto. Pronto se detectó que se debía a una excepción administrada, emitida por un complemento administrado. Se envió a un amigo mío [“Mío” hace referencia al coautor de la columna Jesse Kaplan (nota del director)] del equipo de Visual Studio Tools para Office (VSTO), responsable de complementos administrados en Office, para diagnosticar el problema en el equipo de una de las víctimas más importantes de este error: Ray Ozzie, que se desempeñaba como director general del Área Técnica en ese momento.
Una vez en la oficina de Ray, mi amigo rápidamente determinó que se había instalado una versión beta de .NET Framework 2.0 mediante un programa beta interno e identificó el complemento de Office que estaba ocasionando el problema. Como director de proyecto de compatibilidad del equipo de CLR, instalé el complemento y lo quité de allí.
Rápidamente detectamos lo que había ocurrido: el complemento tenía una condición de carrera en la que iniciaba nueve subprocesos diferentes, y luego de iniciar cada uno, inicializaba los datos que el subproceso procesaba (consulte la Figura 1) Los codificadores tuvieron suerte con el tiempo, pero una vez que se instaló .NET Framework 2.0, el complemento se actualizó automáticamente a .NET 2.0, por las razones antes mencionadas. No obstante, .NET 2.0 era levemente más rápido al iniciar los subprocesos, por lo que la condición de carrera latente comenzó a surgir en forma constante.
Figura 1 Código del complemento de Office
Thread [] threads = new Thread[9];
for (int i=0; i<9; i++)
{
Worker worker = new Worker();
threads[i] = new ThreadStart(worker.Work);
threads[i].Start(); //This line starts the thread executing
worker.identity =i; //This line initializes a value that
//the thread needs to run properly
}
El error de esta aplicación nos dio una lección en compatibilidad: independientemente de que hagamos todo lo posible por evitar cambios de comportamiento que puedan ocasionar errores en las aplicaciones, cosas simples como una mejora del rendimiento pueden generar problemas en las aplicaciones y los complementos, que no permitirán el correcto funcionamiento si no se ejecutan en el tiempo de ejecución sobre el que se crearon y probaron. Nos dimos cuenta de que no había forma de mejorar la plataforma de manera significativa y asegurar que las aplicaciones como la anterior se ejecutaran perfectamente con la versión más actualizada.
Error de instalación
Durante las pruebas de compatibilidad, encontramos una aplicación que se ejecutaba correctamente si .NET 2.0 se instalaba después de la aplicación pero que no funcionaba si la aplicación se instalaba en un equipo que tuviese las versiones 1.1 (la versión en la que se creó la aplicación) y 2.0. Nos llevó un tiempo determinar lo que estaba ocurriendo, pero detectamos que el problema se encontraba en un fragmento de código que se estaba ejecutando dentro del instalador que, nuevamente, se estaba actualizando a 2.0 y esta vez tenía problemas para encontrar el directorio del marco.
La lógica de detección era claramente frágil e incorrecta, como se puede observar aquí:
string sFrameworkVersion = System.Environment.Version.ToString();
string sWinFrameworkPath = session.Property["WindowsFolder"] +
"Microsoft.NET\\Framework\\v" +
sFrameworkVersion.Substring(0,8) + "\\";
Sin embargo, incluso después de reparar el error, la aplicación no podía ejecutarse correctamente luego de la instalación. Aquí tenemos la solución:
string sWinFrameworkPath = System.Runtime.InteropServices.RuntimeEnvironment.GetRuntimeDirectory();
Ocurre que el instalador buscaba el directorio del marco para obtener la ruta de caspol.exe y dar permiso a la aplicación para ejecutarse en ese marco. El error se producía incluso después de encontrar la ruta porque se había otorgado permiso para ejecutarse en el CLR 2.0 aunque la aplicación se ejecuta en el CLR 1.1. Éste es el código del problema:
System.Diagnostics.Process.Start(sWinFrameworkPath + "caspol.exe " + casPolArgs);
Compatibilidad con In-Process Side-by-Side
El motivo principal que ocasiona problemas en todos estos casos, según comprendimos, es que era imposible realizar adiciones o cambios significativos en nuestra plataforma y aun así asegurar que la versión más reciente pudiera ejecutar cualquier aplicación al igual que las versiones anteriores.
Desde un principio, .NET Framework intentó resolver este problema admitiendo instalaciones en paralelo de varias versiones del marco en un equipo y que cada aplicación eligiera qué versión deseaba ejecutar.
Desafortunadamente, la limitación de un tiempo de ejecución por proceso para los componentes COM administrados y los escenarios de extensibilidad, donde había aplicaciones independientes múltiples ejecutándose en el mismo proceso, significó que no hubiese una única opción que funcionara para todos. Esta limitación significó que algunos componentes no obtendrían el tiempo de ejecución que deseaban y que, más allá del esfuerzo realizado por mantener la compatibilidad, cierto porcentaje podría fallar.
Nuestra nueva capacidad de cargar varias versiones del tiempo de ejecución en un proceso resuelve estos problemas.
Principios fundamentales
Para que pueda comprender mejor algunas de las decisiones que tomamos y el comportamiento detallado que describimos más adelante en esta columna, es importante analizar los principios fundamentales en los que nos basamos al diseñar esta característica.
- La instalación de una nueva versión de .NET Framework, no debe afectar las aplicaciones existentes.
- Las aplicaciones y los complementos deben ejecutarse en la versión del marco en la cual se crearon y probaron.
- Existen situaciones, como al usar bibliotecas, en las cuales no podemos ejecutar código en el marco en el cual se crearon las bibliotecas, por lo tanto debemos asegurar una compatibilidad total con versiones anteriores.
Todas las aplicaciones y complementos existentes deben continuar ejecutándose en las versiones del marco en las que se crearon y configuraron y no deben ver la versión nueva a menos que lo soliciten específicamente. Esta siempre ha sido la regla para aplicaciones administradas, pero ahora también se aplica a complementos COM administrados y consumidores de las API de hospedaje del tiempo de ejecución.
Además de asegurar que las aplicaciones se ejecuten en la versión del tiempo de ejecución en la que se crearon, también debemos asegurar que la transición a un tiempo de ejecución más reciente sea fácil, para poder mantener la compatibilidad de .NET Framework 4 en el mismo nivel o en un nivel superior que con .NET 2.0.
Información general del comportamiento
El tiempo de ejecución de .NET Framework 4, y todos los tiempos de ejecución futuros, se podrán ejecutar entre sí durante el proceso. Si bien no migramos esta funcionalidad a tiempos de ejecución anteriores (1.0 a 3.5), nos aseguramos de que el 4 y los posteriores puedan ejecutarse durante el proceso con cualquier tiempo de ejecución anterior. En otras palabras, el 4, 5 y 2.0 se podrán cargar en el mismo proceso, pero el 1.1 y 2.0 no se podrán cargar en el mismo proceso. .Las versiones 2.0 a 3.5 de NET Framework se ejecutan en el tiempo de ejecución 2.0 y por lo tanto no tienen problemas entre sí, según se indica en la Figura 2.
Versión de .NET Framework | ||||
1.1 | 2.0 hasta 3.5 | 4 | 5 | |
1.1 | No disponible | No | Sí | Sí |
2.0 hasta 3.5 | No | No disponible | Sí | Sí |
4 | Sí | Sí | No disponible | Sí |
5 | Sí | Sí | Sí | No disponible |
Figura 2 ¿Estos tiempos de ejecución se cargarán en el mismo proceso?
Ninguna de las aplicaciones y los componentes existentes deben notar diferencias cuando se instala el tiempo de ejecución de .NET Framework 4: deben continuar admitiendo cualquier tiempo de ejecución sobre el que fueron creados. Las aplicaciones y los componentes COM administrados creados en .NET 4 se ejecutarán en el tiempo de ejecución 4. Los hosts que deseen interactuar con el tiempo de ejecución 4 deberán solicitarlo específicamente.
¿Qué significa para usted In-Process Side-by-Side?
Usuarios finales y administradores del sistema: ahora puede estar seguro de que cuando instale una nueva versión del tiempo de ejecución, ya sea de manera independiente o con una aplicación, no afectará a su equipo y todas las aplicaciones existentes continuarán ejecutándose como lo hacían hasta el momento.
Desarrolladores de aplicaciones: In-Proc SxS casi no afecta a los desarrolladores de aplicaciones. Las aplicaciones siempre se ejecutaron de forma predeterminada en la versión del marco en que se crearon y probaron, lo cual no ha cambiado. El único cambio en el comportamiento que hemos realizado y que afecta a los desarrolladores de aplicaciones es que ya no ejecutaremos en forma automática una aplicación creada con un tiempo de ejecución anterior en una versión más reciente cuando la versión original no está. En cambio, le solicitaremos al usuario que descargue la versión original y proporcionaremos un vínculo para hacerlo más fácil.
De todas formas, proporcionamos opciones de configuración que le permiten indicar en qué versiones del marco desea ejecutar la aplicación, para que sea posible ejecutar una aplicación anterior en un tiempo de ejecución más reciente, aunque no lo haremos en forma automática.
Desarrolladores de bibliotecas y consumidores: In-Proc SxS no resuelve los problemas de compatibilidad con los que se enfrentan los desarrolladores de bibliotecas. Cualquier biblioteca cargada directamente por una aplicación, mediante una referencia directa o por un Assembly.Load*, continuará cargándose directamente en el tiempo de ejecución y AppDomain de la aplicación que la carga. Esto significa que si una aplicación está compilada para ejecutarse en el tiempo de ejecución de .NET Framework 4 pero tiene ensamblados dependientes creados en .NET 2.0, dichos ensamblados también se cargarán en el tiempo de ejecución de .NET 4. Por lo tanto, recomendamos probar las bibliotecas con todas las versiones del marco que desea admitir. Esta es una de las razones por las cuales hemos mantenido nuestro elevado nivel de compatibilidad con versiones anteriores.
Desarrolladores de componentes de COM administrados: Anteriormente, estos componentes podían ejecutarse automáticamente con la versión más reciente del tiempo de ejecución instalada en el equipo. Actualmente, los componentes anteriores a .NET Framework 4 se activarán con el tiempo de ejecución más reciente (3.5 o anterior) y todos los componentes más recientes se cargarán con la versión en la que se crearon, según se indica en la Figura 3.
Componentes de COM administrados: ¿En qué versión se ejecutará mi componente? | ||||
Versión del componente | 1.1 | 2.0 hasta 3.5 | 4 | 5 |
Estado del equipo/proceso | ||||
1.1, 3.5, 4, 5 instalados Ninguno cargado |
3.5 | 3.5 | 4 | 5 |
1.1, 3.5, 4, 5 instalados 1.1, 4 cargados |
1.1 | Error al cargar* | 4 | 5 |
1.1, 3.5, 4, 5 instalados 3.5, 5 cargados |
3.5 | 3.5 | 4 | 5 |
1.1, 3.5, 5 instalados Ninguno cargado |
3.5 | 3.5 | Error al cargar en forma predeterminada** | 5 |
*Estos componentes tampoco se podían cargar anteriormente. **Al registrar los componentes, ahora puede especificar un intervalo de versiones que admiten, para poder configurar el componente para que se ejecute en tiempo de ejecución 5 o futuros si los ha probado. |
Figura 3 Componentes COM administrados e interoperabilidad con el tiempo de ejecución
Desarrolladores de extensiones de shell: extensión de shell es un nombre general que se aplica a una amplia variedad de puntos de extensibilidad dentro del shell de Windows. Dos ejemplos muy comunes son las extensiones que se agregan al menú contextual que aparece al hacer clic con el botón secundario para archivos y carpetas y los que proporcionan iconos personalizados o superposición de iconos para archivos y carpetas.
Estas extensiones se muestran mediante un modelo COM estándar y su principal característica es que se cargan en el proceso con cualquier aplicación. Este aspecto y el hecho de que se permite sólo un CLR por proceso ocasionaron problemas con las extensiones de shell administradas. Es decir:
- La extensión de shell se escribió en una versión de tiempo de ejecución N.
- Debe poder cargarse en cualquier aplicación en el equipo, incluidas aquellas creadas con versiones anteriores y posteriores a N.
- Si la aplicación se creó en una versión posterior de la extensión, en general funciona correctamente, a menos que haya problemas de compatibilidad.
- Si la aplicación se creó en una versión anterior de la extensión, seguramente no funcionará dado que el tiempo de ejecución anterior no puede ejecutar una extensión de shell creada en una versión más reciente.
- Si la extensión de shell se cargó antes de los componentes de código administrado de la aplicación, la opción seleccionada de versión del marco podría ocasionar problemas con la aplicación e interrumpir todos los procesos.
Estos problemas nos llevaron a que no recomendáramos, ni respaldáramos, el desarrollo de extensiones de shell en proceso que usan código administrado. Como se puede observar en el foro de MSDN donde se explica el problema, esta fue una opción complicada para nosotros y nuestros clientes. https://social.msdn.microsoft.com/forums/en-US/netfxbcl/thread/1428326d-7950-42b4-ad94-8e962124043e. Las extensiones de shell son muy comunes y uno de los últimos lugares donde los desarrolladores de ciertos tipos de aplicaciones se ven obligados a escribir código nativo. Desafortunadamente, debido a nuestra limitación que sólo nos permite un tiempo de ejecución por proceso, no podemos admitirlas.
Con la capacidad de tener varios tiempos de ejecución en proceso con cualquier otro tiempo de ejecución, ahora podemos escribir extensiones de shell administradas; incluso aquellas que se ejecutan en el proceso con aplicaciones arbitrarias en el equipo. Aún no podemos escribir extensiones de shell usando cualquier versión anterior a .NET Framework 4 porque dichas versiones de tiempo de ejecución no se cargan en el proceso entre sí y, en muchos casos, ocasionarían problemas.
Los desarrolladores de extensiones de shell, administradas y nativas, aún deben tener mucho cuidado y asegurarse de que puedan ejecutarse en una amplia variedad de entornos y funcionar correctamente entre sí. A medida que nos acerquemos a las versiones finales (RTM), brindaremos más información y ejemplos que le permitirán desarrollar extensiones de shell administradas de alta calidad que funcionen correctamente en el ecosistema de Windows.
Hosts de código administrado: Si hospeda código administrado usando activación COM nativa, no deberá hacer nada en especial para trabajar con varios tiempos de ejecución. Simplemente, deberá activar los componentes como lo hace habitualmente y el tiempo de ejecución los cargará según las reglas enumeradas en la Figura 3.
Si alguna vez utilizó alguna de nuestras API de hospedaje anteriores a .NET Framework 4, habrá notado que todas suponen que sólo se carga un tiempo de ejecución en el proceso. Por lo tanto, si hospeda el tiempo de ejecución usando nuestras API nativas, deberá modificar su host para que sea compatible con In-Proc-SxS. Como parte de nuestro nuevo método de tener varios tiempos de ejecución en proceso, hemos descartado las API de hospedaje anteriores que sólo admiten un tiempo de ejecución y agregado un nuevo conjunto de API de hospedaje que le permitirán administrar un entorno con varios tiempos de ejecución. MSDN contará con toda la documentación necesaria para las nuevas API, aunque serán relativamente fáciles de usar si tiene experiencia usando las actuales.
Uno de los principales desafíos con los que nos encontramos al desarrollar In-Proc SxS, fue cómo actualizar el comportamiento de las API de hospedaje actual que admiten sólo un tiempo de ejecución. Contábamos con varias opciones disponibles, pero al seguir los principios planteados anteriormente en esta columna, sólo nos quedaba la siguiente pauta: las API deben comportarse de manera tal que cuando se instale .NET Framework 4 en un equipo, conserven el mismo comportamiento previo. Esto significa que sólo pueden admitir un tiempo de ejecución en cada proceso y que aunque se utilicen de manera tal que se activaría el tiempo de ejecución más reciente en el equipo, sólo le darán el tiempo de ejecución más reciente con una versión anterior a 4.
Existen formas de “enlazar” estas API al tiempo de ejecución de .NET Framework 4 traspasándoles en forma explícita el número de versión 4 o configurando la aplicación de cierta forma, pero, una vez más, esto ocurrirá sólo si solicita en forma específica el tiempo de ejecución 4, no si solicitó el “más reciente”.
En resumen: el código que utilice las API de hospedaje existentes seguirá funcionando cuando .NET Framework 4 se instale pero obtendrá una vista del proceso que sólo ve un tiempo de ejecución cargado. Además, para mantener la compatibilidad, generalmente podrán interactuar sólo con las versiones anteriores a la 4. Los detalles de la versión que se elige para cada una de estas API anteriores estará disponible en MSDN, pero las reglas antes mencionadas le permitirán comprender cómo determinamos estos comportamientos. Si desea interactuar con varios tiempos de ejecución, deberá pasar a las nuevas API.
Desarrolladores C++/CLI: C++/CLI, o C++ administrado, es una interesante tecnología que le permite a los desarrolladores combinar código administrado y nativo en el mismo ensamblado y administrar las transiciones entre ambos sin tener que interactuar.
Debido a dicha arquitectura, habrá límites en la forma de usar estos ensamblados en este nuevo mundo. Uno de los principales problemas es que si permitíamos que estos ensamblados se cargaran varias veces por proceso, aun así deberíamos mantener aisladas las secciones de datos administrados y nativos. Es decir, cargar ambas secciones dos veces, lo cual no está permitido por el cargador de Windows nativo. Los detalles completos de por qué tenemos las siguientes restricciones están fuera del ámbito de esta columna, pero estarán disponibles en otra parte a medida que nos acerquemos al lanzamiento.
La restricción básica es que la versión anterior de .NET Framework 2.0, basada en ensamblados C++/CLI, sólo puede cargarse en el tiempo de ejecución de .NET 2.0. Si proporciona una biblioteca de C++/CLI 2.0 y desea que sea fácil de adoptar desde la versión 4 en adelante, deberá compilarla con cada versión que desea que se cargue. Si adopta una de estas bibliotecas, deberá obtener una versión actualizada del desarrollador de la biblioteca o, en última instancia, podrá configurar su aplicación para que bloquee de su proceso los tiempos de ejecución anteriores al 4.
No más problemas
Microsoft .NET Framework 4 es la versión más compatible con versiones anteriores de .NET. A través de In-Proc SxS, Microsoft garantiza que una acción tan simple como instalar .NET 4 no generará problemas en ninguna aplicación existente y que todo lo que esté instalado en el equipo seguirá funcionando como hasta el momento.
Los usuarios finales ya no deberán preocuparse porque la instalación del marco, ya sea directamente o con una aplicación, pueda ocasionar problemas en alguna de las aplicaciones existentes en el equipo.
Las empresas y los profesionales de TI podrán adoptar las versiones más recientes del marco según lo deseen, sin tener que preocuparse porque las diferentes versiones y aplicaciones utilizadas puedan ocasionar conflictos entre sí.
Los desarrolladores podrán utilizar la versión más reciente del marco para crear sus aplicaciones y podrán garantizar a sus clientes la seguridad de su implementación.
Finalmente, los desarrolladores de complementos y hosts podrán estar seguros de que obtendrán la versión del marco que desean, sin afectar a nadie y sus códigos continuarán funcionando aun cuando se instalen versiones nuevas del marco.
Jesse Kaplan es administrador de programas de interoperabilidad nativa/administrada para el equipo de CLR de Microsoft. Entre sus anteriores responsabilidades se incluían los problemas de compatibilidad y extensibilidad.
Luiz Santos, anteriormente parte del equipo de CLR, es administrador de programas en el equipo de conectividad de SQL, donde es responsable de los proveedores administrados ADO.NET, incluidos SqlClient, ODBCClient y OLEDBClient.
Gracias a los siguientes expertos técnicos por su ayuda en la revisión de este artículo: Joshua Goodman, Simon Hall y Sean Selitrennikoff