Cómo crear y usar una biblioteca estática

Como programadores somos personas ordenadas, (al menos la mayor parte del tiempo, y en lo que se refiere a nuestros programas)... Bueno, bueno, tal vez no sea siempre así, pero debería serlo.

En lo que sí coincidimos casi todos es en lo poco que nos gusta hacer varias veces las mismas cosas. Esto suele aburrirnos. Los programadores solemos buscar la forma de que el ordenador haga por nosotros las cosas que de otra forma tendríamos que repetir nosotros mismos, una y otra vez.

Debido a esto surgieron las bibliotecas. Principalmente se trata de colecciones de funciones que necesitamos usar frecuentemente. A menudo estas bibliotecas se comparten con otros programadores, ya sea de una forma comercial o amistosa.

Una de las características de lenguajes como C y C++ es la posibilidad de reutilizar el código, ahorrando muchas horas de trabajo, por no hablar del ahorro de aspirinas y cafeína.

Ya desde el principio de nuestro aprendizaje del lenguaje surgen inquietudes relacionadas con este tema: nos vemos repitiendo una y otra vez las mismas rutinas, y pensamos que nos gustaría dedicar nuestro esfuerzo a hacer cosas nuevas, en lugar de repetir las que ya sabemos hacer. Algunas de esas rutinas se pueden convertir en funciones, y frecuentemente usamos las opciones de cortar y pegar de nuestro editor para copiar esas pequeñas (o grandes) secciones de código.

En este artículo esplicaremos cómo crear bibliotecas estáticas, del mismo tipo de las que incluye cualquier compilador como bibliotecas ANSI. Así, cada uno de nosotros podrá crear sus propias bibliotecas, que contengan las funciones que use más frecuentemente, de una forma ordenada y sobre todo, siempre accesible, sin necesidad de escribir el código cada vez.

Aprender con un ejemplo

Como nos estamos centrando sólo en un entorno concreto: Dev-C++; lo que contemos no será completamente general, pero sí lo suficientemente como para poder adaptarnos a otros entornos de programación sin mucho esfuerzo.

Recordemos que somos personas ordenadas, por lo tanto, nuestras bibliotecas tienen que ser un reflejo de ese orden. Procuraremos no mezclar en una misma biblioteca funciones sobre temas diferentes. Por ejemplo, no es buena idea crear una biblioteca que contenga funciones de tratamiendo de cadenas con funciones de resolución de ecuaciones o con funciones gráficas. Es preferible crear varias bibliotecas separadas.

Además, las bibliotecas deben estar bien documentadas. Es posible que pensemos que normalmente las usaremos nosotros, y que ya sabemos qué hacen, pero también puede suceder que decidamos compartirlas, o sencillamente, que olvidemos cómo hacen lo que hacen, y necesitemos modificarlas o completarlas.

Para ilustrar este ejemplo crearemos una pequeña biblioteca con funciones para manejar cadenas, que complete un poco a la biblioteca de C para el mismo tema. Podemos incluir funciones para convertir cadenas a mayúsculas y a minúsculas, funciones que comparen cadenas sin importar el tipo de sus caracteres, que inviertan el orden de los caracteres, etc.

En Dev-C++, lo primero que hay que hacer es crear un proyecto de tipo "Static Library". El nombre que demos al proyecto es importante, como veremos después, ya que es el nombre que tendrá el fichero con la biblioteca. A este proyecto lo llamaremos "libcadenas.dev".

Proyecto
Proyecto

Lo segundo, y este paso es muy importante, es crear un fichero de cabecera con los prototipos de las funciones que incluirá nuestra biblioteca.

En realidad bastará con los prototipos de las funciones que queramos compartir, puede que existan funciones privadas no tienen por qué aparecer en el fichero de cabecera. Las funciones cuyos prototipos no aparezcan en el fichero de cabecera se podrán usar dentro de la biblioteca, de forma privada, pero no fuera.

Por lo tanto, añadimos un fichero al proyecto, al que llamaremos "cadenas.h". Este fichero será el que incluiremos en la zona de cabeceras en los programas que usen funciones de esta biblioteca.

/*
  Name: cadenas.h
  Copyright: (C) Marzo  de 2004
  Author:  Salvador Pozo, C con Clase www.conclase.net
  Date: 14/03/04 00:07
  Description: Fichero de cabecera para biblioteca de cadenas
*/

// Compara dos cadenas sin importar el tipo
// Hola y hOLA son la misma cadena
// devuelve 0 si s1 y s2 son iguales
// < 0 si s1 es mayor que s2
// > 0 si s1 es menor que s2
int strcmpst(const char *s1, const char *s2);

// Convierte la cadena s1 a mayúsculas, y devuelve
// un puntero a la cadena s1
char *strtomay(char *s1);

// Convierte la cadena s1 a minúsculas, y devuelve
// un puntero a la cadena s1
char *strtomin(char *s1);

// Invierte el orden de la cadena s1, devuelve un
// puntero a la cadena s1
char *invertir(char *s1);

El siguiente punto importante es implementar las funciones de la biblioteca. Para ello añadimos un nuevo fichero al proyecto, en este caso lo llamaremos "cadenas.cpp".

/*
  Name: cadenas.cpp
  Copyright: (C) Marzo  de 2004
  Author:  Salvador Pozo, C con Clase www.conclase.net
  Date: 14/03/04 00:17
  Description: Implementación de la biblioteca cadenas
*/

#include <cctype>

// Compara dos cadenas sin importar el tipo
// Hola y hOLA son la misma cadena
// devuelve 0 si s1 y s2 son iguales
// > 0 si s1 es mayor que s2
// < 0 si s1 es menor que s2
int strcmpst(const char *s1, const char *s2)
{
   while(*s1 && *s2 && toupper(*s1)==toupper(*s2)) {
      s1++;
      s2++;
   }
   return toupper(*s1)-toupper(*s2);
}

// Convierte la cadena s1 a mayúsculas, y devuelve
// un puntero a la cadena s1
char *strtomay(char *s1)
{
   char *cad = s1;

   while(cad && *cad) {
      *cad = toupper(*cad);
      cad++;
   }
   return s1;
}

// Convierte la cadena s1 a minúsculas, y devuelve
// un puntero a la cadena s1
char *strtomin(char *s1)
{
   char *cad = s1;

   while(cad && *cad) {
      *cad = tolower(*cad);
      cad++;
   }
   return s1;
}

// Invierte el orden de la cadena s1, devuelve un
// puntero a la cadena s1
char *invertir(char *s1)
{
   // Prevenir un puntero nulo
   if(!s1) return 0;

   char *cadf = s1;
   char *cadi = s1;
   char aux;

   // Buscar final de cadena
   while(*cadf) cadf++;
   // cad apunta al último carácter
   cadf--;
   // invertir cadena
   while(cadi < cadf) {
      aux = *cadi;
      *cadi = *cadf;
      *cadf = aux;
      cadi++;
      cadf--;
   }
   return s1;
}

El siguiente paso es compilar el proyecto. Evidentemene, no hay nada que ejecutar. El resultado de compilar una biblioteca estática en Dev-C++ es un fichero con extensión ".a", en este ejemplo obtendremos un fichero con el nombre "libcadenas.a".

Usar una biblioteca en nuestros programas

Ya tenemos una biblioteca, pero, ¿cómo podemos usar esa biblioteca en otros programas?. Es sencillo, veamos:

Creamos otro proyecto para probar nuestra biblioteca, esta vez será un ejecutable de consola, al que llamaremos "usocadenas.dev":

Añadimos un fichero al proyecto, "usacadenas.cpp", con este código:

/*
  Name: usacadenas.cpp
  Copyright: (C) Marzo 2004
  Author: Salvador Pozo C con Clase www.conclase.net
  Date: 14/03/04 00:45
  Description: Programa de prueba de biblioteca cadenas.a
*/

#include <iostream>
#include "cadenas.h"

using namespace std;

int main()
{
   char cad1[] = "";
   char cad2[] = "Hola";
   char cad3[] = "Cadena de prueba más larga";

   cout << strcmpst("Holas", "hOLA") << endl;
   cout << strcmpst("Hola", "hOLA") << endl;

   cout << strtomay(cad1) << endl;
   cout << strtomin(cad1) << endl;
   cout << invertir(cad1) << endl;

   cout << strtomay(cad2) << endl;
   cout << strtomin(cad2) << endl;
   cout << invertir(cad2) << endl;

   cout << strtomay(cad3) << endl;
   cout << strtomin(cad3) << endl;
   cout << invertir(cad3) << endl;

   return 0;
}

Otro paso importante es incluir la biblioteca en la fase de enlazado. Para ello acudimos a "opciones de proyecto" y en el menú "Proyecto - Opciones del proyecto", buscamos la pestaña de "Parámetros". En la columna de la derecha, correspondiente al "linker", pulsamos el botón inferior y añadimos el fichero "libcadenas.a".

Opciones
Opciones

Ahora ya podemos ejecutar el programa.

Aprovechar las opciones del enlazador

Si queremos podemos simplificar algo más el uso de bibliotecas.

El compilador usado por Dev-C++ mantiene un directorio de bibliotecas, normalmente en "C:\dev-cpp\lib". También existe un directorio de ficheros de cabecera, "C:\dev-cpp\include".

Si copiamos nuestro fichero de cabecera al directorio "include" y nuestro fichero de biblioteca al directorio "lib" podemos usar nuestra biblioteca como cualquier otra del sistema.

Podemos, por ejemplo, incluir el fichero de cabecera entre "<>":

#include <cadenas.h>

Pero lo más interesante es que podemos usar la opción "-l" para indicar al enlazador que incluya la biblioteca. Para poder hacer esto es necesaria una segunda condición: el fichero de biblioteca debe tener el prefijo "lib" y la extensión ".a". Pero nosotros ya hemos tomado esa precaución.

Para indicar la enlazador que incluya la biblioteca de este modo se especifica de esta forma:

Opciones
Opciones

Resumen:

  • Crear un proyecto de tipo "Static Library", usando un nombre del tipo "lib<nombre>.dev".
  • Crear un fichero de cabecera con los prototipos de funciones, tipos, estructuras, clases, etc. El nombre será del tipo "<nombre>.h".
  • Otro fichero del proyecto servirá para implementar las funciones de la biblioteca.
  • Compilar el proyecto. El resultado será un fichero con un nombre del tipo "lib<nombre>.a", y el fichero de cabecera.
  • Probar la biblioteca.