que - C/C++ ¿Cómo funciona la vinculación dinámica en diferentes plataformas?
ejemplos de programas en c++ pdf (9)
¿Cómo funciona el enlace dinámico en general?
En Windows (LoadLibrary), necesita una .dll para llamar en tiempo de ejecución, pero en el momento del enlace, debe proporcionar un archivo .lib correspondiente o el programa no se vinculará ... ¿Qué contiene el archivo .lib? Una descripción de los métodos .dll? ¿No es eso lo que contienen los encabezados?
En relación a * nix, no necesita un archivo lib ... ¿Cómo sabe el compilador que los métodos descritos en el encabezado estarán disponibles en tiempo de ejecución?
Como novato, cuando piensas en uno de los dos esquemas y luego en el otro, ninguno de los dos tiene sentido ...
¿Cómo funciona el enlace dinámico en general?
El archivo de biblioteca de enlaces dinámicos (también conocido como objeto compartido) contiene instrucciones y datos de código de máquina, junto con una tabla de metadatos que indica qué compensaciones en ese código / datos se relacionan con qué "símbolos", el tipo del símbolo (por ejemplo, función vs datos), el número de bytes o palabras en los datos, y algunas otras cosas. Los diferentes sistemas operativos tenderán a tener diferentes formatos de archivos de objetos compartidos, y de hecho el mismo sistema operativo puede admitir varios, pero eso es lo esencial.
Entonces, imagine que la biblioteca compartida es una gran parte de bytes con un índice como este:
SYMBOL ADDRESS TYPE SIZE
my_function 1000 function 2893
my_number 4800 variable 4
En general, no es necesario capturar el tipo exacto de los símbolos en la tabla de metadatos: se espera que las declaraciones en los archivos de encabezado de la biblioteca contengan toda la información faltante. C ++ es un poco especial, en comparación con decir C, ya que la sobrecarga puede significar que hay varias funciones con el mismo nombre, y los espacios de nombres permiten símbolos adicionales que de otra manera serían nombrados de manera ambigua. Por esa razón, la manipulación de nombres se usa normalmente para concatenar alguna representación de el espacio de nombres y los argumentos de la función al nombre de la función, formando algo que puede ser único en el archivo objeto de la biblioteca.
Un programa que quiera usar el objeto compartido generalmente puede hacer una de dos cosas:
hacer que el sistema operativo se cargue tanto a sí mismo como al objeto compartido aproximadamente al mismo tiempo (antes de ejecutar
main()
), con el OS Loader responsable de encontrar los símbolos y examinar los metadatos en la imagen del archivo de programa sobre el uso de esos símbolos, y luego aplicar parches en el símbolo direcciones en la memoria que usa el programa, de modo que el programa puede ejecutarse y funcionar funcionalmente como si supiera sobre las direcciones de los símbolos cuando se compiló por primera vez (pero tal vez un poco más lento)o, explícitamente en su propio código fuente, llame a
dlopen
algún momento después de las ejecucionesmain
, luego usedlsym
o similar para obtener las direcciones de los símbolos, guárdelos en los punteros (función / datos) basados en el conocimiento del programador de los tipos de datos esperados, y luego llámelos explícitamente utilizando los punteros.
En Windows (LoadLibrary), necesita una .dll para llamar en tiempo de ejecución, pero en el momento del enlace, debe proporcionar un archivo .lib correspondiente o el programa no se vinculará ...
Eso no suena bien. Debería ser uno u otro que yo pensaría.
¿Qué contiene el archivo .lib? Una descripción de los métodos .dll? ¿No es eso lo que contienen los encabezados?
Un archivo lib es, en este nivel de descripción, más o menos lo mismo que un archivo de objeto compartido ... la principal diferencia es que el compilador encuentra las direcciones de los símbolos antes de que se envíe y ejecute el programa.
De manera relacionada, en OS X (y supongo que * nix ... dlopen), no necesita un archivo lib ... ¿Cómo sabe el compilador que los métodos descritos en el encabezado estarán disponibles en tiempo de ejecución?
Los compiladores o enlazadores no necesitan tal información. Usted, el programador, necesita manejar la situación que las bibliotecas compartidas que intenta abrir con dlopen()
pueden no existir.
Como ya han dicho otros: lo que se incluye en un archivo .lib
en Windows se incluye directamente en .dynlib
/ .dynlib
en Linux / OS X Pero la pregunta principal es ... ¿por qué? ¿No es mejor la solución * nix? Creo que lo es, pero el .lib
tiene una ventaja. El desarrollador que enlaza con la DLL no necesita tener acceso al archivo DLL en sí.
¿Un escenario como ese ocurre a menudo en el mundo real? ¿Vale la pena el esfuerzo de mantener dos archivos por archivo DLL? No lo sé.
Edit: Ok, chicos, ¡hagamos las cosas aún más confusas! Puede enlazar directamente a una DLL en Windows, usando MinGW. Por lo tanto, todo el problema de la biblioteca de importación no está directamente relacionado con Windows. Tomado del artículo sampleDLL de la wiki de MinGW:
La biblioteca de importación creada por la opción de vinculador "--out-implib" es obligatoria si iff (== si y solo si) la DLL se conectará desde algún compilador de C / C ++ que no sea la cadena de herramientas MinGW. La cadena de herramientas MinGW está perfectamente feliz de vincular directamente con la DLL creada. Se pueden encontrar más detalles en los archivos de información ld.exe que forman parte del paquete binutils (que forma parte de la cadena de herramientas).
El archivo .lib
en Windows no es necesario para cargar una biblioteca dinámica, simplemente ofrece una forma conveniente de hacerlo.
En principio, puede usar LoadLibrary
para cargar la dll y luego usar GetProcAddress
para acceder a las funciones proporcionadas por esa dll. La compilación del programa adjunto no necesita acceder a la dll en ese caso, solo se necesita en tiempo de ejecución (es decir, cuando realmente se ejecuta LoadLibrary
). MSDN tiene un ejemplo de código .
La desventaja aquí es que necesita escribir manualmente el código para cargar las funciones desde la dll. En caso de que usted mismo compilara la dll en primer lugar, este código simplemente duplica el conocimiento de que el compilador podría haber extraído del código fuente de la dll automáticamente (como los nombres y firmas de las funciones exportadas).
Esto es lo que hace el archivo .lib
: contiene las llamadas GetProcAddress
para las funciones exportadas por GetProcAddress
, generadas por el compilador para que no tenga que preocuparse por ello. En términos de Windows, esto se denomina enlace dinámico de tiempo de carga , ya que el archivo del archivo .lib carga automáticamente la Dll cuando se carga el programa que lo contiene (a diferencia del enfoque manual, denominado enlace dinámico en tiempo de ejecución ) .
En la biblioteca compartida, como .dll
.dylib
y .dylib
, hay información sobre el nombre y la dirección del símbolo, como esto:
------------------------------------
| symbol''s name | symbol''s address |
|----------------------------------|
| Foo | 0x12341234 |
| Bar | 0xabcdabcd |
------------------------------------
Y la función de carga, como LoadLibrary
y dlopen
, carga la biblioteca compartida y la pone a disposición para su uso.
GetProcAddress
y dlsym
encuentran la dirección de su símbolo. Por ejemplo:
HMODULE shared_lib = LoadLibrary("asdf.dll");
void *symbol = GetProcAddress("Foo");
// symbol is 0x12341234
En Windows, hay un archivo .lib
para usar .dll
. Cuando se vincula a este archivo .lib
, no necesita llamar a LoadLibrary
y GetProcAddress
, y simplemente usar la función de biblioteca compartida como si fueran funciones "normales". ¿Cómo puede funcionar?
De hecho, el .lib
contiene una información de importación . Es así:
void *Foo; // please put the address of Foo there
void *Bar; // please put the address of Bar there
Cuando el sistema operativo carga su programa (hablando estrictamente, su módulo ), el sistema operativo realiza LoadLibrary
y GetProcAddress
automáticamente.
Y si escribes código como Foo();
, compilador convertirlo en (*Foo)();
automáticamente. Así que puedes usarlos como si fueran funciones "normales".
Linux también requiere vincularse, pero en cambio, contra una biblioteca .Lib, debe vincularse con el enlazador dinámico /lib/ld-linux.so.2
, pero esto generalmente ocurre entre bambalinas cuando se usa GCC (sin embargo, si usa un ensamblador, sí lo hace). Necesito especificarlo manualmente).
Ambos enfoques, ya sea el enfoque Windows .LIB o el enfoque de enlace dinámico de Linux, se consideran en realidad como enlaces estáticos. Sin embargo, existe una diferencia en que parte del trabajo de Windows se realiza en el momento del enlace, aunque todavía tiene trabajo en el momento de la carga (no estoy seguro, pero creo que el archivo .LIB es simplemente para que el vinculador conozca el físico). nombre de la biblioteca, los símbolos, sin embargo, solo se resuelven en el momento de la carga), mientras que en Linux todo lo demás, además de vincularlo al enlazador dinámico, ocurre en el momento de la carga.
En general, la vinculación dinámica se refiere a abrir manualmente el archivo DLL en tiempo de ejecución (como usar LoadLinrary ()), en cuyo caso la carga recae exclusivamente en el programador.
Los sistemas modernos * nix derivan un proceso de enlace dinámico desde el sistema operativo Solaris. Linux, en particular, no necesita un archivo .lib separado porque todas las dependencias externas están contenidas en formato ELF. .interp
sección .interp
del archivo ELF indica que hay símbolos externos dentro de este ejecutable que deben resolverse dinámicamente. Esto viene para la vinculación dinámica .
Hay una manera de manejar la vinculación dinámica en el espacio de usuario. Este método se llama carga dinámica . Esto es cuando está utilizando llamadas al sistema para obtener punteros de función a métodos desde externo * .so.
Se puede encontrar más información en este artículo http://www.ibm.com/developerworks/library/l-dynamic-libraries/ .
Para responder a sus preguntas una por una:
El enlace dinámico difiere parte del proceso de enlace al tiempo de ejecución. Se puede utilizar de dos maneras: implícita y explícitamente. Implícitamente, el enlazador estático insertará información en el ejecutable, lo que hará que la biblioteca cargue y resuelva los símbolos necesarios. De forma explícita, debe llamar a
LoadLibrary
odlopen
manualmente, y luegoGetProcAddress
/dlsym
para cada símbolo que necesite usar. La carga implícita se usa para cosas como la biblioteca del sistema, donde la implementación dependerá de la versión del sistema, pero la interfaz está garantizada. La carga explícita se usa para cosas como complementos, donde la biblioteca que se cargará se determinará en el tiempo de ejecución.El archivo
.lib
solo es necesario para la carga implícita. Contiene la información de que la biblioteca realmente proporciona este símbolo, de modo que el vinculador no se quejará de que el símbolo no está definido, y le indica al administrador en qué biblioteca se encuentran los símbolos, por lo que puede insertar la información necesaria para hacer que esta biblioteca Se cargará automáticamente. Todos los archivos de encabezado le dicen al compilador que los símbolos existirán en algún lugar; el enlazador necesita el.lib
para saber dónde.Bajo Unix, toda la información se extrae de
.so
. Por qué Windows requiere dos archivos separados, en lugar de poner toda la información en un archivo, no lo sé; en realidad, está duplicando la mayor parte de la información, ya que la información necesaria en la.lib
también se necesita en la.dll
. (Quizás problemas de licencia. Puede distribuir su programa con la.dll
, pero nadie puede vincular las bibliotecas a menos que tengan una.lib
).
Lo principal que debe retenerse es que si desea una carga implícita, debe proporcionar al vinculador la información adecuada, ya sea con un archivo .lib
o .so
, para que pueda insertar esa información en el archivo ejecutable. Y que si desea una carga explícita, no puede referirse a ninguno de los símbolos en la biblioteca directamente; tienes que llamar a GetProcAddress
/ dlsym
para obtener sus direcciones (y hacer un casting divertido para usarlas).
Puedes usar un archivo DLL en Windows de dos maneras: o lo vinculas con él, y listo, no hay nada más que hacer. O lo carga dinámicamente durante el tiempo de ejecución.
Si se vincula con él, entonces se utiliza el archivo de biblioteca DLL. La biblioteca de enlaces contiene información que el enlazador utiliza para saber realmente qué DLL cargar y dónde están las funciones de DLL, por lo que puede llamarlos. Cuando su programa está cargado, el sistema operativo también carga la DLL para usted, básicamente, ¿cómo se llama LoadLibrary
para usted?
En otros sistemas operativos (como OS X y Linux) funciona de una manera similar. La diferencia es que en estos sistemas, el enlazador puede ver directamente la biblioteca dinámica (el archivo .dynlib
/ .dynlib
) y descubrir qué se necesita sin una biblioteca estática separada como en Windows.
Para cargar una biblioteca dinámicamente, no necesita vincularse con nada relacionado con la biblioteca que desea cargar.