Site hosted by Angelfire.com: Build your free website today!

Tutorial 17: Librerias De Enlace Dinamico (DLL)

En este tutorial aprenderemos algo sobre DLLs, qué son y cómo crearlas.
Te puedes bajar el ejemplo aquí.
 

Teoría:

Si tu programa se agranda demasiado, encontrarás que los programas que escribes usualmente tienen algunas rutinas en común. Es una pérdida de tiempo reescribirlas cada vez que empiezas un nuevo programa. Volviendo a los viejos tiempos del DOS, los programadores almacenaban estas rutinas que usaban comúnmente en una o varias librerías. Cuando querían usar las funciones, enlazaban la librería al archivo objeto y el enlazador extraía las funciones de la librería y las insertaba en el ejecutable final. Este proceso se llama enlace estático. Las librerías de rutinas del lenguaje C son un buen ejemplo. La parte negativa de este método está en que tienes funciones idénticas en muchos programas que las usan. Tu espacio en disco se llena almacenando copias idénticas de las funciones. Pero para programas de DOS este método es bastante aceptable ya que suele haber un único programa activo en memoria. Así que no hay un desperdicio notable de memoria.

Bajo Windows, la situación es mas crítica porque puedes tener varios programas funcionando al mismo tiempo. La memoria es consumida rápidamente si tu programa es bastante grande. Windows tiene la solución a este tipo de problemas: dynamic link libraries [librerias de enlace dinámico]. Las librerías de enlace dinamico son una especie de recopilación de funciones comunes. Windows no cargará varias copias de la DLL en la memoria de manera que si hay muchos programas que la usen solo corriendo al mismo tiempo, habrá una copia de la DLL en la memoria para todos estos programas. Voy a aclarar este punto un poco. En realidad, los programas que usan la misma DLL tendrán su propia copia de esta DLL. Esto hará parecer que hay varias copias de la DLL en memoria. Pero en realidad, Windows hace que esto sea mágico a través de la paginación de manera que todos los procesos compartan el mismo código de la DLL. Así que en la memoria fisica sólo hay una copia del código de la DLL. Como siempre, cada proceso tendrá su sección única de datos de la DLL.

El programa enlaza la DLL en tiempo de ejecución [at run time], no como en las viejas librerías estáticas. ¿Por qué se la llama librería de enlace dinámico?. Sólo puedes descargar la DLL en el proceso cuando ya no la necesitas. Si el programa es el único que usa la DLL, será descargada de la memoria inmediatamente. Pero si la DLL todavía es usada por algún otro programa, la DLL continúa en memoria hasta que el último programa que la use la descargue.

Como siempre, el enlazador tiene el trabajo más difícil cuando fija las direcciones del archivo ejecutable final. Como no puede "extraer" las funciones e insertarlas en el ejecutable final, de alguna manera tendrá que almacenar suficiente información sobre la DLL y las funciones en el ejecutable final para poder localizar y cargar la DLL correcta en tiempo de ejecución [at run time].

Ahí es donde interviene la librería de importación [import library]. Una librería de importación contiene la información sobre la DLL que representa. El enlazador puede extraer la información que necesita de la librería de importación y mete esos datos en el ejecutable. Cuando el cargador de Windows carga el programa en memoria, ve que el programa enlace a una DLL, así que busca esta DLL, la proyecta en el espacio de direcciones del proceso y fija las direcciones para las llamadas a las funciones en la DLL.

Puedes elegir tú mismo cargar la librería sin dejárselo al cargador de Windows. Este método tiene sus pro y sus contras:

Viendo las ventajas/desventajas de la llamada a LoadLibrary, ahora iremos detallando como crear una DLL.

El siguiente codigo es el esqueleto de una DLL.

;--------------------------------------------------------------------------------------
;                           DLLSkeleton.asm
;--------------------------------------------------------------------------------------
.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib

.data
.code
DllEntry proc hInstDLL:HINSTANCE, reason:DWORD, reserved1:DWORD
        mov  eax,TRUE
        ret
DllEntry Endp
;--------------------------------------------------------------------------------------------------------------------------------------------------
;                                                Esta es una función ficticia.
; No hace nada. La he puesto esto aquí para mostrar donde puedes insertar las funciones
; dentro
de una DLL.
;---------------------------------------------------------------------------------------------------------------------------------------------------
TestFunction proc
    ret
TestFunction endp

End DllEntry

;-------------------------------------------------------------------------------------
;                              DLLSkeleton.def
;-------------------------------------------------------------------------------------
LIBRARY   DLLSkeleton
EXPORTS   TestFunction
 

El programa anterior es el esqueleto de una DLL. Todas las DLL deben tener una función de punto de entrada. Windows llamará a la función del punto de entrada en caso que:

DllEntry proc hInstDLL:HINSTANCE, reason:DWORD, reserved1:DWORD
        mov  eax,TRUE
        ret
DllEntry Endp

Puedes nombrar la función del punto de entrada como quieras pero tendrás que terminarla END <Nombre de la función de entrada>. Esta función tiene tres parametros, sólo los dos primeros de estos son importantes.
hInstDLL es el manejador del módulo (module handle) de la DLL. Este no es el mismo que el manejador de la instancia (instance handle) del proceso. Tendrás que guardar este valor si necesitas usarlo mas tarde. No podrás obtenerlo otra vez fácilmente.
reason puede ser uno de los cuatro valores:

Devuelves TRUE en eax si quieres que la DLL siga funcionando. Si devuelves FALSE, la DLL no será cargada. Por ejemplo, si tu codigo de inicialización debe reservar algo de memoria y no puede hacerlo satisfactoriamente, la función del punto de entrada devolverá FALSE para indicar que la DLL no puede funcionar.

Puedes poner tus funciones en la DLL detrás o delante de la función de punto de entrada. Pero si quieres que puedan ser llamadas por otros programas debes poner sus nombres en la lista de exportaciones en el archivo de módulo de definición (.def).

LA DLL necesita un archivo de módulo de definición en su entorno de desarrollo. Vamos a echarle un vistazo a esto.

LIBRARY   DLLSkeleton
EXPORTS   TestFunction

Normalmente deberás tener la primera linea. La declaración LIBRARY define el nombre interno del módulo de la DLL. Tendrás que proporcionarlo con el nombre de archivo de la DLL. La definición EXPORTS le dice al enlazador que funciones de la DLL son exportadas, es decir, pueden ser llamadas desde otros programas. En el ejemplo, queremos que otros módulos sean capaces de llamar a TestFunction, así que ponemos el nombre en la definición EXPORTS.

Cualquier otro cambio es en las opciones del enlazador. Deberás poner  /DLL opciones y /DEF:<el nombre de archivo de tu def> en las opciones de tu enlazador algo así :

link /DLL /SUBSYSTEM:WINDOWS /DEF:DLLSkeleton.def /LIBPATH:c:\masm32\lib DLLSkeleton.obj

Las opciones del ensamblador son las mismas, esto es /c /coff /Cp. Así que después de enlazar el archivo objeto, obtendrás un .dll y un .lib. El .lib es la librería importada que puedes usar para enlazar a otros programas que usen las funciones de la DLL.

Seguido mostraré como usar LoadLibrary para cargar la DLL.

;---------------------------------------------------------------------------------------------
;                                      UseDLL.asm
;----------------------------------------------------------------------------------------------
.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\user32.lib

.data
LibName db "DLLSkeleton.dll",0
FunctionName db "TestHello",0
DllNotFound db "Cannot load library",0
AppName db "Load Library",0
FunctionNotFound db "TestHello function not found",0

.data?
hLib dd ?                                         ; el manejador (handle) de la librería (DLL)
TestHelloAddr dd ?                        ; la dirección de la función TestHello

.code
start:
        invoke LoadLibrary,addr LibName
;----------------------------------------------------------------------------------------------------------------------------------------
; Llama a LoadLibrary con el nombre de la DLL deseada. Si la llamada es correcta
; devolverá el manejador (handle) de la librería (DLL). Si no devolverá NULL
; Puedes pasar el manejador (handle) a GetProcAddress u otra función que requiera
; el manejador (handle) de la librería como parametro.
;-----------------------------------------------------------------------------------------------------------------------------------------
        .if eax==NULL
                invoke MessageBox,NULL,addr DllNotFound,addr AppName,MB_OK
        .else
                mov hLib,eax
                invoke GetProcAddress,hLib,addr FunctionName
;--------------------------------------------------------------------------------------------------------------------------------------------------
; Cuando obtienes el manejador (handle) de la librería, lo pasas a GetProcAddress con la 
; dirección del nombre de la función en la DLL que quieres llamar. Esto devuelve la dirección
; de la función si es correcto. De otra manera devuelve NULL
; Las direcciones de las funciones no cambian a menos que descarges y recarges la librería .
; Así que puedes ponerlas en variables globales para usos futuros.
;---------------------------------------------------------------------------------------------------------------------------------------------------
                .if eax==NULL
                        invoke MessageBox,NULL,addr FunctionNotFound,addr AppName,MB_OK
                .else
                        mov TestHelloAddr,eax
                        call [TestHelloAddr]
;------------------------------------------------------------------------------------------------------------------------------------------------------
; Lo siguiente, puedes llamar la función con un simple call con la variable conteniendo
; la dirección de la función como operando.
;------------------------------------------------------------------------------------------------------------------------------------------------------
                .endif
                invoke FreeLibrary,hLib
;-------------------------------------------------------------------------------------------------------------
; Cuando no necesitas mas la librería descargarla con FreeLibrary.
;-------------------------------------------------------------------------------------------------------------
        .endif
        invoke ExitProcess,NULL
end start

Puedes ver que usando LoadLibrary es un poco mas problemático pero mas flexible.


Indice

Siguiente

[Iczelion's Win32 Assembly HomePage]

n u M I T_o r's   Programming Page

Este tutorial, original de Iczelion, ha sido traducido por JoTaKe  y revisado por  n u M I T_o r