Sunday, January 08, 2006

Funcionamiento de euna DLL

Funcionamiento de una DLL

Antes de conocer los pormenores de la construcción y uso de una librería dinámica (DLL) en un programa C++, es conveniente tener una perspectiva general del mecanismo que rige el funcionamiento de este tipo especial de librerías. Una librería es simplemente un trozo de código que contiene recursos pre-construidos, recursos que pueden ser utilizados en un programa (ejecutable). Sin embargo, si nos fijamos un poco, vemos que esta definición es un tanto ambigua, y puede encajar en objetos que no son realmente librerías. En realidad, la utilización de recursos pre-construidos por parte de un programa puede realizarse de tres formas que podríamos resumir del siguiente modo:
 Utilización de librerías estáticas. Este es el método tradicional; como hemos señalado, son las clásicas colecciones de ficheros objeto .obj (compilados), que en el momento de la construcción de la aplicación, son incluidos por el "Linker" en el propio ejecutable.
 Utilización de librerías dinámicas. En esta modalidad, los recursos ocupan un fichero independiente del ejecutable (fichero que puede ser utilizado por cualquier aplicación que lo necesite). En algún momento, durante la carga del ejecutable o posteriormente (en run-time), el ejecutable deberá integrar este bloque de código en su propio espacio, de forma que pueda acceder a los recursos contenidos en la librería.
 Utilización de programas externos. Es también un recurso utilizado desde siempre en informática. Un ejecutable puede llamar a ejecución a otro mediante mecanismos de varios tipos. El ejecutable llamado proporciona alguna funcionalidad antes de su terminación, y dispone de su propio espacio de ejecución independiente del programa que lo invocó.
Nota: Respecto a la utilización de programas externos "Versus" librerías dinámicas, hay que tener en cuenta que las plataformas Windows disponen de un espacio de memoria protegida para cada proceso o programa que es iniciado por el Sistema, lo que es oneroso en término de recursos, y origina una sobrecarga similar a la que suponen los procesos involucrados en la invocación de funciones. Por contra, las librerías dinámicas no necesitan su propio espacio, y corren en el espacio del proceso que las invoca, lo que es mucho más rápido y ligero en término de recursos.

Las DLLs son trozos de código capaz de realizar determinadas operaciones, cuya "funcionalidad" puede ser utilizada desde otros ejecutables, y como puede verse, ocupan una posición intermedia (diríamos que una solución de compromiso) entre las dos posiciones extremas. Con las librerías estáticas comparten la característica de que es un trozo de código que acaba siendo incluido en el espacio del ejecutable que las utiliza. A su vez, comparten con los programas externos la característica de que constituyen ficheros distintos y físicamente independientes del ejecutable que los usará.

Antes de seguir adelante, debemos puntualizar un extremo que es importante para comprender el funcionamiento de las DLLs; en realidad, la DLL no es cargada en el espacio de memoria del ejecutable, sino que tiene su propio espacio. Lo que ocurre es que este espacio es accesible desde el ejecutable, y que está "mapeado" en él. Es decir: en el ejecutable existe un cierto "mapa" de como es está distribuida esa zona de memoria, donde están sus objetos "exportables". Existen dos formas de incluir esta información en el ejecutable. Además, el hecho de que la DLL disponga de su propio espacio, tiene una importante ventaja adicional: si dos o más procesos que se están ejecutando simultáneamente en el Sistema necesitan de la misma DLL, esta no necesita ser cargada dos veces en memoria, basta que ambos tengan acceso a ella y cierto conocimiento de su estructura interna.
Nota: Esta utilización del mismo código por varias aplicaciones es posible porque los objetos creados por la DLL no pertenecen a esta, sino al programa usuario.

En realidad lo que caracteriza a una DLL es la forma en que es traída a ejecución. No directamente desde el shell del Sistema como un ejecutable .exe normal, sino desde otro ejecutable (que puede ser incluso otra DLL), de forma parecida a como se invoca una función (una especie de función externa al programa). Por esta razón no disponen de una función main o de un módulo de inicio en el sentido clásico.
Algunos enlazadores para DOS permiten que determinadas porciones del ejecutable se sitúen en ficheros independientes (generalmente con terminación .OVL), los denominados "overlays". Estos overlays son traídos a memoria (cargados) automáticamente según convenga, de forma que salvo contadísimas excepciones su funcionamiento es totalmente transparente para el programador. Por su parte las librerías dinámicas permiten también que partes del ejecutable se alojen en ficheros independientes. Sin embargo su comportamiento es mucho (muchísimo) menos flexible que su contrapartida DOS. Como hemos señalado, su funcionamiento se parece más a la invocación de un programa externo (a todos los efectos) que, una vez ejecutado, devuelve el control al programa inicial.
El resultado es que si desde una DLL necesitamos utilizar una funcionalidad existente en el cuerpo del programa (una función o clase), no podemos accederla a menos que dicha función sea también incluida en una DLL independiente. Naturalmente esto exige que la separación de partes del programa en DLLs se realice después de un estudio minucioso de las funcionalidades que serán utilizadas desde cada módulo.

Utilización
De lo dicho se desprende que la utilización de los recursos contenidos en una DLL requiere dos condiciones:
1. Cargar en memoria el trozo de código contenido en la DLL en un espacio accesible desde el ejecutable que la utiliza.
2. Poder acceder al interior de este trozo de código (conocer su topografía interna) para poder utilizar su funcionalidad.
En cuanto a la primera (a), existen dos formas para que el programa "cargue" la librería
 En el mismo momento de la carga del programa.
 En el momento en que se necesite alguno de sus recursos (en runtime).

En el primer caso, las DLLs requeridas por el ejecutable .EXE son cargadas e inicializadas por el módulo de inicio como cualquier otro módulo del programa es decir, que serán inicializadas antes que comience la ejecución de main. Cuando la aplicación es cargada por el SO, este mira en el fichero .EXE para ver que DLLs se necesitan, y se encarga de cargarlas también.
Este tipo de utilización, denominado de enlazado estático o implícito (librería dinámica elazada estáticamente). Es con diferencia el sistema más utilizado.

En el caso a2, la librería es cargada durante la ejecución cuando la aplicación lo necesita. Esta forma de uso de denomina de enlazado dinámico o explícito (librería dinámica enlazada dinámicamente).
Para realizar la carga, el programador dispone de algunas funciones de la API de Windows que se encargan de realizar la tarea cuando él lo decide (de ahí que se denomine enlazado explícito.

Cualquiera que sea la forma de carga elegida, implícita o explícita, el proceso seguido para buscar el fichero .DLL es siempre el mismo:
 En el directorio que contiene el ejecutable (fichero .EXE)
 El directorio actual de la aplicación [4].
 El directorio de sistema de Windows
 El directorio de Windows
 Los directorios incluidos en la variable de entorno PATH del Sistema.
En caso de una carga implícita, si el Sistema no encuentra el fichero .DLL en ninguno de los sitios anteriores, se muestra un mensaje de error y la aplicación no puede ejecutarse .

En caso que el fichero no se enuentre durante el proceso de carga explícita, mediante ciertas funciones a disposición del programado, entonces es potestad de este decidir que hacer si el Sistema devuelve un error. La figura 2 es un ejemplo tomado de una aplicación real cuando no aparece la .DLL requerida





Fig. 2




Es evidente, que una vez cargado en memoria el código de la DLL, el programa usuario necesita conocer las direcciones de los recursos contenidos en esa zona de memoria para poder acceder a ellos. El procedimiento es distinto según el método de carga utilizado:
En el caso de librería dinámica enlazada estáticamente, se construye una librería tradicional (.LIB) de un tipo especial denominado librería de importación que es enlazada estáticamente con el ejecutable (formando parte de él). La librería de importación no contiene código, en realidad es un índice o tabla de dos columnas. En la primera están los nombres de las funciones exportables de las DLLs utilizadas por el ejecutable; la segunda está vacía, pero cuando las librerías son cargadas en memoria durante el proceso de carga del ejecutable, el programa cargador ya puede conocer las direcciones de estos recursos, y completa la segunda columna de la tabla con las direcciones adecuadas. De esta forma, el ejecutable puede acceder a los recursos de la DLL con solo conocer los nombres adecuados.
En el caso de librería dinámica enlazada dinámicamente (cargada en run-time), una vez realizada esta mediante las funciones correspondientes, la API del Sistema dispone de una función específica GetProcAddress(), que permite para obtener punteros a las funciones de la DLL que deban utilizarse.
Es necesario mencionar que para obtener las direcciones de los recursos dentro del bloque de código de la DLL, tanto el programa cargador como la función GetProcAddress de la API, utilizan a su vez una tabla que acompaña a cualquier librería dinámica, la tabla de entrada ("Entry table").

§3 Recordar que para usar las funciones contenidas en una librería (estática o dinámica) se necesitan tres condiciones:
Un prototipo que permita conocer el nombre del fichero que compone la librería, su localización, parámetros y tipo de retorno de la función de librería que queramos utilizar (esto es lo normal para utilizar cualquier función).
Disponer de los tipos de datos que pasarán como argumentos (también normal para cualquier función).
Poder utilizar la convención de llamada que corresponda a la librería en cuestión. Es decir, que el enlazador C++ utilizado permita usar la convención de llamada adecuada, de forma que estos módulos externos puedan ser llamados a ejecución desde nuestro programa.





________________________________________
[1] Esta característica es importante y debe ser tenida en cuenta. Una DLL es un trozo de código inerte que no tiene vida por sí mismo hasta que es incluido en el espacio de la aplicación que la utiliza. No tiene un proceso propio, y cualquier objeto creado por su código pertenece a la aplicación anfitriona. Si los programas tienen vida, seguramente un biólogo compararía a las DLLs con los virus; son materia inerte que solo puede vivir en el interior de una célula anfitriona.
[2] Recuerde que tanto el compilador Borland C++ como MS Visual C++, permiten especificar las convenciones de llamada más usuales en este tipo de librerías ( 4.4.6a).
[3] Por ejemplo el magnífico enlazador Blinker. http://blinkinc.com.
[4] Generalmente una aplicación "reside" en el directorio donde se encuentra el ejecutable, pero puede cambiar su directorio activo durante la ejecución.
[5] Un perfecto ejemplo es el caso de las DLLs que contienen la API de Windows, que son cargadas una sola vez en memoria y utilizadas por casi cualquier aplicación que se corra en el Sistema.




http://www.zator.com/Cpp/E5_1.htm

No comments: