2009-02-26 21 views
31

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.Trabajando con Visual Studios C++ manifest files

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 archivos manifiestos y paquetes redistribuibles.

En lo que a mi entender va:

Con VS 2005, Microsoft creó lado a dlls secundarios. 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á muy 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. descarga la fuente, construye una nueva dll y úsalo 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 nefastos motivos.

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, y siempre que actualicen a la última versión de estudios visuales - su empresa ahora debe hacer lo mismo o el código ya no se ejecutará.

Como un lado, ¿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?

+0

No entiendo por qué esta pregunta ha sido rechazada ... parece estar pensada ... y mientras, Microsoft está un poco cansada de la frustración, bien atemperada y dispuesta a razonar. – Beska

+0

¡Supongo que es mucho texto para leer! Si la pregunta fuera más concisa, podría tener más suerte. –

+0

Cuando el título de una pregunta pregunta "¿x intenta ser malo?" no se sorprenda cuando la gente lo rechaza. :) – bk1e

Respuesta

26

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 solucionar este problema de "solo un tiempo de ejecución" al saber dónde pueden fallar 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, esta accidentes, ya que las funciones de tiempo de ejecución implementan su propia pila.

Puede evitar fácilmente este problema proporcionando una función destroyBla que se debe invocar 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.

Como referencia:

  • no asignar/memoria libre/objetos a través de límites de módulos
  • no utilizan objetos complejos en su interfaz de DLL. (por ejemplo, std :: string, ...)
  • No utilice complejos mecanismos C++ a través de los límites dll. (Typeinfo, excepciones de 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 "¿Cómo evita el uso de Linux Los archivos de manifiesto" cuestión)

¿Qué es un archivo de manifiesto?

Los archivos de manifiesto se introdujeron para colocar la información de desambiguación junto a una biblioteca de vínculos ejecutables/dinámicos existente o directamente incrustado en este archivo.

Esto se hace especificando la versión específica de los archivos DLL 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 meta-datos se pueden poner aquí)

¿Por qué se hace esto?

La versión no forma 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 así?

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 lleva al enfoque de "Distribuir todos los dlls en el directorio de la aplicación".

¿Por qué fue tan malo?

Cuando aparecieron errores, todos los dlls dispersos en varios directorios tuvieron que ser actualizados. (Gdiplus.dll) En otros casos esto no era posible (componentes de Windows)

El enfoque manifiesta

Este enfoque resuelve todos los problemas anteriormente. 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 realmente sólo una suposición ...)

  • La mayoría de las cosas son de código abierto, por lo que volver a compilar para una corrección de errores es un problema no para el público objetivo
  • Debido a que sólo hay un 'tiempo de ejecución' (la tiempo de ejecución gcc), el problema con el tiempo de ejecución de los límites compartir/biblioteca no ocurre tan a menudo
  • Muchos componentes utilizan C a nivel de interfaz, donde estos problemas simplemente no se producen si se hace bien
  • la versión de las bibliotecas son en la mayor parte casos incrustados 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 dll-hell.
  • El tiempo de ejecución de GCC se mantuvo muy estable ABI para que estos problemas no pudieran ocurrir.
+0

¿Qué quiere decir que deben compartir el mismo tiempo de ejecución? Supongo que en Linux, todos los objetos compartidos deberían vincularse con el mismo estándar c lib, y no tengo idea de qué pasaría si no lo hicieran. Sin embargo, los archivos Manifest son una "invención" de microsft completa que parece algo innecesaria. Deseo más comentarios – Voltaire

+0

'diferentes tiempos de ejecución' generalmente significan "depuración o liberación". los asignadores de memoria son diferentes, por lo que alloc en a.dll, pasa la memoria a b.dll se bloqueará su aplicación si son diferentes. La solución es siempre asignar y liberar la memoria en el mismo dll. Entonces no tienes problemas. – gbjbaanb

+4

** (¿Por qué Linux no tiene esta mecánica?) ** Linux usa ELF que almacena información de la versión. Si usa la versión 2.2 de una biblioteca, su programa se ejecutará con 2.3 pero simplemente saldrá con 2.1 o 3.0. Todo el mundo usa un tiempo de ejecución de C compatible (glibc/eglibc) con solo cambios compatibles desde el año 2002. La vinculación estática es extremadamente poco común. Los proveedores de bibliotecas son cuidadosos con los cambios, y DLL Hell es tratado por los mantenedores de distribución, no por los administradores de sistemas. El cambio de C++ ABI fue un dolor pero se acabó. Cuando llegue GTK 3.0, se instalará junto con GTK 2.0, por lo que las aplicaciones para ambos se ejecutarán correctamente. –

1

Finalmente llegué a la conclusión de que la única manera de conseguir que esto funcione (además de forma estática que une todo) es que todas las bibliotecas de terceros deben ser compilados usando la misma versión de Visual Studios - es decir, no utilizan dlls precompilados: descarga la fuente, crea una nueva dll y úsala en su lugar.

alternativa (y la solución que tenemos que utilizar en la que trabajo) es que si las bibliotecas de terceros que se tiene que utilizar todos se construyen (o disponible como incorporado) con la misma versión del compilador, puede " solo "usa 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 trabajo no está en Windows, aunque luchamos con archivos 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 todos están 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).

+1

¿Por qué la vinculación estática es segura? Supongamos que la DLL de terceros está enlazando estáticamente a vc8 y su DLL vinculando estáticamente a vc9, y luego intenta delegar un objeto en su DLL que fue creado en la 3ra DLL de patry, ¿qué pasará? –

+0

@lzprgmr: No es, y - ka-blammo! – Spike0xff

+0

La vinculación estática es segura en este caso porque sabe cuáles son las dependencias de todas las bibliotecas que está vinculando. En otras palabras, no intentará vincular dos bibliotecas que dependen de diferentes tiempos de ejecución. –

3

Si un tercero DLL asignará memoria y que necesidad de liberarlo, necesita las mismas bibliotecas de tiempo de ejecución. Si el DLL tiene funciones de asignar y desasignar, puede estar bien.

Si la DLL de terceros usa 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.

+0

¿Por qué los contenedores estándar causan un diseño de memoria diferente? –

+1

Es la interfaz de código utiliza stl, entonces sí! Los contenedores estándar VC6 están codificados de manera diferente a VS2008 std, que son ambos diferentes de STLPort. El código compilado del dll tiene una definición, pero el código que incluye sus encabezados luego compila ese código de manera diferente. Confía en mí, lo he visto. – crashmstr

3

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 precompilados. Exactamente por esta razón.

Si un archivo DLL se envía solo en forma binaria, también debe enviar todos los componentes redistribuibles contra 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.

Cuestiones relacionadas