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.4b1  Librerías estáticas

§1  Sinopsis

Como se indicó en la introducción ( 1.4.4b), las librerías estáticas, denominadas también librerías-objeto (en relación a que sus componentes o módulos incluyen ficheros de este tipo), son colecciones de ficheros-objeto agrupados en un solo fichero, generalmente de extensión .lib o .a., acompañados de ficheros de cabecera, generalmente .h, que contienen las declaraciones de los objetos definidos en la librería.  Posteriormente, durante la fase de enlazado, el linker incluye en el ejecutable los módulos correspondientes a las funciones y clases de librería que hayan sido utilizadas en la aplicación. Como resultado, tales módulos entran a formar parte del ejecutable, de forma exactamente igual que cualquier otra función o clase que hubiese sido escrita en el cuerpo de la aplicación.

Para el programador C/C++, el manejo de librerías estáticas puede tener una doble vertiente: su utilización y eventualmente la creación de alguna de ellas.  Ambos son procesos distintos e independientes.  El primero es prácticamente inevitable en C++, dado que como señalábamos, la Librería Estándar C++ ( 5) está constituida en su totalidad por librerías estáticas y la mera inclusión de una sentencia del tipo

cout << "Hola mundo" << endl;

supone la utilización de una de ellas.

En lo que respecta a la creación, aunque no es usual en el caso de ejecutables triviales o pequeños, resulta en cambio un recurso habitual cuando el programador constata que algunos trozos de su código (funciones y clases), puedan ser utilizados por distintas aplicaciones.  En consecuencia, es frecuente que tanto los programadores individuales como los departamentos de software de las empresas, construyan sus propios juegos de herramientas en forma de librerías estáticas y de otro tipo, que entran así a formar parte del arsenal de recursos de desarrollo. Esto sin contar con que en algunas compañías dedicadas a la fabricación de software, las librerías constituyen justamente el "producto final" de la empresa.

En el presente capítulo abordaremos ambos procesos. En primer lugar el creación de una librería estática. A continuación su uso en una aplicación. La explicación la haremos sobre un ejemplo muy sencillo pero que muestra claramente el proceso a seguir en todos los casos.

El ejemplo se muestra en dos versiones. Suponemos que ambas se ejecutan sobre Windows32. La primera utilizando el compilador GNU C++ de MinGW, tal como aparece en en el entorno de desarrollo Dev-C++ (ver recuadro en 1.4.0a1).  De esta forma, los ejemplos pueden ser reproducidos en Linux sin modificación.  La segunda tal como se efectuaría en Borland C++ 5.5.

§2  Creación de una Librería estática

Suponemos que queremos utilizar en nuestras aplicaciones ciertas funciones que deseamos estén incluidas en una librería. Además dispondremos de un fichero de cabecera que contenga la interfaz necesaria para la utilización de la mentada librería.  Las funciones se encuentran en tres ficheros fuente y uno de cabecera: planet1.cpp; planet2.cpp; planet3.cpp y planets.h.  Los ficheros están en el directorio D:\LearnC\planets\libs, y responden al siguiente diseño:

// planet1.cpp
#include <iostream>

void showMercury () {
   std::cout << "Primer planeta: Mercurio" << std::endl;
}

 

// planet2.cpp
#include <iostream>

void showVenus () {
   std::cout << "Segundo planeta: Venus" << std::endl;
}

 

// planet3.cpp
#include <iostream>

void showEarth () {
   std::cout << "Tercer planeta: Tierra" << std::endl;
}

 

// planets.h

#ifndef _PLANETS
  #define _PLANETS

  void showMercury();
  void showVenus();
  void showEarth();

#endif // _PLANETS

De la inspección de los ficheros es inmediato deducir que se trata de código C++ absolutamente normal, en el sentido de que es indistinguible del de cualquier otro módulo que formara parte de una aplicación C++.  La razón es la ya señalada, de que estos módulos entrarán finalmente a formar parte del ejecutable que los usa, y que los detalles del proceso dependen exclusivamente del compilador.  La consecuencia es que no es necesario tomar precauciones especiales respecto a asuntos tales como el planchado de nombres, o la convención de llamada de las funciones ( 4.4.6a) en los objetos de la librería.

Nota: como tendremos ocasión de ver ( 1.4.4b2a) , este no es el caso del diseño de módulos de librerías dinámicas, ya que los detalles del enlazado dinámico dependen del SO, y es frecuente que librerías DLLs escritas en un lenguaje, sean utilizadas por aplicaciones escritas en otro. Como resultado, tanto las convenciones de llamada de las funciones como el planchado de nombres, pueden ser diferentes entre los diversos módulos de la aplicación, por lo que el mantenimiento de la compatibilidad exige un acuerdo en la convención a utilizar.  En lo que respecta a las aplicaciones para las plataformas Windows, la convención es no utilizar planchado para los nombres de funciones exportables (que será utilizados por otros módulos de la aplicación), y la convención de llamada __pascal para las funciones que serán invocadas por el Sistema ("callbacks"). 

§2.1  Construir una librería estática con GNU Make

Para la construcción de la librería utilizamos el siguiente makefile ( 1.4.0a1) al que denominamos makefile.gnu [1].

# Makefile.GNU para GNU make

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" 

all: planets.a

planets.a: planet1.o planet2.o planet3.o
    ar r planets.a planet1.o planet2.o planet3.o
    ranlib planets.a

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

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

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

Recordemos que la macro CXXFLAGS señala los directorios donde el compilador g++.exe debe buscar los ficheros de cabecera. Las tres últimas reglas sirven para obtener los ficheros-objeto que serán posteriormente utilizados para construir la librería. g++.exe es el compilador C++ GNU en su versión para Windows. Por su parte, ar es la utilidad GNU que agrupa los tres módulos objeto en un solo fichero planets.a, que es la librería.  A continuación, la utilidad ranlib incluye en el anterior un índice o diccionario con los símbolos definidos en los ficheros que componen la librería ( 1.4.4b).

Para invocar make utilizamos el procedimiento estándar para nuestro entorno. Es decir, nos situamos en el directorio correspondiente, incluimos el directorio con los binarios de Dev-Cpp en nuestra variable de entorno PATH [2], e invocamos la utilidad de forma que utilice nuestro fichero:

C:\Windows>D:
D:\>cd LearnC\planets\libs
D:\LearnC\planets\libs>set PATH=C:\Dev-Cpp\bin;%path%
D:\LearnC\planets\libs>make -f makefile.gnu

Después de unos instantes tenemos en nuestro directorio D:\LearnC\planets\libs cuatro nuevos ficheros: la librería planets.a y los ficheros-objeto planet1.o, planet2.o y planet3.o necesarios para su construcción. Estos últimos pueden ser borrados, ya que en adelante, solo son necesarios la librería propiamente dicha y el fichero de cabecera planets.h.  Observe que los ficheros resultantes tienen las terminaciones usuales de los entornos Linux/Unix.

§2.2  Construir la librería con Borland C++ 5.5 Make

La operatoria para construir una librería con el Make de Borland es análoga a la del caso anterior, aunque aquí utilizamos un makefile makefile.bor, adecuado a las particularidades de dicho compilador y de nuestro entorno:

# Makefile.bor para Make de Borland C++ 5.5.1

CXXFLAGS = -IE:\BorlandCPP\Include
LIBS     = -LE:\BorlandCPP\Lib

all: planets.lib

planets.lib: planet1.obj planet2.obj planet3.obj 
    tlib /C planets -+planet1.obj -+planet2.obj -+planet3.obj

planet1.obj: planet1.cpp
    bcc32 $(CXXFLAGS) $(LIBS) -c -P -Q planet1.cpp

planet2.obj: planet2.cpp
    bcc32 $(CXXFLAGS) $(LIBS) -c -P -Q planet2.cpp

planet3.obj: planet3.cpp
    bcc32 $(CXXFLAGS) $(LIBS) -c -P -Q planet3.cpp

# -c Compile to .OBJ, no link
# -P Perform C++ compile regardless of source extension
# -Q Extended compiler error information (Default = OFF)

La única particularidad digna de mención es que la utilidad tlib desempeña las funciones que en GNU están encomendada a las utilidades ar y ranlib [4]. Los signos -+ delante de los nombres de los objetos tienen por misión que, si el fichero .LIB existiera previamente, se descargue la versión previa del módulo correspondiente antes de incluir la nueva.  En caso de que el módulo no exista previamente, se obtiene un mensaje de aviso.

La invocación es similar a la de GNU Make, aunque en este caso, la variable de entorno PATH corresponde a la situación de los binarios de Borland. 

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

Ahora los ficheros resultantes tienen las terminaciones habituales de los entornos Windows32: planets.LIB para la librería, y planet1.obj, planet2.obj y planet3.obj para los ficheros-objeto.  

§3  Usar una Librería Estática

Para completar la descripción del proceso, incluiremos sendos ejemplos de uso de la librería anterior en un ejecutable C++, representado por un fuente en el directorio D:\LearnC\planets, al que denominaremos main.cpp:

// main.cpp
#include <cstdlib>        // ver nota [3]
#include "libs/planets.h"

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

   system("PAUSE");
   return EXIT_SUCCESS;
}

Se trata de una aplicación de consola (no gráfica) muy sencilla, que utiliza las tres funciones de nuestra librería.  Para ello, la primera medida es incluir el fichero de cabecera correspondiente (planets.h) junto con el resto de includes.  A continuación solo queda construir la aplicación siguiendo los procedimientos estándar de la plataforma utilizada. Como se verá en los ejemplos que siguen, la única precaución especial es indicar al compilador que debe incluir la librería correspondiente.

§3.1 Construir la aplicación con GNU Make

Para construir la aplicación utilizamos un makefile, al que denominamos makefile2.gnu, situado en el mismo directorio que el fuente main.cpp:

# Makefile2.gnu  Construir la aplicación planets.exe (GNU g++)

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"

planets.exe: main.o
    g++.exe main.o -o "planets.exe" $(LIBS) libs/planets.a

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

El proceso no tiene nada especial; después de obtenido el objeto main.o en la última línea, se ordena al enlazador (invocado a través de g++.exe) que lo enlace para producir el ejecutable.  El único punto a destacar respecto al makefile utilizado para crear la librería , es la inclusión de la macro LIBS, que indica al enlazador donde encontrar las librerías estándar, y la indicación de que incluya en la compilación nuestra libs/planets.a.  Esto último es importante, pues de lo contrario se obtendrían errores en el enlazado señalando que algunas referencias no han podido ser resueltas:

main.o(.data+0x0):main.cpp: undefined reference to `showMercury()'
main.o(.data+0x4):main.cpp: undefined reference to `showVenus()'
main.o(.data+0x8):main.cpp: undefined reference to `showEarth()'

Suponiendo las condiciones señaladas antes para la confección de la librería, la invocación de este makefile solo exige situarse en el directorio e invocar el fichero:

D:\LearnC\planets\libs>cd ..
D:\LearnC\planets>make -f makefile2.gnu

La respuesta es la creación del ejecutable planets.exe y del fichero-objeto main.o. Como cabría esperar, la ejecución del primero produce la siguiente salida:

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

§3.2  Construir la aplicación con Borland C++ 5.5 Make

Para construir la aplicación que utiliza nuestra librería estática, mediante el Make de Borland, utilizamos un makefile makefile2.bor con el siguiente diseño:

# Makefile2.bor construir la aplicación planets.exe (Borland C++ 5.5.1)

LIBS     = -LE:\BorlandCPP\Lib \
           -LD:\LearnC\planets\libs
CXXFLAGS = -IE:\BorlandCPP\Include

all: planets.exe

planets.exe: main.cpp
    bcc32 -eplanets.exe $(CXXFLAGS) $(LIBS) -WC -P -Q main.cpp planets.LIB

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

La única particularidad es que hemos optado por construir la aplicación en mediante un solo comando, de forma que el compilador Borland gcc32.exe, se encarga de invocar sucesivamente los módulos correspondientes.  Observe que en la línea de comando indicamos que debe incluirse la librería planets.LIB. A su vez, mediante la macro LIBS, señalamos las direcciones donde deben buscarse las librerías necesarias.

La invocación es análoga a la anterior:

D:\LearnC\planets\libs>cd ..
D:\LearnC\planets>make -f makefile2.bor

En esta ocasión, el resultado incluye el fichero main.obj además del ejecutable planets.exe.

  Inicio.


[1]  Recordemos que en los makefiles GNU, las líneas de comando deben estar precedidas de una tabulación (TAB), mientras que en los de Borland basta con un espacio.

[2]  El lector debe realizar los ajustes necesarios en los comandos para adecuarlos a las condiciones particulares de su entorno.

[3]  Para la compilación con Borland C++ 5.5 este include debe ser cambiado por la versión tradicional de la cabecera:

#include <stdlib.h>

[4]  Las "binutils" de Borland incluyen tlib.exe, una herramienta que combina la funcionalidad de ar y ranlib de GNU.