ZeroC Ice: Desarrollo de plugins

IceArco

Añadir nueva funcionalidad a ZeroC Ice es fácil: usando plugins. Ice provee una interfaz local, Ice::Plugin, que podemos usar para crearlos. Veamos un ejemplo sencillo. Probado en Ice 3.3.

Puesto que un plugin no es lo primero que se suele hacer al trabajar con Ice, deduzco que ya tienes experiencia, y que tienes Ice instalado correctamente, las herramientas de compilación necesarias, etc.

Interfaz

En primer lugar, veamos la interfaz que tenemos que implementar. En Debian, se encuentra en el fichero /usr/share/slice/Ice/Plugin.ice

module Ice {
    local interface Plugin {
        void initialize();
        void destroy();
   };
};

Sencilla, ¿verdad? Bien, pues lo primero que podemos hacer es implementar esos dos métodos. Como no es una interfaz RMI, no es necesario usar los translators, así que la implementación es directa. Veamos el código:

PluginI.h

// -*- mode: c++; coding: utf-8 -*-
 
#ifndef PLUGIN_H
#define PLUGIN_H
 
#include <Ice/Ice.h>
 
class PluginI : public Ice::Plugin {
public:
 
  void initialize();
  void destroy();
 
};
 
#endif // PLUGIN_H

PluginI.cpp

// -*- mode: c++; coding: utf-8 -*-
 
#include <iostream>
#include "PluginI.h"
 
using namespace std;
 
void
PluginI::initialize() {
  cout << "Ha sido cargado el plugin más tonto del mundo." << endl;
}
 
void
PluginI::destroy() {
  cout << "Ha sido descargado el plugin más tonto del mundo." << endl;
}

Por supuesto, puedes cambiar el nombre de la clase y el contenido… Eye-wink Como ves, tenemos dos métodos, initialize y destroy. Quizá te preguntes porqué no usar el constructor y el destructor. Sencillo: para resolver problemas de dependencias entre los plugins, Ice los carga todos en primero lugar y luego los inicializa. Así, si necesitas usar otro plugin, en el initialize sabes que ya estará cargado (pero puede no estar inicializado. Si necesitas un orden de carga determinado, puedes especificarlo en la configuración. Para más detalles, véase el manual).

Bien, ya tenemos el plugin propiamente dicho. El objeto puede implementar más métodos, y usando el PluginManager, puedes obtener una instancia para usarlo cuando lo necesites. Pero ¿cómo lo crea el PluginManager? Pues esa es nuestra siguiente tarea. Tenemos que definir una función que se encargue de instanciarlo, y que pueda ser llamada desde la factoría. He aquí una muestra:

Loader.cpp

// -*- mode: c++; coding: utf-8 -*-
 
#include <Ice/Ice.h>
#include "PluginI.h"
 
using namespace std;
 
extern "C"
{
 
ICE_DECLSPEC_EXPORT Ice::Plugin*
createPluginSample(const Ice::CommunicatorPtr& communicator,
                   const string& name,
                   const Ice::StringSeq& args) {
 
    cout << "Se va a cargar un plugin llamado: '" << name << "'" << endl;
    return new PluginI();
}
 
}

Como ves, la definición debe ser en C, no en C++. Se recomienda en el manual usar la macro ICE_DECLSPEC_EXPORT para dotarlo de compatibilidad entre plataformas. Como es C, no tenemos smart pointer, por lo que retornamos un puntero. El nombre de la función puede ser cualquiera, pues luego debemos especificarlo en el fichero de configuración.

¡Y ya lo tenemos! Esta es la estructura mínima de un plugin (a mi entender). Vamos a probarlo con un ejemplo muy sencillo:

test.cpp

// -*- mode: c++; coding: utf-8 -*-
 
#include <Ice/Ice.h>
 
using namespace std;
 
class Test : public Ice::Application {
public:
    int run(int argc, char** argv) {
        cout << "Aplicación de ejemplo que usa el plugin..." << endl;
    }
};
 
int
main(int argc, char** argv) {
    Test().main(argc, argv, "config");
}

¡Andando! Pero… ¿donde se usa el plugin? Pues bien, en este ejemplo, que sólo pretende demostrar la carga y descarga del mismo, no se usa Sticking out tongue Pero nos sirve igualmente. Si añadimos una línea en el fichero de configuración tal que así:

config

# -*- mode: conf; coding: utf-8 -*-
Ice.Plugin.Sample=IcePluginSample:createPluginSample

cargará nuestro plugin, y lo registrará en el PluginManager, disponible para que lo usemos cuando queramos (tip: el communicator tiene un método llamado getPluginManager…). El nombre del plugin, en este caso Sample, puedes elegirlo tu… Laughing out loud.

Compilando

Para que todo esto funcione, tienes que compilarlo Sticking out tongue. El plugin debe ser una librería, para no tener que recompilar Ice cada vez que añadas uno. Los flags de compilación son los de siempre: -fPIC -shared. Un detalle a tener en cuenta es el nombre: debe terminar en .33, que es la versión de Ice para la que enlazas, así que tienes que crearte un enlace simbólico para enlazar correctamente… Para ayudarte a compilar el ejemplo, he te aquí un Makefile:

Makefile

# -*- mode: makefile-gmake; coding: utf-8 -*-
 
CXX     = g++
LDFLAGS = -L.
 
OBJ     = test
LIB     = libIcePluginSample.so.33
 
 
all: $(LIB) $(OBJ)
        @echo -e "\nProbando...\n"
        @LD_LIBRARY_PATH=. ./test
 
$(OBJ): LDLIBS = -lIce -lIcePluginSample
$(OBJ): $(OBJ:=.cpp)
 
$(LIB): Loader.cpp PluginI.cpp
        $(CXX) -o $@ -fPIC -shared $^
        ln -s $@ $(LIB:%.33=%)
 
 
.PHONY: clean
clean:
        $(RM) -vrf *~ *.o *.so *.so.* $(OBJ)

En la sección de descargas tienes todo el código, listo para funcionar. Si lo compilas y lo lanzas, podrás ver algo como esto:

Se va a cargar un plugin llamado: 'Sample'
Ha sido cargado el plugin más tonto del mundo.
Aplicación de ejemplo que usa el plugin...
Ha sido descargado el plugin más tonto del mundo.

Y listo, ya tienes tu plugin funcionando. ¡Que te aproveche! Próxima parada, un endpoint… Sticking out tongue

Referencias

Descargas