Disponible la nueva versión "donationware" 7.3 de OrganiZATOR
Descubre un nuevo concepto en el manejo de la información.
La mejor ayuda para sobrevivir en la moderna jungla de datos la tienes aquí.

Curso C++

[Home]  [Inicio]  [Índice]


1.4.4b2b  Usar una DLL

§1  Sinopsis

En contra de lo que ocurre en su construcción, que sigue un proceso único con independencia de como vaya a ser utilizada más tarde ( 1.4.4b2a), existen varias formas de utilizar los recursos de una librería dinámica desde otro ejecutable. En el presente capítulo se exponen los detalles de su utilización, incluyendo ejemplos de construcción de una aplicación que utiliza los recursos de las DLLs de la página anterior.

§2  Generalidades

El acceso desde un ejecutable a un identificador definido en una DLL, como puede ser la invocación de una función o la utilización de una clase de librería, se denomina importar y el objeto "importable". La razón de este apelativo es obvia: el programa debe disponer de un mecanismo (cargador) que permita cargar en memoria la DLL, para a continuación, acceder a dicho recurso que se encuentra fuera del ejecutable (recuerde todos los ejecutables deben ser previamente cargados en memoria). De forma recíproca, el hecho de hacer accesibles desde el exterior determinados recursos de una librería se denomina exportar, y dichos objetos se declaran exportables.

Evidentemente, además de ser cargadas, tales librerías deben enlazarse de alguna forma con los ejecutables que utilicen sus recursos. Veremos que existen dos modalidades para este enlazado .  De otro lado, también resulta evidente que el programa usuario debe tener algún modo de declarar al compilador que determinadas funciones o recursos son "importados", es decir, que están en librerías dinámicas y por tanto, su acceso es un tanto especial.

Nota: existen herramientas que permiten conocer las librerías dinámicas utilizadas por un ejecutable. Por ejemplo, la utilidad tdump del compilador BC++.

§3  Declarar un recurso importado

En el caso de las plataformas BC++, MSVC y GNU MinGW para Windows, la declaración de que un recurso se encuentra en una librería dinámica, y por tanto es "importado", puede hacerse de dos formas, que como se verá, son simétricas de las utilizadas en la construcción ( 1.4.4b2a) para declarar que un recurso es exportable.

a.-  Utilizar el declarador _import

b.-  Utilizar el declarador _declspec(dllimport)

§3.1  Especificador _import

Los recursos "importados" pueden ser declarados con los especificadores _import o __import (son equivalentes).

Sintaxis

Son posibles tres formas, según que el recurso a importar sea una función, una clase o una variable normal:

valor-devuelto _import nombre-funcion (argumentos);  §3.1.1a

class _import nombre-de-clase;                       §3.1.1b

tipo-de-dato _import nombre-de-variable;             §3.1.1c

Ejemplos:

extern "C" _import double MayorValor(double, double);

class _import miClase;

double _import db;

§3.2 Especificador  dllimport

Los recursos importados pueden ser también declarados mediante el especificador  __declspec(dllimport)  ( 4.4.1b).

Sintaxis

Existen dos formas:

__declspec(dllimport) valor-devuelto funcion (argumentos);   §3.2.1a

__declspec(dllimport) tipo-de-dato nombre-de-variable;       §3.2.1b

Ejemplos:

extern "C" __declspec(dllimport) double MayorValor(double, double);

__declspec(dllimport) int x;

§3  Tipos de enlazado

Atendiendo a la forma en que se relaciona la DLL con el ejecutable que deba utilizar sus recursos, existen dos formas de enlazado: estático y dinámico, ya citados en el capítulo anterior ( 1.4.4), pero cuyo significado cuando se refiere a librerías dinámicas tiene algunos matices que repasaremos aquí.


§3.1  DLL enlazada estáticamente.  Significa que la DLL estará siempre presente (cargada) cuando corra el ejecutable. Para ello, algunos compiladores exigen la utilización de una librería estática auxiliar (.LIB), denominada librería de importación [1], que es enlazada estáticamente con el ejecutable. Esta librería auxiliar contiene referencias a la DLL  ( 1.4.4b2c). En otros casos, estas referencias son incluidas por el enlazador en el ejecutable en base a la información extraída directamente de la librería.

En cualquier caso, el fichero ejecutable contiene las referencias que momentáneamente no están resueltas, pero en cuanto comienza la ejecución, la DLL es cargada en memoria y las referencias pueden ser resueltas. Las DLLs enlazadas estáticamente a un ejecutable .EXE son cargadas e inicializadas por el módulo de inicio como cualquier otro módulo del programa (sin que el programador tenga que hacer nada especial al respecto). Es decir, que serán inicializadas antes que comience la ejecución de main.

Nota no confundir lo anterior "librería dinámica enlazada estáticamente" con "librería de enlazado estático". Esto último significa que se ha enlazado una librería estática ( 1.4.4b1), generalmente con extensiones .a o .lib, que contiene recursos (.OBJ) que se han incluido en el ejecutable durante el proceso de enlazado.


§3.2  DLL enlazada dinámicamente significa que su carga se realiza solo en el momento en que es necesitada por el ejecutable. Este tipo de librería es inicializada solo en el momento de la carga, siendo el programador responsable de decidir el momento de carga y eventualmente el de descarga.

En el caso de Windows, para la carga pueden utilizarse dos funciones de la API del sistema: LoadLibrary() y LoadLibraryEx().  La segunda permite establecer algunas particularidades sobre la forma en que se realizará la carga. Para la descarga puede utilizarse FreeLibrary(); tienen el siguiente aspecto:

HINSTANCE LoadLibrary(LPCTSTR);

HINSTANCE LoadLibraryEx(LPCTSTR, HANDLE, DWORD);
BOOL FreeLibrary(HMODULE);

§4  Formas de uso

Según lo anterior, son posibles dos posibilidades de uso de una librería dinámica:

a.-  Librería dinámica (DLL) enlazada estáticamente. En ocasiones, mediante una librería de importación .LIB que referencia la DLL.  Para esto solo hay que enlazar la mencionada librería de importación con el resto de las que componen el ejecutable.

b.-  Librería dinámica (DLL) enlazada dinámicamente.  Esto puede hacerse de dos formas:

b1.-  Enlazando la librería estáticamente mediante la librería de importación (como en el caso anterior), pero utilizando la opción de carga retrasada ( 1.4.4b2e).

b2.-  Utilizando las funciones ya citadas de la API de windows:  LoadLibrary() y LoadLibraryEx().  A continuación, se utiliza GetProcAddress() para obtener punteros individuales a los recursos que deban utilizarse.


Para ilustrar el proceso con ejemplos concretos, construiremos sendos ejecutables que utiliza las librerías dinámicas creadas en la página anterior ( 1.4.4.b2a).  Ambas utilizan sendos fuentes situados en el directorio D:\LearnC\planets.  La primera versión utiliza carga estática, es el fuente mainE.cpp; la otra, correspondiente al fuente mainD.cpp utiliza carga dinámica.  Ambas versiones construirán de dos formas; utilizando el compilador C++ GNU y el de Borland.

§4.1 Utilizar una librería dinámica con carga estática

La aplicación consta de un solo fichero mainE.cpp con el siguiente diseño:

// mainE.cpp

#include <windows.h>
#include <stdlib.h>

#include "dlibs/planets.h"

int main(int argc, char *argv[]) {
   showMercury();
   showVenus();
   showEarth();

   system("PAUSE");
   return EXIT_SUCCESS;

Observe que el fuente es exactamente análogo al utilizado para el uso de librerías estáticas ( 1.4.4b1) y que, aparte de la utilización del fichero de cabecera correspondiente, nada hace presagiar que utilizaremos una librería dinámica. Recuerde que el fichero planets.h, junto con el resto de los utilizados para crear la librería, están en D:\LearnC\planets\dlibs.

§4.1.1  Con GNU Make

Para construir un ejecutable que utilice la referida librería dinámica con carga estática, utilizamos un makefile makefilE.gnu situado en el directorio del fuente anterior:

# MakefilE.gnu Uso de planetsG.dll
# crear aplicación planetsE.exe usando librería dinámica con enlazado estático

LIBS     = -L"C:/DEV-CPP/lib"
CXXFLAGS = -I"C:/DEV-CPP/lib/gcc/mingw32/3.4.2/include" \
           -I"C:/DEV-CPP/include/c++/3.4.2/backward" \
           -I"C:/DEV-CPP/include/c++/3.4.2/mingw32" \
           -I"C:/DEV-CPP/include/c++/3.4.2" -I"C:/DEV-CPP/include"

planetsE.exe: mainE.o
     g++ mainE.o -o "planetsE.exe" $(LIBS) dlibs/planetsG.dll

mainE.o: mainE.cpp
     g++ -c mainE.cpp -o mainE.o $(CXXFLAGS)

La última regla simplemente obtiene el fichero objeto mainE.o, que será enlazado para obtener el ejecutable.  La construcción se realiza en el comando de la primera regla; en ella, a través de compilador, le indicamos al enlazador que incluya nuestra librería junto con el objeto de la aplicación.

La invocación de make para la construcción del ejecutable se realiza de forma análoga a la utilizada para la construcción de la librería:

D:\LearnC\planets>make -f makefilE.gnu

Como resultado se obtiene el objeto mainE.o y el ejecutable deseado, planetsE.exe, cuya ejecución proporciona desde luego las mismas salidas que cuando se utilizó una librería estática.

Primer planeta: Mercurio
Segundo planeta: Venus
Tercer planeta: Tierra
Presione cualquier tecla para continuar . . .


Como se ha señalado, si todo el proceso se ha realizado con las herramientas MinGW, no es necesaria la librería de importación, pero si no fuese este el caso. Por ejemplo, porque la DLL hubiese sido construida con otro compilador, podríamos compilar con la librería de importación planets.a, en lugar de con la DLL (la suponemos en el directorio dlibs). Para esto bastaría modificar ligeramente la primera regla del makefile anterior que quedaría como sigue:

planetsE.exe: mainE.o
     g++ mainE.o -o "planetsE.exe" $(LIBS) dlibs/planets.a

Nota: en la página dedicada a las librerías de importación, se indica cómo obtener una de estas librerías a partir de una DLL existente ( 1.4.4b2c).

§4.1.2  Con Borland 5.5 Make

Para la construcción de nuestro ejecutable, que usará la librería dinámica creada en la página anterior ( 1.4.4b2a) mediante un enlazado implícito (estático), utilizamos un makefile, makefilE.bor, situado en el mismo directorio que el fuente mainE.cpp

# MakefilE.bor para Borland C++ 5.5.1
# crear aplicación planetsE.exe usando librería dinámica con enlazado estático

LIBS     = -LE:\BorlandCPP\Lib

all: planetsE.exe

planetsE.exe: mainE.cpp dlibs\planetsB.lib
    bcc32 -eplanetsE.exe $(LIBS) -WCR -P -Q mainE.cpp dlibs\planetsB.lib

# -P Perform C++ compile regardless of source extension
# -Q Extended compiler error information (Default = OFF)
# -WCR Console aplication

Observe que, aparte de la indicación de incluir la librería planetsB.lib en el ejecutable, en la compilación no existe ninguna mención a la librería dinámica planetsB.dll que se utilizará en runtime (el nombre de esta DLL está incluida en la información aportada por la librería de importación).

La invocación de make es como siempre:

C:\Windows>D:
D:\>cd LearnC\planets\
D:\LearnC\planets\>set PATH=E:\BORLAN~1\BIN;%path%
D:\LearnC\planets\>make -f makefilE.bor

Una vez construido el ejecutable planetsE.exe, su ejecución solo exige de la presencia del propio ejecutable y de la DLL correspondiente planetsB.dll (la librería de importación planetsB.lib no es necesaria). 

§4.2 Utilizar una librería dinámica con carga dinámica

La utilización de una librería dinámica con carga dinámica (explícita) en nuestra aplicación, exige ciertas modificaciones en el fuente respecto al diseño utilizado para la carga estática .  El fichero del nuevo fuente será mainD.cpp situado en el mismo directorio que el anterior: D:\LearnC\planets.

// mainD.cpp
// usar librería dinámica con enlazado dinámico

#include <windows.h>
#include <stdlib.h>
#include <iostream>
#include "dlibs/planets.h"

typedef void __stdcall (* FPTR)();

int main(int argc, char *argv[]) {
   HMODULE dllHandle = LoadLibrary("planetsX.dll");  // cargar librería [2]
   if (!dllHandle) {
      std::cout << "Error en la carga de planets.dll\n";
   }  else {
      FPTR mercurio = (FPTR) GetProcAddress(dllHandle, "showMercury");
      FPTR venus = (FPTR) GetProcAddress(dllHandle, "showVenus");
      FPTR tierra = (FPTR) GetProcAddress(dllHandle, "showEarth");

      if (!mercurio)
         std::cout << "Error al obtener direccion de showMercury()\n";
      else mercurio();
      if (!venus)
         std::cout << "Error al obtener direccion de showVenus()\n";
      else venus ();
      if (!tierra)
         std::cout << "Error al obtener direccion de showEarth()\n";
      else tierra();

      FreeLibrary(dllHandle);    // descargar librería
   }

   system("PAUSE");
   return EXIT_SUCCESS;

Como puede verse, el diseño es muy distinto de utilizado cuando se usa la DLL con carga implícita (estática).  El typedef FPTR, definido como "puntero-a-función que no acepta argumentos y devuelve void", se ha incluido para simplificar la sintaxis de las expresiones con las que obtenemos los punteros mercurio, venus, tierra a las funciones de la librería. Estas expresiones utilizan invocaciones a la función GetProcAddres de la API de Windows. Observe que antes hemos cargado explícitamente la librería, mediante la función LoadLibrary de la API.

La invocación de las funciones de la librería no se realizan directamente mediante sus nombres, sino a través de los punteros obtenidos con GetProcAddres. Finalmente, antes de salir de la aplicación, descargamos la librería mediante FreeLibrary.  Esta descarga puede efectuarse en cualquier momento a partir del instante en que los recursos de la librería no sean necesarios.

§4.2.1 Con GNU Make

Para la construcción de una aplicación que utilice la librería dinámica planets.dll creada en la página anterior  ( 1.4.4b2a) con carga explícita (dinámica) con el compilador GNU gcc, utilizaremos el makefile makefilD.gnu:

# MakefilD.gnu
# crear aplicación planetsD.exe usando librería dinámica con enlazado dinámico

LIBS     = -L"C:/DEV-CPP/lib"
CXXFLAGS = -I"C:/DEV-CPP/lib/gcc/mingw32/3.4.2/include" \
           -I"C:/DEV-CPP/include/c++/3.4.2/backward" \
           -I"C:/DEV-CPP/include/c++/3.4.2/mingw32" \
           -I"C:/DEV-CPP/include/c++/3.4.2" \

           -I"C:/DEV-CPP/include"

planetsD.exe: mainD.o
     g++ mainD.o -o "planetsD.exe" $(LIBS) dlibs/planetsG.dll

mainD.o: mainD.cpp
     g++ -c mainD.cpp -o mainD.o $(CXXFLAGS)

La invocación de make se realiza en la forma acostumbrada:

D:\LearnC\planets>make -f makefilD.gnu

La construcción no exige que exista ningún fichero especial en el directorio D:\LearnC\planets de trabajo; solo que el fichero de cabecera planets.h y la librería planets.dll, se encuentren en el directorio D:\LearnC\planets\dlibs. Sin embargo, para la ejecución del ejecutable planetsD.exe obtenido, es necesario que la librería se encuentre en alguno de los sitios estándar donde busca el cargador del Sistema ( 1.4.4b2).

La compilación anterior también puede hacerse contra la librería de importación planets.a en lugar de contra la librería planets.dll propiamente dicha. Esto puede ser necesario en alguno de los supuestos ya comentados.  Para ello, basta modificar la primera regla del makefile:

planetsD.exe: mainD.o
     g++ mainD.o -o "planetsD.exe" $(LIBS) dlibs/planets.a

§4.2.2 Con Borland 5.5 Make

El fichero makefilD.bor para construir el ejecutable que utiliza enlazado dinámico con la librería es el siguiente:

# MakefilD.bor para Borland C++ 5.5.1
# crear aplicación planetsD.exe usando librería dinámica con enlazado dinámico

LIBS = -LE:\BorlandCPP\Lib

all: planetsD.exe

planetsD.exe: mainD.cpp dlibs/planetsB.dll
    bcc32 -eplanetsD.exe $(LIBS) -WCR -P -Q mainD.cpp

# -P Perform C++ compile regardless of source extension
# -Q Extended compiler error information (Default = OFF)
# -WCR Console aplication (enlazado dinámico)

Una vez invocado el makefile de la forma usual, el ejecutable obtenido planetsD.exe produce la misma salida que en los casos anteriores, pero recuerde que su ejecución exige que la librería planetsB.dll se encuentre en alguno de los sitios estándar donde busca el cargador del Sistema ( 1.4.4b2).

  Inicio.


[1]  Existe un tipo de librerías muy similar a las que aquí comentamos. Son las denominadas librerías de tipos. Un tipo particular de librería de importación que no contiene referencias a una DLL, sino a un control ActiveX; un objeto OLE, o un objeto COM ( 1.4.4b2d).

[2] Sustituir en cada caso, la X por la letra adecuada a la DLL utilizada. Es decir, planetsG.dll para la compilación con GNU y planetsB.dll para la compilación con Borland.