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:
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.
[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