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