Publi

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:

  • dlopen() : Abre el fichero que queremos cargar, que contendrá el código que quiero ejecutar. Podemos utilizar varios flags para llamarlo, por ejemplo con RTLD_LAZY, todos los símbolos se resolverán sobre la marcha, a medida que vamos haciendo llamadas, con RTLD_NOW, todos los símbolos se resolverán antes de terminar la ejecución de la función. dlopen() nos devolverá un manejador de la biblioteca dinámica.
  • dlclose() : Descarga la biblioteca de la memoria. (Si no especificamos RTLD_NODELETE a la hora de hacer dlopen()).
  • dlerror() : Nos devuelve el texto del error (si ha habido) de la última llamada a estas funciones. Si no hay error, devolverá NULL
  • dlsym() : Devuelve la zona de memoria donde se encuentra un símbolo en memoria. Es lo que nos permite cargar las funciones que tenemos en el shared object.

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

There are 8 comments left Ir a comentario

  1. Pingback: Bitacoras.com /

  2. Pingback: Enlazado dinámico en C++ (dynamic linking) III: Carga dinámica de objetos | Poesía Binaria /

  3. old C wolf /
    Usando Mozilla Firefox Mozilla Firefox 52.0 en Linux Linux

    De man dlopen
    «… Since the value of the symbol could actually be NULL (so that a NULL return from dlsym() need not indicate an error), the correct way to test for an error is to call dlerror() to clear any old error conditions, then call dlsym(), and then call dlerror() again, saving its return value into a variable, and check whether this saved value is not NULL. …»

    Entonces se tiene que escribir:

    dlerror(); saludo = dlsym(lh, «saludo»);
    if ((error = dlerror()) != NULL)

    dlerror; valor = dlsym(lh, «valor»);
    if ((error = dlerror()) != NULL)

    dlerror(); pitagoras = dlsym(lh, «pitagoras»);
    if ((error = dlerror()) != NULL)

    1. Gaspar Fernández / Post Author
      Usando Mozilla Firefox Mozilla Firefox 63.0 en Ubuntu Linux Ubuntu Linux

      Gracias por tu comentario. Es cierto, habría que limpiar la condición de error antes de hacer cualquier cosa. Aunque si la respuesta ante el error, como en el ejemplo, es salir y cancelar la ejecución tampoco pasa nada porque partimos de una situación en la que no ha habido problemas. Pero llevas toda la razón, en un entorno real, con un programa que sea capaz de detectar y solventar algunos problemas, por ejemplo, buscar si varias bibliotecas tienen ciertas funciones o dar la posibilidad al usuario de cambiar la biblioteca (conectando con otro plugin, por ejemplo) estaría muy bien limpiar las condiciones de error para que la ejecución del programa sea correcta.

  4. old C wolf /
    Usando Mozilla Firefox Mozilla Firefox 52.0 en Linux Linux

    Está muy claro tu post, pero aquí hay un pequeño detalle:

    «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.»

    No aclaras que los nombres saludo, valor y pitagoras, son respectivamente variables de tipo apuntador a funciones con el tipo:
    void saludo(), int valor() y float pitagoras(float,float)
    porque las declaraciones con la forma:
    tipo (*f)(tipos)
    significa que f es un apuntador a una función definida como:
    tipo f(tipos)
    La manera correcta de llamar a la función que apunta una variable de tipo apuntador a función, es la que usaste:
    x = (*f)(args);
    pero también se puede escribir:
    x = f(args);

    1. Gaspar Fernández / Post Author
      Usando Mozilla Firefox Mozilla Firefox 63.0 en Ubuntu Linux Ubuntu Linux

      ¡¡¡ Gracias por tu comentario !!! Revisaré este post en cuanto pueda para explicar mejor lo que me indicas, junto con algunos detalles más que tengo anotados en el post.

  5. Mike Rooney /
    Usando Google Chrome Google Chrome 117.0.0.0 en Windows Windows NT

    I really like the way that you have expressed yourself. There is a lot to be admired from this post. You might want to click onYellowstone Merchandise

  6. Andrew Mark /
    Usando Google Chrome Google Chrome 117.0.0.0 en Windows Windows NT

    This is excellent article, thank you for the share! This is what I am looking for, hope in future you will continue sharing such an superb work.
    T Birds Jacket

Leave a Reply to old C wolf Cancle Reply