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

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;
   
   cin.get();
   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

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

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.

Comentarios de los usuarios (9)

Elio Espinosa
2011-06-03 05:54:01

"En este artículo esplicaremos"

Esta frase esta tomada del ultimo parrafo del articulo que se llama: Como crear y usar una biblioteca estatica.me parece notar un error en la palabra esplicaremos creo que se escribe explicaremos. por lo demas este pagina es my intructiva especialmente para aquellos que no saben programar como yo

Diego
2012-04-16 23:12:07

Tengo una duda, lo que pasa es que recién estoy aprendiendo esto de hacer proyectos, crear archivos objetos, enlazarlos y crear ejecutables o bibliotecas, se que hay varios tipos de archivos más relacionados con la programación, como las bibliotecas dinámicamente enlazadas, los archivos de recursos y los makefile, pero aún no los domino.

Por lo general todas las implementaciones de clases y funciones con sus respectivas declaraciones y definiciones que he hecho, las he guardado en un archivo de cabecera con las típicas instrucciones del Pre-procesador ifndef define endif, y cuando quiero volver a ocupar el código, escribo la instrucción #include archivo.h

Mis programas eran un solo archivo .cpp y varios .h, pensando que todos los programas se basaban en un único archivo.cpp el cual contiene la función main, bueno, así me enseñaron en la universidad, aun no me han dicho nada de enlazadores ni ficheros objetos.

Leyendo me di cuenta de que la idea de los archivos.cpp

es implementar las definiciones de funciones y clases, mientras que los archivos de cabecera deben tener las declaraciones de clases y prototipos de funciones.

Ahora se que es mejor tener las librerías ya compiladas con el fin de ahorrar tiempo de compilación, así que he querido compilar algunas bibliotecas mías con sus respectivos archivos.h y he aquí el problema.

¿Que hago en el caso de los templates?

Muchas de mis implementaciones son plantillas, por ejemplo

una pila que contiene nodos con datos de tipo "tipo", si compilo mi plantilla pila con sus respectivas definiciones y todo, me dice que está todo OK, pero al momento de crear un objeto pila de tipo int por ejemplo, el enlazador me da error, es decir no encuentra las definiciones en la biblioteca. Al instante se me vino a la cabeza que las plantillas se crean al momento de compilación y se crea una clase por cada tipo que se use en el programa, entonces surgió mi duda.

¿Se pueden crear bibliotecas estáticas (o dinámicas) de una plantilla o que se hace en ese caso?, si he dicho algo erroneo en mi comentario me gustaría saberlo para aclarar conceptos, saludos y muy buena página :).

Steven R. Davidson
2012-04-16 23:49:33

Hola Diego,

Efectivamente, las plantillas producen un problema cuando queremos separar la declaración de entidades de sus definiciones (o implementaciones). La razón como bien dices es que las plantillas son un mecanismo para el compilador y no para el enlazador. Esto significa que TODA la definición de una plantilla debe conocerse durante el tiempo de compilación. Por ejemplo,

// "sc.h"
template< typename T >
class sc  // Simple Contenedor
{
public:
  typedef T tipo;
  typedef T *ptr;
  typedef const T *const_ptr;
  typedef T &ref;
  typedef const T &const_ref;

private:
  T dato;

public:
  sc();
  sc( const_ref r );
  sc( const sc &ref );
  ~sc();

  const_ref elem() const;
  ref elem();
};

template< typename T >
sc<T>::sc()  {}

template< typename T >
sc<T>::sc( sc<T>::const_ref r ) : dato(r)  {}

template< typename T >
sc<T>::sc( const sc &ref ) : dato(ref.dato)  {}

template< typename T >
sc<T>::~sc()  {}

template< typename T >
typename sc<T>::const_ref sc<T>::elem() const  { return dato; }

template< typename T >
typename sc<T>::ref sc<T>::elem()  { return dato; }

Como puedes ver, TODO debe estar dentro del fichero de cabecera, el cual se #incluye cuando se quiera usar en la compilación. Esto es la limitación de una plantilla en C++. Lo que sí podemos hacer es guardar una clase, generada de una plantilla, en un fichero objeto o en una biblioteca estática o dinámica, como puede ser el caso de las clases estándares: 'string', 'complex', etcétera al igual que los objetos predefinidos estándares: 'cin', 'cout', 'cerr', y 'clog'; pero no las plantillas en sí.

Espero haber aclarado la duda.

Steven

Diego
2012-04-17 01:36:05

Muchas gracias por la respuesta, totalmente aclarado. Aunque respecto a lo que dices sobre guardar una plantilla generada, por ejemplo si tengo este código:

template <typename tipo = int>
  class fraccion
  {
     private:
       tipo numerador;
       tipo denominador;

     public:
       fraccion();
      //Todos los métodos asociados    
  };

template <typename tipo>
  fraccion<tipo>::fraccion() : 
   numerador(static_cast<tipo>(0)),
   denominador(static_cast<tipo>(1)){}

   //Más definiciones de los métodos

template <typename tipo>
  void InsertionSort(tipo* array, unsigned int longitud)
  {
    //Procedimiento
  }

Como haría para crear un archivo objeto que tenga las definiciones de la plantilla fracción, para int, long int y long long int.

y en cuanto a la función plantilla InsertionSort(No se si se dice función plantilla) crear un archivo objeto con las definiciones para float, double y long double en un fichero objeto.

Y por último, ¿las declaraciones de objetos serían iguales?

gracias por responder :)

Steven R. Davidson
2012-04-17 03:36:49

Hola Diego,

- Para las clases que te interesan, tienes que generarlas en alguna parte. Sugiero crear un tipo definido; por ejemplo,

typedef fraccion< int > ifraccion;
typedef fraccion< long int > lifraccion;
typedef fraccion< long long int > llifraccion;

- Sí; se dice "función plantilla". Haríamos lo mismo que antes. En cuanto usemos una función particular, basada en una plantilla, el compilador genera tal función. También podríamos usar 'typedef'.

- Las definiciones de los objetos, también serían "normales". De hecho, si vas a instanciar objetos de clases plantilla, entonces no necesitas el "truco" del 'typedef'. Esto sería,

fraccion< int > g_identidad( 1, 1 );

Al mencionar 'fraccion<int>', se generará esta clase y existirá en este código fuente.

Espero haber aclarado el tema.

Steven

Diego
2012-04-17 04:13:23

Gracias, ahora si que me ha quedado claro, osea, para incluir ciertas instancias de plantillas de clases y funciones tengo que escribir algo en el código fuente que le indique al compilador que debe generar todas esas instancias de plantillas, por ejemplo, ¿podría hacer algo como esto?


//Función estática con el objetivo de generar instancias
static void generador()
{
   fraccion<int> a;
   fraccion<long int> b;
   fraccion<long long int> c;
   InsertionSort((float*)0, 0U);
   InsertionSort((double*)0, 0U);
   InsertionSort((long double*)0, 0U);
}

Esta si es la última pregunta que hago, gracias Steven por darte el tiempo de contestar mis dudas, te lo agradezco mucho, saludos.

Steven R. Davidson
2012-04-17 04:29:53

Hola Diego,

Efectivamente, tenemos que hacer alguna mención de las clases o funciones que queremos para forzar el compilador a generarlas a partir de la definición de la plantilla.

En cuanto a 'InsertionSort()', podemos ser más explícitos, escribiendo lo siguiente,

InsertionSort< float >( 0, 0U );
InsertionSort< double >( 0, 0U );
InsertionSort< long double >( 0, 0U );

Como poder hacerse, sí se puede. Sin embargo, deberíamos pensar si realmente queremos hacer esto, ya que la idea tras las plantillas es la de generar clases y funciones globales que se adapten a los criterios representados por los parámetros de tales plantillas. De hecho, esto se suele llamar "polimorfismo estático", ya que se parece mucho al polimorfismo "regular" (dinámico) pero en tiempo de compilación.

Hasta pronto,

Steven

Diego
2012-04-24 18:44:56

Hola, acerca de la conversación del otro día he averiguado algo bastante útil y quisiera compartirlo.

C++ tiene una sintaxis específica para "obligar" al compilador a instanciar una plantilla de forma explícita y es como sigue:

Para el caso de la clases, en el caso de la clase fraccion por ejemplo, debe escribirse:

template class fraccion<int>; 

No confundir con,

template<> class fraccion<int>;

El cual indica que definiremos una especialización.

El otro método más coloquial que me resultó, sería declarar un objeto de esa clase plantilla para generarla implicitamente, usando por ejemplo una variable global estática,

static fraccion<int> a;

Esos son los dos métodos que me resultaron.

Bueno y en cuanto a las plantillas de funciones la sintaxis es análoga.

template void InsertionSort<double>(double*, unsigned int);

//o simplemente

template void InsertionSort(double*, unsigned int);

Que no debe ser confundida con

template<> void InsertionSort(double*, unsigned int);

que es el prototipo de una especialización.

Espero que haya sido util esta información, saludos.

blog cypascal
2015-06-05 14:22:20

Buen artículo, como todos los del blog, la referencia de la programación en C en español. Dejo un pequeño aporte, sobre este tema también, tratado desde un punto de vista diferente, probablemente más acedémico.

http://cypascal.blogspot.com.es/2012/11/crea-tu-propia-biblioteca-en-cc.html

Un saludo