c++ - tuviera - msdn manifest
Trabajar con archivos de manifiesto Visual Studios C++ (4)
He escrito un código que hace uso de una biblioteca de código abierto para hacer algo de trabajo pesado. Este trabajo se realizó en Linux, con pruebas unitarias y cmake para ayudar a portarlo a Windows. Hay un requisito para ejecutarlo en ambas plataformas.
Me gusta Linux y me gusta cmake y me gusta que pueda obtener archivos de estudios visuales generados automáticamente. Como está ahora, en Windows todo se compilará y se vinculará y generará los ejecutables de prueba.
Sin embargo, para llegar a este punto tuve que luchar con Windows durante varios días, aprendiendo todo sobre los archivos manifiestos y los paquetes redistribuibles.
En mi opinión:
Con VS 2005, Microsoft creó dlls de lado a lado. La motivación para esto es que antes, múltiples aplicaciones instalarían diferentes versiones del mismo dll, causando que las aplicaciones previamente instaladas y en funcionamiento colapsen (es decir, "Dll Hell"). Dobles de lado a lado corrigen esto, ya que ahora hay un "archivo de manifiesto" adjunto a cada ejecutable / dll que especifica qué versión se debe ejecutar.
Esto está todo bien. Las aplicaciones ya no deberían fallar misteriosamente. Sin embargo...
Microsoft parece lanzar un nuevo conjunto de dlls de sistema con cada versión de Visual Studios. Además, como mencioné anteriormente, soy un desarrollador que intenta vincular a una biblioteca de terceros. A menudo, estas cosas se distribuyen como un "dll precompilado". Ahora, ¿qué sucede cuando un dll precompilado compilado con una versión de estudios visuales está vinculado a una aplicación que utiliza otra versión de estudios visuales?
Por lo que he leído en Internet, sucede algo malo. Afortunadamente, nunca llegué tan lejos: me encontré con el problema "MSVCR80.dll no encontrado" al ejecutar el ejecutable y así comencé mi incursión en todo este problema de manifiesto.
Finalmente llegué a la conclusión de que la única forma de hacer que esto funcione (además de vincular estáticamente todo) es que todas las bibliotecas de terceros deben compilarse utilizando la misma versión de Visual Studios, es decir, no usar dll precompilados, descargar la fuente, construir un nuevo dll y usar eso en su lugar.
¿Es esto verdad? ¿Me he perdido algo?
Además, si este parece ser el caso, entonces no puedo evitar pensar que Microsoft lo hizo a propósito por nefastas razones.
No solo rompe todos los binarios precompilados, por lo que es innecesariamente difícil usar binarios precompilados, si trabaja para una empresa de software que hace uso de bibliotecas propietarias de terceros, cada vez que se actualice a la última versión de estudios visuales, su empresa debe ahora haga lo mismo o el código ya no se ejecutará.
Como un aparte, ¿cómo lo evita Linux? Aunque dije que prefería desarrollar y entiendo la mecánica de los enlaces, no he mantenido ninguna aplicación lo suficiente como para encontrarme con este tipo de problema de versiones de bibliotecas compartidas de bajo nivel.
Finalmente, para resumir: ¿es posible usar binarios precompilados con este nuevo esquema de manifiesto? Si es así, ¿cuál fue mi error? Si no es así, ¿piensa honestamente Microsoft que esto facilita el desarrollo de aplicaciones?
Actualización: una pregunta más concisa: ¿cómo evita Linux el uso de los archivos Manifest?
Finalmente llegué a la conclusión de que la única forma de hacer que esto funcione (además de vincular estáticamente todo) es que todas las bibliotecas de terceros deben compilarse utilizando la misma versión de Visual Studios, es decir, no usar dll precompilados, descargar la fuente, construir un nuevo dll y usar eso en su lugar.
Alternativamente (y la solución que tenemos que usar donde trabajo) es que si las librerías de terceros que necesita usar están compiladas (o disponibles como compiladas) con la misma versión de compilador, puede "simplemente" usar esa versión. Puede ser un "tener que" arrastrar el uso de VC6, por ejemplo, pero si hay una biblioteca que debe usar y su fuente no está disponible y así es como viene, sus opciones son tristemente limitadas de lo contrario.
... como yo lo entiendo :)
(Mi línea de trabajo no está en Windows, aunque luchamos con DLL en Windows desde la perspectiva del usuario de vez en cuando; sin embargo, tenemos que usar versiones específicas de compiladores y obtener versiones de software de terceros que están todos construidos con el mismo compilador. Afortunadamente, todos los proveedores tienden a estar bastante actualizados, ya que han estado haciendo este tipo de soporte durante muchos años).
Si una DLL de terceros asigna memoria que necesita liberar, entonces la DLL ha roto una de las principales reglas de envío de DLL precompiladas. Exactamente por esta razón.
Si un archivo DLL se envía únicamente en forma binaria, también debe enviar todos los componentes redistribuibles con los que está vinculado y sus puntos de entrada deben aislar al llamante de cualquier posible problema de versión de la biblioteca en tiempo de ejecución, como diferentes asignadores. Si siguen esas reglas, entonces no deberías sufrir. Si no lo hacen, entonces usted tendrá dolor y sufrimiento o tendrá que quejarse a los autores externos.
Si una DLL de terceros asignará memoria y necesita liberarla, necesitará las mismas bibliotecas de tiempo de ejecución. Si el DLL tiene funciones de asignar y desasignar, puede estar bien.
Si el archivo DLL de terceros utiliza contenedores std
, como vector
, etc., podría tener problemas ya que el diseño de los objetos puede ser completamente diferente.
Es posible hacer que las cosas funcionen, pero hay algunas limitaciones. Me he encontrado con los dos problemas que he enumerado anteriormente.
Todos los componentes en su aplicación deben compartir el mismo tiempo de ejecución. Cuando este no es el caso, te encuentras con problemas extraños como afirmar en las instrucciones de eliminación.
Esto es lo mismo en todas las plataformas. No es algo inventado por Microsoft.
Puede sortear este problema de "solo un tiempo de ejecución" al estar consciente de dónde pueden reprimir los tiempos de ejecución. Esto ocurre principalmente en casos en los que asigna memoria en un módulo y lo libera en otro.
a.dll
dllexport void* createBla() { return malloc( 100 ); }
b.dll
void consumeBla() { void* p = createBla(); free( p ); }
Cuando a.dll y b.dll están vinculados a diferentes rumtimes, esto falla, porque las funciones de tiempo de ejecución implementan su propio montón.
Puede evitar fácilmente este problema proporcionando una función destroyBla que debe invocarse para liberar la memoria.
Hay varios puntos donde puede tener problemas con el tiempo de ejecución, pero la mayoría se puede evitar envolviendo estos constructos.
Para referencia :
- no asignar / liberar memoria / objetos a través de los límites del módulo
- no use objetos complejos en su interfaz dll. (por ejemplo, std :: string, ...)
- no use elaborados mecanismos C ++ a través de los límites dll. (typeinfo, excepciones C ++, ...)
- ...
Pero esto no es un problema con los manifiestos.
Un manifiesto contiene la información de versión del tiempo de ejecución utilizado por el módulo y se integra en el binario (exe / dll) por el vinculador. Cuando se carga una aplicación y se deben resolver sus dependencias, el cargador examina la información de manifiesto incrustada en el archivo exe y utiliza la versión correspondiente de los dlls de tiempo de ejecución de la carpeta WinSxS. No puede simplemente copiar el tiempo de ejecución u otros módulos en la carpeta WinSxS. Debe instalar el tiempo de ejecución ofrecido por Microsoft. Hay paquetes MSI suministrados por Microsoft que pueden ejecutarse cuando instala su software en una máquina de prueba / usuario final.
Así que instale su tiempo de ejecución antes de usar su aplicación, y no obtendrá un error de ''falta de dependencia''.
(Actualizado a la pregunta "¿Cómo evita Linux el uso de archivos Manifest?")
¿Qué es un archivo manifiesto?
Los archivos de manifiesto se introdujeron para colocar la información de desambiguación junto a una biblioteca de vínculos dinámicos / ejecutables existente o directamente incorporada en este archivo.
Esto se hace especificando la versión específica de dlls que se cargarán al iniciar las dependencias de aplicación / carga.
(Hay varias otras cosas que puede hacer con los archivos de manifiesto, por ejemplo, algunos metadatos se pueden poner aquí)
¿Por qué se hace esto?
La versión no es parte del nombre dll debido a razones históricas. Por lo tanto, "comctl32.dll" se denomina de esta manera en todas las versiones. (Entonces, el comctl32 en Win2k es diferente del de XP o Vista). Para especificar qué versión realmente desea (y ha probado en contra), coloca la información de la versión en el archivo "appname.exe.manifest" (o incruste este archivo / información).
¿Por qué se hizo de esta manera?
Muchos programas instalaron sus dlls en el directorio system32 en systemrootdir. Esto se hizo para permitir que las correcciones de errores a las bibliotecas compartidas se implementen fácilmente para todas las aplicaciones dependientes. Y en los días de memoria limitada, las bibliotecas compartidas redujeron la huella de memoria cuando varias aplicaciones usaban las mismas bibliotecas.
Este concepto fue abusado por muchos programadores, cuando instalaron todos sus dlls en este directorio; a veces sobrescribiendo las versiones más nuevas de las bibliotecas compartidas con las más antiguas. A veces las bibliotecas cambiaban silenciosamente en su comportamiento, de modo que las aplicaciones dependientes se bloqueaban.
Esto condujo al enfoque de "Distribuir todos los dlls en el directorio de la aplicación".
¿Por qué fue esto malo?
Cuando aparecieron errores, todos los dlls dispersos en varios directorios tuvieron que ser actualizados. (gdiplus.dll) En otros casos esto ni siquiera era posible (componentes de Windows)
El enfoque manifiesto
Este enfoque resuelve todos los problemas anteriores. Puede instalar los dlls en un lugar central, donde el programador no puede interferir. Aquí los dlls pueden actualizarse (actualizando el dll en la carpeta WinSxS) y el cargador carga el dll ''correcto''. (El cargador dll realiza la coincidencia de versión).
¿Por qué Linux no tiene esta mecánica?
Tengo varias conjeturas. (Esto es solo adivinar ...)
- La mayoría de las cosas son de código abierto, por lo que recompilar para una corrección de errores no es un problema para el público objetivo.
- Como solo hay un ''tiempo de ejecución'' (el tiempo de ejecución de gcc), el problema con el tiempo de ejecución compartido / límites de la biblioteca no ocurre tan a menudo
- Muchos componentes usan C en el nivel de interfaz, donde estos problemas simplemente no ocurren si se hace bien
- La versión de las bibliotecas está en la mayoría de los casos incrustada en el nombre de su archivo.
- La mayoría de las aplicaciones están vinculadas estáticamente a sus bibliotecas, por lo que no puede ocurrir un infierno.
- El tiempo de ejecución de GCC se mantuvo muy estable ABI para que estos problemas no pudieran ocurrir.