Poesía Binaria

Enlazado dinámico en C (dynamic linking) II: Carga dinámica de shared objects

La semana pasada vimos una introducción y cómo podíamos hacer una carga estática de bibliotecas dinámicas o shared objects.

Hoy dedicaré el post a la carga dinámica, esto es, no hace falta que el objeto exista para poder compilar el programa principal, y el programa principal, no tiene por qué saber que lo va a utilizar hasta el último momento en que lo esté utilizando. Pueden utilizarse para extender la funcionalidad de nuestra aplicación una vez esté construida, puede que por nosotros, o por otra persona.

Pero puede haber cientos de funciones a las que llamar dentro de un shared object, por lo que debemos seguir unos prototipos de funciones que nosotros estableceremos, y que sabemos con seguridad que se encuentran en el shared object. Crearemos un ejemplo, con varios tipos de funciones, una función void, una función que devuelve un entero, y una función (pitagoras(), sacada del anterior post) que devuelve un float y tiene dos parámetros tipo float. Sería interesante si no tienes experiencia en prototipos de funciones echarle un vistazo a esto, de todas formas, aquí lo contaré con detalle.

Como veréis en la compilación, el programa principal desconoce la biblioteca que va a utilizar, será durante la ejecución cuando le digamos el nombre del archivo que tiene que enlazar. Además, la biblioteca no tiene por qué tener la palabra «lib» delante para poder ser incluida.

external.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>
#include <math.h>

void saludo()
{
  printf ("Hola mundo, soy external.c\n");
}

int valor()
{
  return 99;
}

float pitagoras(float c1, float c2)
{
  return sqrt(c1*c1+ c2*c2);
}

Para compilar, lo hacemos igual que con la carga estática (usamos -lm para incluir la biblioteca matemática):

$ gcc -c -fPIC external.c
$ gcc -shared -o external.so external.o -lm

o, si queremos hacerlo directamente:

$ gcc -fPIC -shared -o external.so external.c -lm

main.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include <stdio.h>
#include <dlfcn.h>
#include <stdlib.h>

int main(int argc, char *argv[])
{
  void *lh = dlopen("./external.so", RTLD_LAZY);
  void (*saludo)();
  int (*valor)();
  float (*pitagoras)(float c1, float c2);
  char* error;

  if (!lh)
    {
      printf("ERROR CARGANDO EL ARCHIVO %s\n", dlerror());
      exit(1);
    }
 
  saludo = dlsym(lh, "saludo");
  if ((error = dlerror()) != NULL)  
    {
      fprintf(stderr, "%s\n", error);
      exit(1);
    }

  valor = dlsym(lh, "valor");
  if ((error = dlerror()) != NULL)  
    {
      fprintf(stderr, "%s\n", error);
      exit(1);
    }

  pitagoras = dlsym(lh, "pitagoras");
  if ((error = dlerror()) != NULL)  
    {
      fprintf(stderr, "%s\n", error);
      exit(1);
    }

  (*saludo)();
  printf("Valor: %d\n", (*valor)());
  printf("Hipotenusa: %f\n", (*pitagoras)(3,4));
 
  dlclose(lh);
  return 0;
}

Y compilamos con:

$ gcc -o main main.c -ldl

-ldl lo usamos para incluir la biblioteca de carga dinámica (dynamic loading)

En este código utilizaremos 4 funciones importantes:

Es importante que conozcamos la forma que tienen las funciones que vamos a llamar, por ejemplo, saludo() la declaramos así:

1
  void (*saludo)();

Con esto definimos el prototipo de una función void, y pitagoras la declaramos así:

1
  float (*pitagoras)(float c1, float c2);

donde le definimos los argumentos que admite la función. Si cometemos algún fallo en la declaración de los prototipos de funciones podemos provocar errores en tiempo de ejecución de nuestra aplicación.

Es importante llamar a dlerror() tras cada llamada a dlsym(), incluso después de dlopen() o dlclose() si no vamos a provocar el fin de la ejecución del programa, y por supuesto, podemos crear funciones que utilicen estas llamadas para hacer nuestro código más fácil para nosotros.

Foto: Jota Aguilar (Flickr) CC-A a 01/03/2013

También podría interesarte....