Creación de Plugins

ARCO

Revision History
Revision 0.113 / 08 / 2004Revised by: Víctor Rodríguez López
Inicial.

Guia básica para la creación de Plugins en entorno GNU/Linux.


Table of Contents
1. Introducción
2. El cargador de enlace dinámico (libdl)
3. Plugin usando libdl
3.1. Carga Explicita, usando dlsym()
3.1.1. Makefile
3.1.2. Código de la Librería
3.1.3. Código del Programa Principal
3.2. Carga Implicita, usando Lista Enlazada
3.2.1. Makefile
3.2.2. Código de las Librerías
3.2.3. Código del Programa Principal
4. Plugin usando GModule
4.1. Carga Explicita, usando g_module_symbol()
4.1.1. Makefile
4.1.2. Código de la Librería
4.1.3. Código del Programa Principal
4.2. Carga Implicita, usando Tabla Hash
4.2.1. Makefile
4.2.2. Código de la Librería
4.2.3. Código del Programa Principal
Referencias

1. Introducción

Los plugins son librerías que pueden ser cargadas bajo demanda en tiempo de ejecución. Es una forma muy elegante para construir programas extensibles y modulares.


2. El cargador de enlace dinámico (libdl)

POR ESCRIBIR


3. Plugin usando libdl


3.1. Carga Explicita, usando dlsym()

En este ejemplo se crean dos librerías, una de ellas dinámica libb.so y la otra estática liba.a. El código de este ejemplo se puede encontrar en el repositorio publico del grupo ARCO.

Estas librerías serán llamadas más tarde en el programa principal, libb.so será cargada como un plugin, por otro lado liba.a será tratada como librería estática, y así de esta manera resaltar las diferencias que existen entre ambas.


3.1.1. Makefile

Vamos a ver en detalle el Makefile principal:

CC=gcc
CFLAGS=-Wall -ggdb -Idira                                  (1)
LDFLAGS=-Ldira                                             (2)
LDLIBS=-la -ldl                                            (3)

all: libs main

main: main.o

#
# Estas reglas ejecutan los makefiles que hay en los subdirectorios
#
libs:
	$(MAKE) -C dira
	$(MAKE) -C dirb

clean:
	$(RM) *.o *~ main
	$(MAKE) -C dira clean
	$(MAKE) -C dirb clean

	  
(1)
-Idira sirve para indicar donde se encuentra el fichero de interfaz.
(2)
-Ldira indica el directorio donde se encuentra a librería.
(3)
-la y -ldl indica el nombre de la librería.

3.1.2. Código de la Librería

En dira y dirb se encuentra el código de las dos librerías de nuestro ejemplo.

No tiene nada especial, simplemente las funciones que queremos que implemente la librería.


3.1.3. Código del Programa Principal

Únicamente se resaltan los aspectos importantes del fichero main.c para la creación de plugins.

Indicamos el archivo de cabecera para la librería libdl.

	  #include <dlfcn.h>
	

dlopen carga en tiempo de ejecución la librería indicada en el primer argumento, el segundo argumento es un flag de la librería.

	  pLibb = dlopen("./dirb/libb.so.1.0.0", RTLD_LAZY);
	

Ahora se obtiene la referencia al símbolo (o función) de la librería, para ello se usa dlsym.

	  pFuncion = dlsym(pLibb, "b"));
	

Usar el símbolo:

	  pFuncion(i);  
	

Liberamos recursos:

	  dlclose(pLibb);
	

3.2. Carga Implicita, usando Lista Enlazada

El código de este ejemplo se puede encontrar en carga_implicita.


3.2.1. Makefile

En las opciones de complilación, se añaden las opciones: -fPIC compilación independiente de la posición, y -rdynamic equivale a la opcion -Wl,--export-dynamic que evita tener que cargar los símbolos de las librerías.

	  CFLAGS=-Wall -ggdb -Idira -fPIC
	  LDFLAGS=-Ldira  -rdynamic
	

3.2.2. Código de las Librerías

Cuando la librería es llamada por primera vez deberá ser cargada y registrada en la lísta, por eso se incluye en las librerías el siguiente código.

	  static void init() __attribute__((constructor));
	  
	  static void init() {
	  register_plugin("b", &b);
	  }
	

3.2.3. Código del Programa Principal

Creamos una lista enlazada donde se irán almacenando los plugins que vayamos usando. El código en main.c

struct plugin {
  char* key;
  void (*f)(int);
  struct plugin* next;
};

static struct plugin* protos;	    
	

Habrá tres funciones para tratar la lísta enlazada: register_plugin, unregister_plugin y busca. Los nombres de las funciones son descriptivos de su funcionamiento.

void
register_plugin (char* key, void (*f)(int))
{
  struct plugin* p = (struct plugin*) malloc(sizeof(struct plugin));
  p->next = protos;
  p->f = f;
  p->key = key;
  protos = p;
  printf("** Plugin '%s' registrado con éxito.\n", key);
}

void
unregister_plugin (char* key)
{
  struct plugin *prev = NULL, *p = protos;
  while (p) {
    if (0==strcmp(p->key, key)) break;
    prev = p;
    p = p->next;
  }
  if (p) {
    if (prev) prev->next = p->next;
    else protos = p->next;
    free(p);
  }
}

static struct plugin*
busca(char* key)
{
  struct plugin *p = protos;
  while (p) {
    if (0==strcmp(p->key, key)) break;
    p = p->next;
  }
  return p;
}

La función f() es la que realmente hace la llamada al símbolo (o función) del plugin.

void
f (char* key, int i)
{
  struct plugin* p;
  p = busca(key);
  if (!p) {
    char libname[PATH_MAX];
    char path[PATH_MAX];

    sprintf(libname, "%s/dir%s/lib%s.so", getcwd(path, PATH_MAX), key, key);
    printf("Intentando cargar '%s'.\n", libname);
    dlopen(libname, RTLD_LAZY);

    p = busca(key);
  }
  if (p) p->f(i);
  else 
    fprintf(stderr, "ERROR: El plugin '%s' no está disponible.\n", key);
}
	

Llamada a la función del plugin:

 f("b",i);
	

4. Plugin usando GModule


4.1. Carga Explicita, usando g_module_symbol()

El código de este ejemplo se puede encontrar en carga_explicita.


4.1.1. Makefile

Los únicos cambios en el Makefile son los relacionados con la librería glib. Dentro de glib podemos encontrar gmodule que sirve para hacer carga dinámica 'plug-ins'.

CFLAGS=-Wall -ggdb -Idira `pkg-config --cflags glib-2.0` `pkg-config
	    --cflags gmodule-2.0`
LDLIBS=-la -ldl `pkg-config --libs gmodule-2.0` `pkg-config --libs glib-2.0`	    
	

4.1.2. Código de la Librería

No tiene ningún cambio significativo.


4.1.3. Código del Programa Principal

A destacar g_module_open para cargar la librería, y g_module_symbol para obtener la referencia al simbolo del plugin. El código del programa principal en main.c

pmodule = g_module_open("./dirb/libb.so.1.0.0",G_MODULE_BIND_LAZY);       
...
...
...
g_module_symbol(pmodule,"b", (gpointer*)&pFuncion);
	

Liberar recursos:

	    g_module_close(pmodule);
	  

4.2. Carga Implicita, usando Tabla Hash

El código de este ejemplo se puede encontrar en carga_implicita.


4.2.1. Makefile

La diferencia en el Makefile sólo tiene que ver con glib, por tanto los cambios son los mismos del ejemplo anterior.


4.2.3. Código del Programa Principal

El código en main.c

Cabecera para el manejo de tablas Hash

#include <glib/ghash.h>	 
	

Creación de una tabla para almacenar los plugins, y funciones para registra o des-registrar plugins usando la tabla Hash.

static GHashTable* plugin = NULL;

void
register_plugin (char* key, void (*f)(int))
{
 if (plugin == NULL)
    plugin = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
 g_hash_table_insert(plugin,key,f);
 printf("** Plugin '%s' registrado con éxito.\n", key);
}

void
unregister_plugin (char* key)
{
  if (plugin != NULL)
    g_hash_table_remove(plugin,key);
  
}	    
	

Esta función es la que llama al símbolo del plugin.

void
f (char* key, int i)
{
  GModule* pmodule;
  void (*p)(int);

  if (plugin == NULL)
    plugin = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);

  p = g_hash_table_lookup(plugin, key);
  if (!p) {
    char libname[PATH_MAX];
    char path[PATH_MAX];
   
    sprintf(libname, "%s/dir%s/lib%s.so", getcwd(path, PATH_MAX), key, key);
    printf("Intentando cargar '%s'.\n", libname);
    if((pmodule = g_module_open(libname,G_MODULE_BIND_LAZY))==NULL){
      g_warning("No se pudo cargar la librería %s", libname);
      exit(1);
    }

    p = g_hash_table_lookup(plugin, key);
  }
  if (p) p(i);
  else
     fprintf(stderr, "ERROR: El plugin '%s' no está disponible.\n", key);
}
	

Referencias

[1] Escuela Superior de Informática, Aplicaciones de desarrollo en GNU/Linux, 2004.

[2] Plugins, Repositorio publico del grupo ARCO, 2004.

[3] Librerías, mini-doc del grupo ARCO, 2003.