C++ (Decimotercera parte)
1.4.4a1 Ejemplo de fichero de definición .DEF
§1 Sinopsis
A continuación se incluye un fichero .DEF junto con un comentario detallado de sus diversas secciones.
; fichero .DEF de ejemplo
NAME HOLA
DESCRIPTION 'C++ Windows Hola Mundo'
EXETYPE WINDOWS
CODE MOVEABLE
DATA MOVEABLE MULTIPLE
HEAPSIZE 1024
STACKSIZE 5120
EXPORTS MainWindowProc
Comentario
NAME Indica el nombre de un ejecutable normal. Si se desea construir una DLL, debe utilizarse la sección LIBRARY en sustitución de esta. Cualquier fichero DEF debe tener alguna de estas dos secciones, pero no ambas al mismo tiempo. Recuerde que este nombre debe coincidir con el del ejecutable.
DESCRIPTION Es una cadena que permite especificar libremente determinadas características del ejecutable o librería.
EXETYPE es WINDOWS
.
CODE determina los atributos por defecto del segmento de código. El argumento MOVEABLE significa que este segmento puede ser movido en tiempo de ejecución.
DATA determina los argumentos por defecto del segmento de datos. En este caso, MOVEABLE significa que puede ser movido en memoria en tiempo de ejecución. Por su parte, MULTIPLE garantiza que cada instancia de la aplicación dispondrá de su propio segmento de datos
.
HEAPSIZE especifica el tamaño del montón ( 1.3.2).
STACKSIZE especifica el tamaño de la pila ( 1.3.2). Recuerde que no debe utilizar esta variable si pretende crear una DLL.
EXPORTS relaciona aquellas funciones de la aplicación HOLA que pueden ser invocadas por otras aplicaciones o por el mismo Windows (las "callbacks". Recuerde que los compiladores BC++ y MSVC proveen de los especificadores __declspec(dllexport) y __export, de forma que las funciones declaradas con ellos no necesitan ser incluidas en esta sección.
Observe que esta aplicación no tiene sección IMPORTS, porque suponemos que las únicas funciones que utiliza de otros módulos son de la API de Windows ( 1.7.1) y estas funciones son importadas mediante la inclusión de IMPORT32.LIB (esta librería es incluida automáticamente por BC++ en el proceso de construcción de un ejecutable Windows). Recuerde que cuando una aplicación necesita invocar funciones externas (situadas en librerías dinámicas), dichas funciones deben ser incluidas en la sección IMPORTS, o en una librería de importación ( 1.4.4b2c) enlazada estáticamente con la aplicación.
La aplicación presentada no incluye la sección STUB. Suponemos que se compila con BC++ y no incluimos nada en esta opción, ya que esta plataforma de desarrollo dispone de un "Stub" preconstruido que es utilizado por defecto si no se especifica explícitamente otro ejecutable.
Nota: Este stub por defecto es el responsable de que si pretendemos correr una aplicación desarrollada para Windows32 en una máquina MS DOS, aparezca un mensaje: This program must be run under Win32.
Inicio.
________________________________________
La documentación que acompaña al compilador Borland C++ 5.5 indica que esta versión solo soporta WINDOWS como parámetro de la variable EXETYPE. No obstante, esta se mantiene solo por cuestiones de compatibilidad, y debe ser sustituida por NAME o LIBRARY.
Recuerde que Windows es un sistema operativo que permite multiprogramación ( 1.7). Cada programa en ejecución se denomina "instancia" y estas instancias pueden ser del mismo ejecutable.
1.4.4b Librerías: generalidades
Nota: tenga en cuenta que este capítulo no trata de características que puedan considerarse estándar del lenguaje C++, sino de peculiaridades (aunque muy extendidas) de la construcción de aplicaciones. Tenga en cuenta también que en informática, el concepto "Librería" es muy general, y no está asociado a ningún lenguaje concreto (aunque C y C++ las utilizan ampliamente). De hecho, es posible y frecuente, utilizar en un lenguaje librerías que han sido escritas en otro. Por ejemplo, buena parte de las librerías de C++Builder, la denominadas VCL "Visual Component Library", han sido desarrolladas en Pascal ( 4.11.8b).
§1 Sinopsis
Al tratar de la construcción de un programa ( 1.4) señalamos que en ocasiones no se desea construir un ejecutable, al menos no en el sentido tradicional del término, sino una librería, y que estas librerías son trozos de código que contienen alguna funcionalidad pre-construida que puede ser utilizada por un ejecutable. Por supuesto, las librerías contienen en su interior variables y funciones. Si como suponemos son librerías C++, lo más probable es que estas variables y funciones estén encapsuladas en forma de clases ( 4.11). Observe que la idea central de librería es precisamente la de ser un módulo de software preconstruido -generalmente por terderos- para cuya utilización no es necesario conocer los detalles íntimos de su funcionamiento, sino su interfaz. Es decir, que respuestas nos puede dar y cómo hay que preguntar -a la librería- para obtenerlas.
En general, el término librería se utiliza para referirse a un conjunto de módulos objeto .obj / .o (resultados de compilación) agrupados en un solo fichero que suele tener las extensiones .lib, .bpl
.a, .dll, etc. Estos ficheros permiten tratar las colecciones de módulos como una sola unidad, y representan una forma muy conveniente para el manejo y desarrollo de aplicaciones grandes, además de ser un concepto muy fértil para la industria del software, ya que permiten la existencia de las librerías de los propios compiladores ( 5) y de un mercado de utilidades y componentes adicionales. Son las denominadas librerías 3pp (de terceras partes), en referencia a que no son incluidas de forma estándar con los compiladores ni creadas por el programador de la aplicación.
En este sentido el software se parece a cualquier otro mercado de componentes. Además de las librerías más o menos extensas que acompañan a los compiladores, pueden adquirirse otras, que permiten añadir a nuestros programas las funcionalidades más diversas sin necesidad de ser un experto en cada área de la programación y sin necesidad de que tengamos que estar reinventando la rueda constantemente. Si quiere una opinión autorizada -en inglés- sobre la filosofía de uso e importancia de las librerías en C++, puede consultar este documento del Sr. Stroustrup: Abstraction, libraries, and efficiency in C++
§2 Tipos
En lo que respecta al lenguaje C++, existen dos tipos fundamentales de librerías: estáticas y dinámicas, que aunque comparten el mismo nombre genérico "librería", utilizan mecanismos distintos para proporcionar su funcionalidad al ejecutable.
En ambos casos es costumbre, que junto a las librerías propiamente dichas (ficheros .lib, .a, .dll etc), se incluya un fichero .h denominado "de cabecera" ( 4.4.1), porque es tradición utilizar las primeras líneas del programa para poner las directivas #include ( 4.9.10g) que los incluirán en el fuente durante la fase de preproceso ( 1.4). Este fichero contiene las declaraciones de las entidades contenidas en la librería, así como las macros y constantes predefinidas utilizadas en ella, de forma que el programador solo tiene que incluir el correspondiente fichero .h en su aplicación para poder utilizar los recursos de la librería en cuestión (recuerde que en C/C++ es imprescindible incluir la declaración de cualquier función o clase antes de su utilización 4.1.2). Este sistema tiene la ventaja adicional de que proporciona al usuario la información mínima para su uso. Es decir, la "interfaz" de las funciones o clases que utilizará. En el caso de funciones esto se concreta en el prototipo ( 4.4.1); en el caso de clases, en la especificación de sus métodos y propiedades públicas.
§2.1 Librerías estáticas
Denominadas también librerías-objeto, son colecciones de ficheros objeto (compilados) agrupados en un solo fichero de extensión .lib, .a, etc. junto con uno o varios ficheros de cabecera (generalmente .h).
Nota: una posición extrema la constituyen aquellas librerías en las que toda la funcionalidad se ha incluido en el fichero de cabecera .h, en cuyo caso no existen los módulos compilados .lib, .a, etc. Es el caso de la Librería Estándar de Plantillas STL ( 5.1) que está compuesta casi exclusivamente por ficheros de cabecera. No obstante, lo anterior representa un caso extremo que suele ser evitado, ya que por lo general, los autores incluyen en los ficheros de cabecera la información mínima indispensable para utilizar la librería (la interfaz), incluyendo la operatoria en forma de ficheros compilados. La razón no suele ser otra que proteger la propiedad intelectual (el "know how".
Durante la construcción de la aplicación, el preprocesador incluye en los fuentes los ficheros de cabecera. 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 el programa, de forma que el conjunto entra a formar parte del ejecutable. De ahí su nombre: Librerías enlazadas estáticamente
.
Dejando aparte consideraciones de comodidad y rapidez, el resultado de utilizar una de tales librerías no se diferencia en nada al que puede obtenerse escribiendo en al fuente las funciones o clases correspondientes y compilándolas como un módulo más de nuestra aplicación.
Nota: genralmente los compiladores disponen de herramientas específicas para la creación de librerías estáticas. Por ejemplo, la del compilador Borland C++ es el ejecutable TLIB.EXE ( 1.4.0w1); las de GNU se denominan ar y ranlib. Como tendremos ocasión de ver en los ejemplos, también pueden crearse mediante opciones específicas en la orden de compilación.
§2.1.1 Diccionario
Junto con los módulos .obj que las componen, las librerías estáticas incluyen una especie de índice o diccionario con información sobre su contenido. Este índice contiene los nombres de los recursos públicos de los distintos módulos (que pueden ser accedidos desde el exterior) y su dirección. Estos nombres deben ser distintos para evitar ambigüedades durante el enlazado, y sirven para incrementar la velocidad de enlazado cuando el "Linker" debe incluir alguno en un ejecutable.
Nota: cuando se crea una librería estática a partir de uno o varios ficheros relocalizables (objetos), el proceso de incluir esta tabla o diccionario de símbolos puede ejecutarse en un solo paso o en dos, aunque siempre en el momento de crear la librería. Por ejemplo, tlib de Boland crea la librería y la tabla en un solo proceso. En cambio, ar de GNU puede crear la librería y posteriormente añadir la tabla (esto último puede también hacerse con ranlib). Cuando se añade un nuevo módulo a una librería existente, la misma herramienta que añade el contenido, se encarga de actualizar el índice.
§2.2 Librerías dinámicas
Otra forma de añadir funcionalidad a un ejecutable son las denominadas librerías de enlazado dinámico (repasar en 1.4.4 el significado de "enlazado dinámico", generalmente conocidas como DLLs, acrónimo de su nombre en inglés ("Dynamic Linked Library". Estas librerías se utilizan mucho en la programación para el SO Windows. Este Sistema contiene un gran número de tales librerías de terminación .DLL, aunque en realidad pueden tener cualquier otra terminación .EXE, .FON, .BPI, .DRV etc. Cualquiera que sea su terminación, de forma genérica nos referiremos a ellas como DLLs, nombre por el que son más conocidas.
Nota: la programación tradicional de aplicaciones Windows utilizando la API del Sistema ( 1.7.1) es en realidad una sucesión de invocación a funciones contenidas en librerías de este tipo. En realidad este Sistema Operativo está constituido por un conjunto de DLLs; la mayoría de los ficheros de disco asociados con el Sistema son de este tipo, y se ha llegado a afirmar que escribir una DLL es escribir una extensión del propio Windows ( PW2E Petzold p.878).
§3 Diferencias: librería Estática "versus" Dinámica
Las diferencias más relevantes de las librerías dinámicas respecto a las estáticas son fundamentalmente dos:
• Las librerías estáticas quedan incluidas en el ejecutable, mientras las dinámicas son ficheros externos, con lo que el tamaño de la aplicación (nuestro ejecutable) es mayor en el primer caso que en el segundo. Esto puede ser de capital importancia en aplicaciones muy grandes, ya que el ejecutable debe ser cargado en memoria de una sola vez
.
• Las librerías dinámicas son ficheros independientes que pueden ser invocados desde cualquier ejecutable, de modo que su funcionalidad puede ser compartida por varios ejecutables. Esto significa que solo se necesita una copia de cada fichero de librería (DLL) en el Sistema. Esta característica constituye la razón principal de su utilización, y es también origen de algunos inconvenientes, principalmente en sistemas como Windows en los que existen centenares de ellas.
Como consecuencia de las diferencias citadas se derivan otras. Por ejemplo:
• Si se realizan modificaciones en los módulos de una librería estática, es necesario recompilar todos los ejecutables que la utilizan, mientras que esto no es necesario en el caso de una librería dinámica, siempre que su interfaz se mantenga.
• Como consecuencia de lo anterior, generalmente es más difícil la depuración y mantenimiento de aplicaciones que utilizan librerías dinámicas que las estáticas, ya que en el primer caso, es necesario controlar qué versiones de los ejecutables (.EXE) son compatibles con qué versiones de las DLLs y de estas entre sí, de forma que el usuario no utilice un versiones incompatibles de los ficheros que componen la aplicación.
• Durante la ejecución de un ejecutable, las librerías estáticas que hubiesen intervenido en su construcción no necesitan estar presentes, en cambio las dinámicas deben estar en el mismo directorio o en el camino de búsqueda "Path"
.
• Las librerías estáticas solo se utilizan en la fase de construcción del ejecutable. Las dinámicas se utilizan durante la ejecución.
• Los ejecutables que utilizan librería estáticas solo incorporan los módulos de aquellas que necesitan para resolver sus símbolos externos. Por contra, las librerías dinámicas deben ser cargadas en su totalidad aunque no solo se utilice una parte de su funcionalidad (no son divisibles).
• Las librerías estáticas, que entran a formar parte indivisible del ejecutable, son cargadas con el proceso de carga de este. Las librerías dinámicas no necesariamente tienen que cargarse con la carga inicial (aunque pueden serlo). De hecho, una librería dinámica puede ser cargada bajo demanda en el momento en que se necesita su funcionalidad, e incluso puede ser descargada cuando no resulta necesaria.
• El mecanismo de enlazado estático depende del compilador. El de enlazado dinámico depende del SO, de forma que manteniendo ciertas precauciones, las DLLs construidas con un lenguaje y un compilador pueden ser utilizadas por cualquier aplicación.
§4 Utilizar Librerías
Desde la óptica del programador C++, el manejo de librerías comprende dos aspectos totalmente diferenciados: su utilización y quizás la construcción de alguna de ellas si nuestras aplicaciones son medianamente grandes.
En cuanto al primer punto, es seguro que cualquier aplicación por pequeña que sea, utilice algunas de la Librería Estándar ( 5). Por ejemplo, cada vez que en su código aparece una sentencia del tipo
cout << "Hola mundo" << endl;
está utilizando una librería estática, y cada vez que en la programación de una aplicación Windows utiliza un mensaje del tipo
MessageBox(NULL, "Hola mundo!", "Mi primer programa", MB_OK);
está usando una librería dinámica. En cuanto a su construcción, si se dedica a esto de programar en C++, antes o después pondrá manos a la obra. Por cierto: existen empresas de software cuya principal actividad es precisamente fabricar y vender librerías (ya hemos indicado que el mercado de las 3pp es todo un "mundillo" dentro de la informática).
Cualquiera que sea el caso, tanto la utilización como la construcción, son diferentes según se trate de librerías estáticas o dinámicas. En las páginas que siguen se describen en detalle ambas situaciones. Empezaremos por una descripción general de su funcionamiento, para continuar con la descripción de los pasos necesarios para construirlas. A continuación exponemos los detalles de su utilización, incluyendo un ejemplo de construcción de un ejecutable que utiliza los recursos de una librería.
Inicio.
________________________________________
Recordemos que en C++, uno de los significados del término "estático" es algo que ha sido resuelto en tiempo de compilación ( 1.4.4).
Existen utilidades que permiten compactar ("Squeeze" un ejecutable disminuyendo su tamaño como fichero, lo que puede ser de utilidad a la hora de transportarlo (por redes por ejemplo). Pero incluso en estos casos, después de cargados en memoria deben ser "expandidos" a su tamaño normal y reacomodados en memoria según un patrón definido por el SO.
BPI; acrónimo de Borland Package Import Library. Un tipo especial de librería dinámica del citado compilador, que utiliza enlazado estático con el ejecutable que las usa.
En el caso del compilador BC++, durante la construcción de un programa o librería, este "path" puede ser controlado mediante el comando de compilación -L o de enlazado /L. Más tarde durante la ejecución (runtime) el "path" depende de las variables de entorno del sistema.
En los programas para Windows caben tres opciones: poner las librerías específicas en el mismo directorio que el ejecutable (o un subdirectorio del anterior); en nuestra opinión esto sería lo recomendado. Otra opción es ponerlas junto con el resto de librerías del sistema (WindowsSystem) o en un directorio particular cualquiera (no es aconsejable).
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:LearnCplanetslibs, 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
.
# 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
, e invocamos la utilidad de forma que utilice nuestro fichero:
C:Windows>D:
D:>cd LearnCplanetslibs
D:LearnCplanetslibs>set PATH=Cev-Cppbin;%path%
D:LearnCplanetslibs>make -f makefile.gnu
Después de unos instantes tenemos en nuestro directorio D:LearnCplanetslibs 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:BorlandCPPInclude
LIBS = -LE:BorlandCPPLib
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
. 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 LearnCplanetslibs
D:LearnCplanetslibs>set PATH=E:BORLAN~1BIN;%path%
D:LearnCplanetslibs>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:LearnCplanets, al que denominaremos main.cpp:
// main.cpp
#include <cstdlib> // ver nota
#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:LearnCplanetslibs>cd ..
D:LearnCplanets>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:BorlandCPPLib
-LD:LearnCplanetslibs
CXXFLAGS = -IE:BorlandCPPInclude
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:LearnCplanetslibs>cd ..
D:LearnCplanets>make -f makefile2.bor
En esta ocasión, el resultado incluye el fichero main.obj además del ejecutable planets.exe.
Inicio.
________________________________________
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.
El lector debe realizar los ajustes necesarios en los comandos para adecuarlos a las condiciones particulares de su entorno.
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>
Las "binutils" de Borland incluyen tlib.exe, una herramienta que combina la funcionalidad de ar y ranlib de GNU.
1.4.4a1 Ejemplo de fichero de definición .DEF
§1 Sinopsis
A continuación se incluye un fichero .DEF junto con un comentario detallado de sus diversas secciones.
; fichero .DEF de ejemplo
NAME HOLA
DESCRIPTION 'C++ Windows Hola Mundo'
EXETYPE WINDOWS
CODE MOVEABLE
DATA MOVEABLE MULTIPLE
HEAPSIZE 1024
STACKSIZE 5120
EXPORTS MainWindowProc
Comentario
NAME Indica el nombre de un ejecutable normal. Si se desea construir una DLL, debe utilizarse la sección LIBRARY en sustitución de esta. Cualquier fichero DEF debe tener alguna de estas dos secciones, pero no ambas al mismo tiempo. Recuerde que este nombre debe coincidir con el del ejecutable.
DESCRIPTION Es una cadena que permite especificar libremente determinadas características del ejecutable o librería.
EXETYPE es WINDOWS
.
CODE determina los atributos por defecto del segmento de código. El argumento MOVEABLE significa que este segmento puede ser movido en tiempo de ejecución.
DATA determina los argumentos por defecto del segmento de datos. En este caso, MOVEABLE significa que puede ser movido en memoria en tiempo de ejecución. Por su parte, MULTIPLE garantiza que cada instancia de la aplicación dispondrá de su propio segmento de datos
.
HEAPSIZE especifica el tamaño del montón ( 1.3.2).
STACKSIZE especifica el tamaño de la pila ( 1.3.2). Recuerde que no debe utilizar esta variable si pretende crear una DLL.
EXPORTS relaciona aquellas funciones de la aplicación HOLA que pueden ser invocadas por otras aplicaciones o por el mismo Windows (las "callbacks". Recuerde que los compiladores BC++ y MSVC proveen de los especificadores __declspec(dllexport) y __export, de forma que las funciones declaradas con ellos no necesitan ser incluidas en esta sección.
Observe que esta aplicación no tiene sección IMPORTS, porque suponemos que las únicas funciones que utiliza de otros módulos son de la API de Windows ( 1.7.1) y estas funciones son importadas mediante la inclusión de IMPORT32.LIB (esta librería es incluida automáticamente por BC++ en el proceso de construcción de un ejecutable Windows). Recuerde que cuando una aplicación necesita invocar funciones externas (situadas en librerías dinámicas), dichas funciones deben ser incluidas en la sección IMPORTS, o en una librería de importación ( 1.4.4b2c) enlazada estáticamente con la aplicación.
La aplicación presentada no incluye la sección STUB. Suponemos que se compila con BC++ y no incluimos nada en esta opción, ya que esta plataforma de desarrollo dispone de un "Stub" preconstruido que es utilizado por defecto si no se especifica explícitamente otro ejecutable.
Nota: Este stub por defecto es el responsable de que si pretendemos correr una aplicación desarrollada para Windows32 en una máquina MS DOS, aparezca un mensaje: This program must be run under Win32.
Inicio.
________________________________________
La documentación que acompaña al compilador Borland C++ 5.5 indica que esta versión solo soporta WINDOWS como parámetro de la variable EXETYPE. No obstante, esta se mantiene solo por cuestiones de compatibilidad, y debe ser sustituida por NAME o LIBRARY.
Recuerde que Windows es un sistema operativo que permite multiprogramación ( 1.7). Cada programa en ejecución se denomina "instancia" y estas instancias pueden ser del mismo ejecutable.
1.4.4b Librerías: generalidades
Nota: tenga en cuenta que este capítulo no trata de características que puedan considerarse estándar del lenguaje C++, sino de peculiaridades (aunque muy extendidas) de la construcción de aplicaciones. Tenga en cuenta también que en informática, el concepto "Librería" es muy general, y no está asociado a ningún lenguaje concreto (aunque C y C++ las utilizan ampliamente). De hecho, es posible y frecuente, utilizar en un lenguaje librerías que han sido escritas en otro. Por ejemplo, buena parte de las librerías de C++Builder, la denominadas VCL "Visual Component Library", han sido desarrolladas en Pascal ( 4.11.8b).
§1 Sinopsis
Al tratar de la construcción de un programa ( 1.4) señalamos que en ocasiones no se desea construir un ejecutable, al menos no en el sentido tradicional del término, sino una librería, y que estas librerías son trozos de código que contienen alguna funcionalidad pre-construida que puede ser utilizada por un ejecutable. Por supuesto, las librerías contienen en su interior variables y funciones. Si como suponemos son librerías C++, lo más probable es que estas variables y funciones estén encapsuladas en forma de clases ( 4.11). Observe que la idea central de librería es precisamente la de ser un módulo de software preconstruido -generalmente por terderos- para cuya utilización no es necesario conocer los detalles íntimos de su funcionamiento, sino su interfaz. Es decir, que respuestas nos puede dar y cómo hay que preguntar -a la librería- para obtenerlas.
En general, el término librería se utiliza para referirse a un conjunto de módulos objeto .obj / .o (resultados de compilación) agrupados en un solo fichero que suele tener las extensiones .lib, .bpl
.a, .dll, etc. Estos ficheros permiten tratar las colecciones de módulos como una sola unidad, y representan una forma muy conveniente para el manejo y desarrollo de aplicaciones grandes, además de ser un concepto muy fértil para la industria del software, ya que permiten la existencia de las librerías de los propios compiladores ( 5) y de un mercado de utilidades y componentes adicionales. Son las denominadas librerías 3pp (de terceras partes), en referencia a que no son incluidas de forma estándar con los compiladores ni creadas por el programador de la aplicación.
En este sentido el software se parece a cualquier otro mercado de componentes. Además de las librerías más o menos extensas que acompañan a los compiladores, pueden adquirirse otras, que permiten añadir a nuestros programas las funcionalidades más diversas sin necesidad de ser un experto en cada área de la programación y sin necesidad de que tengamos que estar reinventando la rueda constantemente. Si quiere una opinión autorizada -en inglés- sobre la filosofía de uso e importancia de las librerías en C++, puede consultar este documento del Sr. Stroustrup: Abstraction, libraries, and efficiency in C++
§2 Tipos
En lo que respecta al lenguaje C++, existen dos tipos fundamentales de librerías: estáticas y dinámicas, que aunque comparten el mismo nombre genérico "librería", utilizan mecanismos distintos para proporcionar su funcionalidad al ejecutable.
En ambos casos es costumbre, que junto a las librerías propiamente dichas (ficheros .lib, .a, .dll etc), se incluya un fichero .h denominado "de cabecera" ( 4.4.1), porque es tradición utilizar las primeras líneas del programa para poner las directivas #include ( 4.9.10g) que los incluirán en el fuente durante la fase de preproceso ( 1.4). Este fichero contiene las declaraciones de las entidades contenidas en la librería, así como las macros y constantes predefinidas utilizadas en ella, de forma que el programador solo tiene que incluir el correspondiente fichero .h en su aplicación para poder utilizar los recursos de la librería en cuestión (recuerde que en C/C++ es imprescindible incluir la declaración de cualquier función o clase antes de su utilización 4.1.2). Este sistema tiene la ventaja adicional de que proporciona al usuario la información mínima para su uso. Es decir, la "interfaz" de las funciones o clases que utilizará. En el caso de funciones esto se concreta en el prototipo ( 4.4.1); en el caso de clases, en la especificación de sus métodos y propiedades públicas.
§2.1 Librerías estáticas
Denominadas también librerías-objeto, son colecciones de ficheros objeto (compilados) agrupados en un solo fichero de extensión .lib, .a, etc. junto con uno o varios ficheros de cabecera (generalmente .h).
Nota: una posición extrema la constituyen aquellas librerías en las que toda la funcionalidad se ha incluido en el fichero de cabecera .h, en cuyo caso no existen los módulos compilados .lib, .a, etc. Es el caso de la Librería Estándar de Plantillas STL ( 5.1) que está compuesta casi exclusivamente por ficheros de cabecera. No obstante, lo anterior representa un caso extremo que suele ser evitado, ya que por lo general, los autores incluyen en los ficheros de cabecera la información mínima indispensable para utilizar la librería (la interfaz), incluyendo la operatoria en forma de ficheros compilados. La razón no suele ser otra que proteger la propiedad intelectual (el "know how".
Durante la construcción de la aplicación, el preprocesador incluye en los fuentes los ficheros de cabecera. 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 el programa, de forma que el conjunto entra a formar parte del ejecutable. De ahí su nombre: Librerías enlazadas estáticamente
.
Dejando aparte consideraciones de comodidad y rapidez, el resultado de utilizar una de tales librerías no se diferencia en nada al que puede obtenerse escribiendo en al fuente las funciones o clases correspondientes y compilándolas como un módulo más de nuestra aplicación.
Nota: genralmente los compiladores disponen de herramientas específicas para la creación de librerías estáticas. Por ejemplo, la del compilador Borland C++ es el ejecutable TLIB.EXE ( 1.4.0w1); las de GNU se denominan ar y ranlib. Como tendremos ocasión de ver en los ejemplos, también pueden crearse mediante opciones específicas en la orden de compilación.
§2.1.1 Diccionario
Junto con los módulos .obj que las componen, las librerías estáticas incluyen una especie de índice o diccionario con información sobre su contenido. Este índice contiene los nombres de los recursos públicos de los distintos módulos (que pueden ser accedidos desde el exterior) y su dirección. Estos nombres deben ser distintos para evitar ambigüedades durante el enlazado, y sirven para incrementar la velocidad de enlazado cuando el "Linker" debe incluir alguno en un ejecutable.
Nota: cuando se crea una librería estática a partir de uno o varios ficheros relocalizables (objetos), el proceso de incluir esta tabla o diccionario de símbolos puede ejecutarse en un solo paso o en dos, aunque siempre en el momento de crear la librería. Por ejemplo, tlib de Boland crea la librería y la tabla en un solo proceso. En cambio, ar de GNU puede crear la librería y posteriormente añadir la tabla (esto último puede también hacerse con ranlib). Cuando se añade un nuevo módulo a una librería existente, la misma herramienta que añade el contenido, se encarga de actualizar el índice.
§2.2 Librerías dinámicas
Otra forma de añadir funcionalidad a un ejecutable son las denominadas librerías de enlazado dinámico (repasar en 1.4.4 el significado de "enlazado dinámico", generalmente conocidas como DLLs, acrónimo de su nombre en inglés ("Dynamic Linked Library". Estas librerías se utilizan mucho en la programación para el SO Windows. Este Sistema contiene un gran número de tales librerías de terminación .DLL, aunque en realidad pueden tener cualquier otra terminación .EXE, .FON, .BPI, .DRV etc. Cualquiera que sea su terminación, de forma genérica nos referiremos a ellas como DLLs, nombre por el que son más conocidas.
Nota: la programación tradicional de aplicaciones Windows utilizando la API del Sistema ( 1.7.1) es en realidad una sucesión de invocación a funciones contenidas en librerías de este tipo. En realidad este Sistema Operativo está constituido por un conjunto de DLLs; la mayoría de los ficheros de disco asociados con el Sistema son de este tipo, y se ha llegado a afirmar que escribir una DLL es escribir una extensión del propio Windows ( PW2E Petzold p.878).
§3 Diferencias: librería Estática "versus" Dinámica
Las diferencias más relevantes de las librerías dinámicas respecto a las estáticas son fundamentalmente dos:
• Las librerías estáticas quedan incluidas en el ejecutable, mientras las dinámicas son ficheros externos, con lo que el tamaño de la aplicación (nuestro ejecutable) es mayor en el primer caso que en el segundo. Esto puede ser de capital importancia en aplicaciones muy grandes, ya que el ejecutable debe ser cargado en memoria de una sola vez
.
• Las librerías dinámicas son ficheros independientes que pueden ser invocados desde cualquier ejecutable, de modo que su funcionalidad puede ser compartida por varios ejecutables. Esto significa que solo se necesita una copia de cada fichero de librería (DLL) en el Sistema. Esta característica constituye la razón principal de su utilización, y es también origen de algunos inconvenientes, principalmente en sistemas como Windows en los que existen centenares de ellas.
Como consecuencia de las diferencias citadas se derivan otras. Por ejemplo:
• Si se realizan modificaciones en los módulos de una librería estática, es necesario recompilar todos los ejecutables que la utilizan, mientras que esto no es necesario en el caso de una librería dinámica, siempre que su interfaz se mantenga.
• Como consecuencia de lo anterior, generalmente es más difícil la depuración y mantenimiento de aplicaciones que utilizan librerías dinámicas que las estáticas, ya que en el primer caso, es necesario controlar qué versiones de los ejecutables (.EXE) son compatibles con qué versiones de las DLLs y de estas entre sí, de forma que el usuario no utilice un versiones incompatibles de los ficheros que componen la aplicación.
• Durante la ejecución de un ejecutable, las librerías estáticas que hubiesen intervenido en su construcción no necesitan estar presentes, en cambio las dinámicas deben estar en el mismo directorio o en el camino de búsqueda "Path"
.
• Las librerías estáticas solo se utilizan en la fase de construcción del ejecutable. Las dinámicas se utilizan durante la ejecución.
• Los ejecutables que utilizan librería estáticas solo incorporan los módulos de aquellas que necesitan para resolver sus símbolos externos. Por contra, las librerías dinámicas deben ser cargadas en su totalidad aunque no solo se utilice una parte de su funcionalidad (no son divisibles).
• Las librerías estáticas, que entran a formar parte indivisible del ejecutable, son cargadas con el proceso de carga de este. Las librerías dinámicas no necesariamente tienen que cargarse con la carga inicial (aunque pueden serlo). De hecho, una librería dinámica puede ser cargada bajo demanda en el momento en que se necesita su funcionalidad, e incluso puede ser descargada cuando no resulta necesaria.
• El mecanismo de enlazado estático depende del compilador. El de enlazado dinámico depende del SO, de forma que manteniendo ciertas precauciones, las DLLs construidas con un lenguaje y un compilador pueden ser utilizadas por cualquier aplicación.
§4 Utilizar Librerías
Desde la óptica del programador C++, el manejo de librerías comprende dos aspectos totalmente diferenciados: su utilización y quizás la construcción de alguna de ellas si nuestras aplicaciones son medianamente grandes.
En cuanto al primer punto, es seguro que cualquier aplicación por pequeña que sea, utilice algunas de la Librería Estándar ( 5). Por ejemplo, cada vez que en su código aparece una sentencia del tipo
cout << "Hola mundo" << endl;
está utilizando una librería estática, y cada vez que en la programación de una aplicación Windows utiliza un mensaje del tipo
MessageBox(NULL, "Hola mundo!", "Mi primer programa", MB_OK);
está usando una librería dinámica. En cuanto a su construcción, si se dedica a esto de programar en C++, antes o después pondrá manos a la obra. Por cierto: existen empresas de software cuya principal actividad es precisamente fabricar y vender librerías (ya hemos indicado que el mercado de las 3pp es todo un "mundillo" dentro de la informática).
Cualquiera que sea el caso, tanto la utilización como la construcción, son diferentes según se trate de librerías estáticas o dinámicas. En las páginas que siguen se describen en detalle ambas situaciones. Empezaremos por una descripción general de su funcionamiento, para continuar con la descripción de los pasos necesarios para construirlas. A continuación exponemos los detalles de su utilización, incluyendo un ejemplo de construcción de un ejecutable que utiliza los recursos de una librería.
Inicio.
________________________________________
Recordemos que en C++, uno de los significados del término "estático" es algo que ha sido resuelto en tiempo de compilación ( 1.4.4).
Existen utilidades que permiten compactar ("Squeeze" un ejecutable disminuyendo su tamaño como fichero, lo que puede ser de utilidad a la hora de transportarlo (por redes por ejemplo). Pero incluso en estos casos, después de cargados en memoria deben ser "expandidos" a su tamaño normal y reacomodados en memoria según un patrón definido por el SO.
BPI; acrónimo de Borland Package Import Library. Un tipo especial de librería dinámica del citado compilador, que utiliza enlazado estático con el ejecutable que las usa.
En el caso del compilador BC++, durante la construcción de un programa o librería, este "path" puede ser controlado mediante el comando de compilación -L o de enlazado /L. Más tarde durante la ejecución (runtime) el "path" depende de las variables de entorno del sistema.
En los programas para Windows caben tres opciones: poner las librerías específicas en el mismo directorio que el ejecutable (o un subdirectorio del anterior); en nuestra opinión esto sería lo recomendado. Otra opción es ponerlas junto con el resto de librerías del sistema (WindowsSystem) o en un directorio particular cualquiera (no es aconsejable).
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:LearnCplanetslibs, 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
.
# 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
, e invocamos la utilidad de forma que utilice nuestro fichero:
C:Windows>D:
D:>cd LearnCplanetslibs
D:LearnCplanetslibs>set PATH=Cev-Cppbin;%path%
D:LearnCplanetslibs>make -f makefile.gnu
Después de unos instantes tenemos en nuestro directorio D:LearnCplanetslibs 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:BorlandCPPInclude
LIBS = -LE:BorlandCPPLib
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
. 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 LearnCplanetslibs
D:LearnCplanetslibs>set PATH=E:BORLAN~1BIN;%path%
D:LearnCplanetslibs>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:LearnCplanets, al que denominaremos main.cpp:
// main.cpp
#include <cstdlib> // ver nota
#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:LearnCplanetslibs>cd ..
D:LearnCplanets>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:BorlandCPPLib
-LD:LearnCplanetslibs
CXXFLAGS = -IE:BorlandCPPInclude
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:LearnCplanetslibs>cd ..
D:LearnCplanets>make -f makefile2.bor
En esta ocasión, el resultado incluye el fichero main.obj además del ejecutable planets.exe.
Inicio.
________________________________________
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.
El lector debe realizar los ajustes necesarios en los comandos para adecuarlos a las condiciones particulares de su entorno.
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>
Las "binutils" de Borland incluyen tlib.exe, una herramienta que combina la funcionalidad de ar y ranlib de GNU. 
