¿Cómo funciona la biblioteca de importación? Detalles?

88

Sé que esto puede parecer bastante básico para los geeks. Pero quiero dejarlo muy claro.

Cuando quiero usar una DLL de Win32, generalmente solo llamo a las API como LoadLibrary () y GetProcAdderss (). Pero recientemente, estoy desarrollando con DirectX9, y necesito agregar archivos d3d9.lib , d3dx9.lib , etc.

He escuchado lo suficiente que LIB es para enlaces estáticos y DLL es para enlaces dinámicos.

Entonces, mi entendimiento actual es que LIB contiene la implementación de los métodos y está vinculado estáticamente en el momento del enlace como parte del archivo EXE final. Mientras que DLL se carga dinámicamente en tiempo de ejecución y no forma parte del archivo EXE final.

Pero a veces, hay algunos archivos LIB que vienen con los archivos DLL, así que:

  • ¿Para qué sirven estos archivos LIB?
  • ¿Cómo logran lo que están destinados?
  • ¿Existe alguna herramienta que pueda permitirme inspeccionar el interior de estos archivos LIB?

Actualización 1

Después de consultar wikipedia, recuerdo que estos archivos LIB se llaman biblioteca de importación . Pero me pregunto cómo funciona con mi aplicación principal y las DLL que se cargan dinámicamente.

Actualización 2

Tal como dijo RBerteig, hay un código auxiliar en los archivos LIB que nacen con las DLL. Entonces, la secuencia de llamada debería ser así:

Mi aplicación principal -> stub en LIB -> DLL de destino real

Entonces, ¿qué información debería estar contenida en estos LIB? Podría pensar en lo siguiente:

  • El archivo LIB debe contener la ruta completa de la DLL correspondiente; Por tanto, el tiempo de ejecución podría cargar la DLL.
  • La dirección relativa (¿o el desplazamiento del archivo?) Del punto de entrada de cada método de exportación DLL debe estar codificado en el stub; Por lo tanto, se podrían realizar llamadas correctas de saltos / métodos.

¿Estoy en lo cierto en esto? Hay algo mas

Por cierto: ¿Existe alguna herramienta que pueda inspeccionar una biblioteca de importación? Si puedo verlo, no habrá más dudas.

smwikipedia
fuente
4
Veo que nadie abordó la última parte de su pregunta, que se refiere a las herramientas que pueden inspeccionar una biblioteca de importación. Con Visual C ++, hay al menos dos formas de hacerlo: lib /list xxx.liby link /dump /linkermember xxx.lib. Consulte esta pregunta de Stack Overflow .
Alan
Además, dumpbin -headers xxx.libproporciona información más detallada, en comparación con las utilidades liby link.
m_katsifarakis

Respuestas:

102

La vinculación a un archivo DLL puede ocurrir implícitamente durante la compilación del vínculo o explícitamente durante la ejecución. De cualquier manera, la DLL termina cargada en el espacio de memoria del proceso y todos sus puntos de entrada exportados están disponibles para la aplicación.

Si se usa explícitamente en tiempo de ejecución, usa LoadLibrary()y GetProcAddress()para cargar manualmente la DLL y obtener punteros a las funciones que necesita llamar.

Si se vincula implícitamente cuando se crea el programa, los códigos auxiliares para cada exportación de DLL utilizada por el programa se vinculan al programa desde una biblioteca de importación, y esos códigos auxiliares se actualizan a medida que se cargan el EXE y el DLL cuando se inicia el proceso. (Sí, lo he simplificado más que un poco aquí ...)

Esos stubs deben provenir de algún lugar, y en la cadena de herramientas de Microsoft provienen de una forma especial de archivo .LIB llamada biblioteca de importación . El .LIB requerido generalmente se crea al mismo tiempo que la DLL y contiene un código auxiliar para cada función exportada desde la DLL.

Confusamente, una versión estática de la misma biblioteca también se enviaría como un archivo .LIB. No hay una forma trivial de distinguirlos, excepto que las LIB que son bibliotecas de importación para DLL normalmente serán más pequeñas (a menudo mucho más pequeñas) que la LIB estática correspondiente.

Si usa la cadena de herramientas de GCC, por cierto, no necesita bibliotecas de importación que coincidan con sus DLL. La versión del enlazador Gnu portado a Windows comprende las DLL directamente y puede sintetizar la mayoría de los stubs necesarios sobre la marcha.

Actualizar

Si simplemente no puede resistirse a saber dónde están realmente todas las tuercas y tornillos y qué está pasando realmente, siempre hay algo en MSDN para ayudarlo. El artículo de Matt Pietrek Una mirada en profundidad al formato de archivo ejecutable portátil Win32 es una descripción general muy completa del formato del archivo EXE y cómo se carga y ejecuta. Incluso se ha actualizado para cubrir .NET y más desde que apareció originalmente en MSDN Magazine ca. 2002.

Además, puede resultar útil saber cómo saber exactamente qué DLL utiliza un programa. La herramienta para eso es Dependency Walker, también conocido como depende.exe. Se incluye una versión con Visual Studio, pero la última versión está disponible de su autor en http://www.dependencywalker.com/ . Puede identificar todas las DLL que se especificaron en el momento del enlace (carga anticipada y carga retardada) y también puede ejecutar el programa y observar cualquier DLL adicional que cargue en tiempo de ejecución.

Actualización 2

He vuelto a redactar parte del texto anterior para aclararlo al volver a leerlo y para usar los términos del arte de vinculación implícita y explícita para mantener la coherencia con MSDN.

Entonces, tenemos tres formas en que las funciones de la biblioteca pueden estar disponibles para ser utilizadas por un programa. La pregunta de seguimiento obvia es entonces: "¿Cómo elijo cuál camino?"

La vinculación estática es la forma en que se vincula la mayor parte del programa en sí. Todos sus archivos de objeto se enumeran y el vinculador los recopila en el archivo EXE. En el camino, el enlazador se encarga de tareas menores como arreglar referencias a símbolos globales para que sus módulos puedan llamar a las funciones de los demás. Las bibliotecas también se pueden vincular estáticamente. Un bibliotecario recopila los archivos de objeto que componen la biblioteca en un archivo .LIB en el que el vinculador busca módulos que contengan los símbolos necesarios. Un efecto de la vinculación estática es que sólo los módulos de la biblioteca que utiliza el programa están vinculados a ella; otros módulos se ignoran. Por ejemplo, la biblioteca matemática tradicional de C incluye muchas funciones de trigonometría. Pero si lo vincula y usacos(), no termina con una copia del código para sin()oa tan()menos que también llame a esas funciones. Para bibliotecas grandes con un rico conjunto de características, esta inclusión selectiva de módulos es importante. En muchas plataformas, como los sistemas integrados, el tamaño total del código disponible para su uso en la biblioteca puede ser grande en comparación con el espacio disponible para almacenar un ejecutable en el dispositivo. Sin la inclusión selectiva, sería más difícil gestionar los detalles de los programas de creación para esas plataformas.

Sin embargo, tener una copia de la misma biblioteca en cada programa en ejecución crea una carga para un sistema que normalmente ejecuta muchos procesos. Con el tipo correcto de sistema de memoria virtual, las páginas de memoria que tienen un contenido idéntico solo necesitan existir una vez en el sistema, pero pueden ser utilizadas por muchos procesos. Esto crea un beneficio para aumentar las posibilidades de que las páginas que contienen código sean idénticas a alguna página en tantos otros procesos en ejecución como sea posible. Pero, si los programas se vinculan estáticamente a la biblioteca en tiempo de ejecución, entonces cada uno tiene una combinación diferente de funciones, cada una de las cuales se presenta en el mapa de memoria de los procesos en diferentes ubicaciones, y no hay muchas páginas de códigos compartibles a menos que sea un programa que por sí solo es ejecutar en más de proceso. Así que la idea de una DLL obtuvo otra gran ventaja.

Una DLL para una biblioteca contiene todas sus funciones, listas para ser utilizadas por cualquier programa cliente. Si muchos programas cargan esa DLL, todos pueden compartir sus páginas de códigos. Todo el mundo gana. (Bueno, hasta que actualice una DLL con una nueva versión, pero eso no es parte de esta historia. Google DLL Hell para ese lado de la historia).

Por lo tanto, la primera gran elección que debe tomar al planificar un nuevo proyecto es entre la vinculación dinámica y estática. Con el enlace estático, tiene menos archivos para instalar y es inmune a que terceros actualicen una DLL que usa. Sin embargo, su programa es más grande y no es tan buen ciudadano del ecosistema de Windows. Con el enlace dinámico, tiene más archivos para instalar, es posible que tenga problemas con un tercero que actualice una DLL que usa, pero generalmente está siendo más amigable con otros procesos en el sistema.

Una gran ventaja de una DLL es que se puede cargar y utilizar sin volver a compilar o incluso volver a vincular el programa principal. Esto puede permitir que un proveedor de bibliotecas de terceros (piense en Microsoft y el tiempo de ejecución de C, por ejemplo) corrija un error en su biblioteca y la distribuya. Una vez que un usuario final instala la DLL actualizada, inmediatamente obtiene el beneficio de esa corrección de errores en todos los programas que usan esa DLL. (A menos que rompa cosas. Consulte DLL Hell.)

La otra ventaja proviene de la distinción entre carga implícita y explícita. Si realiza un esfuerzo adicional de carga explícita, es posible que la DLL ni siquiera existiera cuando se escribió y publicó el programa. Esto permite mecanismos de extensión que pueden descubrir y cargar complementos, por ejemplo.

RBerteig
fuente
3
Eliminando mi publicación y votando esto, porque explicas las cosas de una manera mucho mejor que yo;) Buena respuesta.
ere el
2
@RBerteig: Gracias por tu gran respuesta. Sólo un poco de corrección, según aquí ( msdn.microsoft.com/en-us/library/9yd93633.aspx ), hay 2 tipos de dinámicas que vinculan a un archivo DLL, los tiempos de carga vinculación implícita y en tiempo de ejecución del enlace explícito . Sin vinculación en tiempo de compilación . Ahora me pregunto cuál es la diferencia entre el enlace estático tradicional (enlace a un archivo * .lib que contiene la implementación completa) y el enlace dinámico de tiempo de carga a una DLL (a través de una biblioteca de importación).
smwikipedia
1
Continuar: ¿Cuáles son los pros y los contras de la vinculación estática y la vinculación dinámica en tiempo de carga ? Parece que estos 2 enfoques cargan todos los archivos necesarios en el espacio de direcciones al comienzo de un proceso. ¿Por qué necesitamos 2 de ellos? Gracias.
smwikipedia
1
tal vez puedas usar una herramienta como "objdump" para mirar dentro de un archivo .lib y averiguar si es una biblioteca de importación o una verdadera biblioteca estática. en Linux, cuando se realiza una compilación cruzada con un destino de Windows, es posible ejecutar 'ar' o 'nm' en los archivos .a (versión mingw de los archivos .lib) y tenga en cuenta que las librerías de importación tienen nombres de archivo .o genéricos y sin código (solo una instrucción 'jmp'), mientras que las librerías estáticas tienen muchas funciones y código en su interior.
don bright
1
Pequeña corrección: también puede vincular implícitamente en tiempo de ejecución. El soporte del vinculador para DLL con carga diferida explica esto en detalle. Esto es útil si desea cambiar dinámicamente la ruta de búsqueda de DLL, o manejar con gracia el error de resolución de importación (para admitir nuevas funciones del sistema operativo, pero aún ejecutar en versiones anteriores, por ejemplo).
IInspectable
5

Estos archivos de biblioteca de importación .LIB se utilizan en la siguiente propiedad del proyecto Linker->Input->Additional Dependencies, cuando se crean varios dll que necesitan información adicional en el momento del enlace, que se proporciona mediante los archivos .LIB de la biblioteca de importación. En el siguiente ejemplo, para no obtener errores del vinculador, necesito hacer referencia a los archivos DLL A, B, C y D a través de sus archivos lib. (tenga en cuenta que para que el vinculador encuentre estos archivos, es posible que deba incluir su ruta de implementación; de lo Linker->General->Additional Library Directoriescontrario, obtendrá un error de compilación por no poder encontrar ninguno de los archivos lib proporcionados).

Vinculador-> Entrada-> Dependencias adicionales

Si su solución está compilando todas las bibliotecas dinámicas, es posible que haya podido evitar esta especificación de dependencia explícita confiando en cambio en los indicadores de referencia expuestos en el Common Properties->Framework and Referencescuadro de diálogo. Estas banderas parecen hacer el enlace automáticamente en su nombre utilizando los archivos * .lib. Marco y referencias

Sin embargo, esto es como dice una propiedad común , que no es específica de la configuración o plataforma. Si necesita admitir un escenario de compilación mixto, como en nuestra aplicación, teníamos una configuración de compilación para representar una compilación estática y una configuración especial que compilaba una compilación restringida de un subconjunto de ensamblados que se implementaron como bibliotecas dinámicas. Había usado las banderas Use Library Dependency Inputs y Link Library Dependenciesconfiguradas como verdaderas en varios casos para hacer que las cosas se compilaran y luego me di cuenta de simplificar las cosas, pero al introducir mi código en las compilaciones estáticas, presenté una tonelada de advertencias del enlazador y la compilación fue increíblemente lenta para las compilaciones estáticas. Terminé presentando un montón de este tipo de advertencias ...

warning LNK4006: "bool __cdecl XXX::YYY() already defined in CoreLibrary.lib(JSource.obj); second definition ignored  D.lib(JSource.obj)

Y terminé usando la especificación manual de Additional Dependenciespara satisfacer al vinculador de las compilaciones dinámicas mientras mantenía contentos a los constructores estáticos al no usar una propiedad común que los ralentizara. Cuando implemento la compilación del subconjunto dinámico, solo implemento los archivos dll, ya que estos archivos lib solo se usan en el momento del enlace, no en el tiempo de ejecución.

jxramos
fuente
3

Hay tres tipos de bibliotecas: estáticas, compartidas y cargadas dinámicamente.

Las bibliotecas estáticas están vinculadas con el código en la fase de vinculación, por lo que en realidad están en el ejecutable, a diferencia de la biblioteca compartida, que solo tiene stubs (símbolos) para buscar en el archivo de biblioteca compartida, que se carga en tiempo de ejecución antes de la se llama a la función principal.

Las que se cargan dinámicamente son muy parecidas a las bibliotecas compartidas, excepto que se cargan cuando y si surge la necesidad por el código que ha escrito.

Zoltán Szőcs
fuente
@Gracias zacsek. Pero no estoy seguro de su afirmación sobre la biblioteca compartida.
smwikipedia
@smwikipedia: Linux los tiene, yo los uso, así que definitivamente existen. Lea también: en.wikipedia.org/wiki/Library_(computing)
Zoltán Szőcs
3
Es una diferencia sutil. Las bibliotecas compartidas y dinámicas son archivos DLL. La diferencia es cuando se cargan. El sistema operativo carga las bibliotecas compartidas junto con el archivo EXE. Las bibliotecas dinámicas se cargan mediante la llamada de código LoadLibrary()y las API relacionadas.
RBerteig
Leí en [1] que DLL es la implementación de Microsoft del concepto de biblioteca compartida. [1]: en.wikipedia.org/wiki/Dynamic-link_library#Import_libraries
smwikipedia
No estoy de acuerdo en que sea una diferencia sutil, desde la vista de programación, hace una gran diferencia si la biblioteca compartida está cargada dinámicamente o no (si está cargada dinámicamente, entonces tiene que agregar código repetitivo para acceder a las funciones).
Zoltán Szőcs