Un ejemplo práctico de uso del enlazado dinámico es la posibilidad de crear plug-ins para nuestros programas. Estos plug-ins nos permitirán aumentar las funcionalidades de nuestro programa sin necesidad de recompilarlo, utilizando el código de añadidos binarios. Para poder «hablar» con los demás binarios, es necesario haber establecido antes unas normas para ese diálogo.
Intentaré poner un ejemplo más o menos completo, pero sencillo, y se podrá descargar el código fuente al final del artículo (serán bastantes archivos).
Vamos a diseñar un programa cuya misión (además de pedir información a los plug-ins) será hacer dibujos ASCII en pantalla).
Para el diálogo con los plugins vamos a tener en cuenta:
- Lectura básica de información del plug-in
- Lectura avanzada de información del plug-in
- Inicialización
- Realización de una tarea principal
- Destrucción
Tabla de contenidos
Diseñando el plug-in
Tenemos que cubrir todos los frentes, y para ello, cada plugin exportará una serie de funciones:
- basicInfo(): Entregará información básica del plugin
- advancedInfo(): Entregará información avanzada del plugin
- initialize(): Inicializa el plugin
- action(): Realiza la acción para la que el plugin fue construido
- destroy(): Destruye el plugin
Lectura básica de información del plug-in
La idea de separar la lectura básica de información de la lectura avanzada, es por la información que nos puede llegar a dar el plug-in; es decir, puede que para la primera versión, hayamos pensado que debemos dar una cantidad de información, pero para la segunda versión, tengamos que dar más información aún. Vamos a dejar nuestro proyecto preparado para eso. La lectura de información será igual en el primer o el segundo caso, pero en el primer caso, vamos a obtener un conjunto de información que dejaremos fijo (hasta el fin del mundo), y en el segundo caso, podemos variar la cantidad de información dependiendo de la versión, en este segundo caso, si nuestro programa principal soporta varias versiones de plugins, tiene que saber hablar con todas.
Para esto, vamos a confeccionar un tipo de dato con la información:
1 2 3 4 5 6 | struct PluginBasicInfo { std::string name; float plugin_version; /* Own plugin version */ int client_version; /* Plugin version system needed */ }; |
Esto será lo que nuestro plugin tiene que aportar cuando lo cargamos, a través de la función basicInfo();
Lectura avanzada de información del plugin
Como nuestro programa tiene una visión de futuro, nos permitirá que en versiones venideras podamos ampliar la información si lo vemos conveniente. Utilizaremos la función advancedInfo(), pero para los plugins de versión 1 esta información será más pequeña que para los plugins de versión 2, por lo que tenemos que tener cuidado de no equivocarnos. La versión que nos interesa es la client_version, es decir, la versión del cliente: un plugin puede ir por la versión 10, pero utilizar el cliente de la versión 1…
Para ello, la versión 1 exportará:
1 2 3 4 5 | struct PluginVersion1Info { std::string description; std::string author; }; |
Y la versión 2 exportará:
1 2 3 4 5 6 7 | struct PluginVersion2Info { std::string description; std::string author; std::string url; std::string date; }; |
Todo ello lo separaremos en varios archivos .h (ya que esto es un proceso de un tiempo, entre versiones), la versión 1 salió al principio, pero la version 2, salió hace poco. Los plugins de la versión 1 utilizarán la primera estructura y los de la versión 2 la segunda, pero como salida de la misma función advancedInfo().
Inicialización del plugin
En este ejemplo, los plugins, no harán nada para inicializarse, pero tal vez en nuestros proyectos, deban abrir archivos, reservar memoria, o intercambiar alguna información previa con el cliente, por eso es interesante incluir esta función aunque la dejemos vacía. En este caso sólo mostraré mensjaes de depuración. Será la función initialize()
Realización de la tarea principal
En este ejemplo, los plugins, devolverán en un string, un dibujo ASCII, pero cada plugin puede devolver varios dibujos de la misma categoría en función del argumento recibido. Lo hacemos a través de la función action(). En un programa más complejo, tal vez los plugins tengan varias acciones a realizar y varias condiciones en las que pueden ser realizadas, eso ya depende de nosotros.
Destrucción del plugin
Cuando descargamos el plugin, tal vez, éste necesite hacer lo contrario que en la inicialización, es decir, cerrar ficheros, liberar memoria, etc. Aunque en este caso no hacemos nada, es interesante dejar esta puerta abierta. Será la función destroy(). También podíamos haber llamado a la función atexit() en la inicialización, si por ejemplo el sistema para el que estamos desarrollando no tiene la función destroy(). atexit() llamará a la función argumento cuando termine la ejecución del programa, o en el caso de objetos compartidos, cuando éste se descargue.
Código fuente
Como siempre me gusta poner el código fuente en la web, para un vistazo rápido.
basicplugin.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | #ifndef _BASICPLUGIN_H #define _BASICPLUGIN_H 1 #include <string> struct PluginBasicInfo { std::string name; float plugin_version; /* Own plugin version */ int client_version; /* Plugin version system needed */ }; typedef void (*getPluginBasicInfo)(PluginBasicInfo*); typedef void (*initializePlugin)(); typedef void (*destroyPlugin)(); typedef std::string (*pluginAction)(std::string argument); #endif /* _BASICPLUGIN_H */ |
pluginversion1.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | #ifndef _PLUGINVERSION1_H #define _PLUGINVERSION1_H 1 #include <string> struct PluginVersion1Info { std::string description; std::string author; }; typedef void (*getPluginVersion1Info)(PluginVersion1Info*); #endif /* _PLUGINVERSION1_H */ |
pluginversion2.h
1 2 3 4 5 6 7 8 9 10 11 12 13 | #include <string> struct PluginVersion2Info { std::string description; std::string author; std::string url; std::string date; }; typedef void (*getPluginVersion2Info)(PluginVersion2Info*); #endif /* _PLUGINVERSION2_H */ |
Ahora presento el código fuente de los tres plugins de prueba, dos serán válidos (uno dibuja vacas, y el otro dibuja Homers), el tercero exportará una versión más nueva que la admitida por el cliente, por lo que debe lanzar un error al cargar.
vacas.cpp
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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 | #include "basicplugin.h" #include "pluginversion1.h" #include <string> #include <iostream> #include <cstdlib> using namespace std; extern "C" { void basicInfo(PluginBasicInfo* info) { info->name = "vacas"; info->plugin_version=1.0; info->client_version=1; } void advancedInfo(PluginVersion1Info *info) { info->description="Just draw ASCII cows on screen"; info->author="Gaspar Fernández"; } void initialize() { cout << "Initializing cows plugin..."<<endl; } void destroy() { cout << "Destroying cows plugin..."<<endl; } string displayVaca1() { return " (__)\n (oo)\n /-------\\/\n / | ||\n* ||----||\n ^^ ^^"; } string displayVaca2() { return " (__)\n (\\/)\n /-------\\/\n / | 666 ||\n* ||----||\n ^^ ^^"; } string displayVaca3() { return " (__) ^\n (oo) /\n _____\\/___/\n / /\\ / /\n ^ / * /\n / ___/\n*----/\\\n / \\\n / /\n ^ ^\n"; } string action(string argument) { int number = atoi(argument.c_str()); switch (number) { case 1: return displayVaca1(); break; case 2: return displayVaca2(); break; case 3: return displayVaca3(); break; default: return "Vaca not found"; } } } |
homer.cpp
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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 | #include "basicplugin.h" #include "pluginversion2.h" #include <string> #include <iostream> #include <cstdlib> using namespace std; extern "C" { void basicInfo(PluginBasicInfo* info) { info->name = "homer"; info->plugin_version=1.0; info->client_version=2; } void advancedInfo(PluginVersion2Info *info) { info->description="Several sizes homers on the screen"; info->author="Gaspar Fernández"; info->url="https://poesiabinaria.net/"; info->date="2013-03-03"; } void initialize() { cout << "Initializing homer plugin..."<<endl; } void destroy() { cout << "Destroying homer plugin..."<<endl; } string displayHomer1() { return " /X \\\n" " _------_\n" " / \\\n" " | |\n" " | |\n" " | __ __)\n" " | / \\/ \\\n" "/\\/\\ (o )o )\n" "/c \\__/ --.\n" "\\_ _-------'\n" " | / \\\n" " | | '\\_______)\n" " | \\_____)\n" " |_____ |\n" "|_____/\\/\\\n" "/ \\\n"; } string displayHomer2() { return " .'/,-Y" "~-.\n" " l.Y ^.\n" " /\\ _\\\n" "i ___/" "\\\n" "| /" "\\ o !\n" "l ] o !__./\n" " \\ _ _ \\.___./ "~\\\n" " X \\/ \\ ___./\n" " ( \\ ___. _..--~~" ~`-.\n" " ` Z,-- / \\\n" " \\__. ( / ______)\n" " \\ l /-----~~" /\n" " Y \\ /\n" " | "x______.^\n" " | \\\n" " j Y\n"; } string displayHomer3() { return " ___\n" " _//_\\\\\n" " ," //".\n" " / \\\n" " _/ |\n" " (.-,--. |\n" " /o/ o \\ /\n" " \\_\\ / /\\/\\\n" " (__`--' ._)\n" " / `-. |\n" " ( ,`-. |\n" " `-,--\\_ ) |-.\n" " _`.__.' ,-' \\\n" " |\\ ) _.-' |\n" " i-\\.'\\ ,--+.\n" " .' .' \\,-'/ \\\n" " / / / \\\n" " 7_| | |\n" " |/ "i.___.j"\n" " / | |\\\n" " / | | \\\n" " / | | |\n" " | | | |\n" " |____ | |-i'\n" " | """"----""| | |\n" " \\ ,-' |/\n" " `. `-, |\n" " |`-._ / /| |\\ \\\n" " | `-. `' | ||`-'\n" " | | `-'|\n" " | | |\n" " | | |\n" " | | |\n" " | | |\n" " | | |\n" " | | |\n" " )`-.___| |\n" " .'`-.____)`-.___.-'(\n" " .' .'-._____.-i\n" "/ .' |\n" "`-------/ . |\n" " `--------' "--'\n"; } string action(string argument) { int number = atoi(argument.c_str()); switch (number) { case 1: return displayHomer1(); break; case 2: return displayHomer2(); break; case 3: return displayHomer3(); break; default: return "Homer not found"; } } } |
newer.cpp
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 | #include "basicplugin.h" #include "pluginversion2.h" #include <string> #include <iostream> #include <cstdlib> using namespace std; extern "C" { void basicInfo(PluginBasicInfo* info) { info->name = "newer"; info->plugin_version=1.0; info->client_version=3; } void advancedInfo(PluginVersion2Info *info) { info->description="It's a plugin newer than the client"; info->author="Gaspar Fernández"; info->url="https://poesiabinaria.net/"; info->date="2013-03-03"; } void initialize() { cout << "Initializing newer plugin..."<<endl; } void destroy() { cout << "Destroying newer plugin..."<<endl; } string action(string argument) { return "Hello world"; } } |
Todos estos archivos irán en un directorio llamado plugins, dentro del directorio de nuestro programa.
Para compilar cada uno de ellos hacemos lo siguiente:
$ g++ -fPIC -shared -o newer.so newer.cpp
$ g++ -fPIC -shared -o vacas.so vacas.cpp
$ g++ -fPIC -shared -o homer.so homer.cpp
Diseñando el cliente
Este será nuestro programa principal, que aceptará la inclusión de plugins, y hará llamadas a las funciones compartidas de los mismos. Para ello tenemos que tener en cuenta varios puntos:
- El programa principal no debe ser muy complicado
- Se deben buscar todos los plugins que existan dentro del directorio plugins/
- Cuando se encuentre un plugin, tenemos que pedir toda la información del mismo
- Antes de usar un plugin, lo tenemos que inicializar
- Cuando dejemos de usar un plugin, lo destruimos
Para ello, he creado una clase PluginManager, que se encargará de todo lo relacionado con esas tareas.
Obteniendo información y cargando los plugins
Esta es la parte más rara, ya que tenemos varias versiones de plugins, he creado esta estructura:
1 2 3 4 5 6 7 | struct Plugin { void *dlhandle; PluginBasicInfo info; void *moreinfo; bool initialized; }; |
En dlhandle, almacenaré el manejador de la biblioteca dinámica, en info, los datos extraidos de basicInfo(), en moreinfo (cuidado, que es otro void*), los datos de advancedInfo (como tenemos dos versiones, convertiremos el tipo en PluginVersion1Info o PluginVersion2Info según nos interese. Y en initialized guardaremos el estado de la inicialización; como los plugins se inicializarán la primera vez que se usen, tenemos que guardar dicho estado para no re-inicializar, asimismo, habrá plugins que no hayamos usado, y por tanto, no los hayamos inicializado, entonces al cerrar el programa, no debemos destruir dichos plugins.
Podríamos incluir otra variable booleana, loaded (o usar el mismo valor de dlhandle), si nuestros plugins van a ser muy pesados y no nos interesa tenerlos en memoria todo el tiempo, para descargarlos al vuelo cuando haga mucho tiempo que no se usan, etc.
Escaneo de plugins
Nada más empezar el programa, la clase PluginManager se encargará de buscar los plugins que encuentra y de añadirlos a un vector de para manejarlos más cómodamente. La estructura podrá variar si queremos optimizar un poco más, o la cantidad de plugins que tenemos es demasiado grande, o pequeña, o sólo vamos a insertar uno…
Para buscar los plugins, vamos a realizar un listado de los archivos .so que encntramos en el directorio plugins/, para eso sería interesante leer este artículo, lo demás es sencillo, puesto que es prácticamente un copia y pega de eso, un poco modificado para integrarlo en la clase PluginManager y añadir los datos en un vector de plugins.
Manejo de errores
Para los errores, se ha creado la clase PluginManagerException, es una clase derivada de exception, es muy simple, lo único que hace es admitir un código y un mensaje y, aunque hay clases de exception que hacen lo mismo, está bien tener delimitado el error como una PluginManagerException:
pluginmanagerexception.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | #ifndef _PLUGINMANAGEREXCEPTION_H #define _PLUGINMANAGEREXCEPTION_H 1 #include <string> #include <exception> using namespace std; class PluginManagerException : public exception { public: PluginManagerException(int code, string err); ~PluginManagerException() throw(); string what(); int getCode(); private: string error; int code; }; #endif /* _PLUGINMANAGEREXCEPTION_H */ |
pluginmanagerexception.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | #include "pluginmanagerexception.h" PluginManagerException::PluginManagerException(int code, string err): code(code), error(err) { } PluginManagerException::~PluginManagerException() throw() { } string PluginManagerException::what() { return error; } int PluginManagerException::getCode() { return code; } |
PluginManager
Aquí presento la clase PluginManager:
pluginmanager.h:
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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 | #ifndef _PLUGINMANAGER_H #define _PLUGINMANAGER_H 1 #include "plugins/basicplugin.h" #include "pluginmanagerexception.h" #include <string> #include <dirent.h> #include <vector> using namespace std; class PluginManager { public: PluginManager(string pluginDir); ~PluginManager(); /* Scan plugins in the plugins directory */ void pluginScan(); /* Lists loaded plugins */ string pluginList(); /* Get information about a plugin */ string getInfo(string plugin); /* Call a plugin with an argument */ string pluginCall(string plugin, string argument); /* Free plugins from memory */ void pluginFree(); private: struct Plugin { void *dlhandle; PluginBasicInfo info; void *moreinfo; bool initialized; }; string pluginDir; vector<Plugin> loadedPlugins; void pluginInit(Plugin &plugin); bool pluginDestroy(Plugin &plugin); void addPlugin(char *filename); /* Call a plugin */ string pluginCall(Plugin &plugin, string argument); /* Information related methods */ string getInfo(Plugin plugin); string getVersion1Info(Plugin plugin); string getVersion2Info(Plugin plugin); void pluginGetMoreInformation(Plugin &p); void pluginGetVersion1Information(Plugin &p); void pluginGetVersion2Information(Plugin &p); /* Maybe these two methods must be separated in another .h. They are so */ /* general purpose. */ char *getFullName(char *path, struct dirent *ent); char *getExtension(char *filename); }; #endif /* _PLUGINMANAGER_H */ |
pluginmanager.cpp:
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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 | #include "pluginmanager.h" #include "plugins/pluginversion1.h" #include "plugins/pluginversion2.h" // Including cstring for speed in dirent operations #include <cstring> #include <dlfcn.h> #include <cstdlib> #include <cstdio> #include <iostream> #include <sstream> PluginManager::PluginManager(string pluginDir): pluginDir(pluginDir) { pluginScan(); } PluginManager::~PluginManager() { pluginFree(); } // Gets filename full name char* PluginManager::getFullName(char *path, struct dirent *ent) { char *fullname; int tmp; tmp=strlen(path); fullname=(char*)malloc(tmp+strlen(ent->d_name)+2); /* Sumamos 2, por el \0 y la barra de directorios (/) no sabemos si falta */ if (path[tmp-1]=='/') sprintf(fullname,"%s%s", path, ent->d_name); else sprintf(fullname,"%s/%s", path, ent->d_name); return fullname; } // Scan and add plugins void PluginManager::pluginScan() { struct dirent *ent; char *path=(char*)pluginDir.c_str(); char *fullFileName; DIR *dir = opendir(path); pluginFree(); if (dir==NULL) throw new PluginManagerException(1, "Couldn't open plugin directory"); while ((ent = readdir (dir)) != NULL) { if ( (strcmp(ent->d_name, ".")!=0) && (strcmp(ent->d_name, "..")!=0) && (strcmp(this->getExtension(ent->d_name), ".so")==0) && (ent->d_type==DT_REG) ) { cout << "Opening plugin "<<ent->d_name<<"..."; fullFileName=this->getFullName(path, ent); try { addPlugin(fullFileName); cout << "OK"<<endl; } catch (PluginManagerException* e) { cout << "Failed loading plugin: "<<e->what()<<endl; } free(fullFileName); } } closedir (dir); } // Gets filename extension char* PluginManager::getExtension(char *filename) { return strrchr(filename, '.'); } // Gets version 1 plugin advanced information (function advancedInfo) void PluginManager::pluginGetVersion1Information(Plugin &p) { PluginVersion1Info *moreinfo; getPluginVersion1Info gai; gai=(getPluginVersion1Info)dlsym(p.dlhandle, "advancedInfo"); if (dlerror()!=NULL) throw new PluginManagerException(5, "Could not load advanced information"); moreinfo=new PluginVersion1Info; gai(moreinfo); p.moreinfo=(void*)moreinfo; } // Gets version 2 plugin advanced information (function advancedInfo) void PluginManager::pluginGetVersion2Information(Plugin &p) { PluginVersion2Info *moreinfo; getPluginVersion2Info gai; gai=(getPluginVersion2Info)dlsym(p.dlhandle, "advancedInfo"); if (dlerror()!=NULL) throw new PluginManagerException(5, "Could not load advanced information"); moreinfo=new PluginVersion2Info; gai(moreinfo); p.moreinfo=(void*)moreinfo; } // Gets information for version 1 or 2 plugins void PluginManager::pluginGetMoreInformation(Plugin &p) { switch (p.info.client_version) { case 1: this->pluginGetVersion1Information(p); break; case 2: this->pluginGetVersion2Information(p); break; default: throw new PluginManagerException(4, "Unknown plugin version"); } } // Adds a plugin to the list void PluginManager::addPlugin(char *filename) { Plugin info; getPluginBasicInfo gbi; info.dlhandle=dlopen(filename, RTLD_LAZY); if (info.dlhandle==NULL) throw new PluginManagerException(2, "Could not load file"); gbi=(getPluginBasicInfo)dlsym(info.dlhandle, "basicInfo"); if (dlerror()!=NULL) throw new PluginManagerException(3, "Could not load symbol"); gbi(&info.info); info.initialized=false; this->pluginGetMoreInformation(info); loadedPlugins.push_back(info); // dlclose(info.dlhandle); } // List plugins into a string string PluginManager::pluginList() { stringstream str; if (loadedPlugins.size()==0) return "No plugins to list"; for (unsigned int i=0; i<loadedPlugins.size(); i++) { str<<loadedPlugins[i].info.name<<". Version "; str<<loadedPlugins[i].info.plugin_version; str<<"\n"; } return str.str(); } // Free all plugins void PluginManager::pluginFree() { cout << "Freeing plugins"<<endl; for (unsigned int i=0; i<loadedPlugins.size(); i++) { cout << "Freeing "<<loadedPlugins[i].info.name<<"... "; if (loadedPlugins[i].initialized) this->pluginDestroy(loadedPlugins[i]); if (dlclose(loadedPlugins[i].dlhandle)!=0) cout << "Error "<<endl; else cout << "OK"<<endl; } loadedPlugins.clear(); } // Gets information of a plugin string PluginManager::getInfo(string plugin) { for (unsigned int i=0; i<loadedPlugins.size(); i++) { if (plugin==loadedPlugins[i].info.name) return getInfo(loadedPlugins[i]); } return "Plugin "+plugin+" not found."; } // Gets plugin version 1 information string PluginManager::getVersion1Info(Plugin plugin) { PluginVersion1Info *info=(PluginVersion1Info*)plugin.moreinfo; stringstream str; str<<"Description: "<<info->description<<endl; str<<"Author: "<<info->author<<endl; return str.str(); } // Gets plugin version 2 information string PluginManager::getVersion2Info(Plugin plugin) { PluginVersion2Info *info=(PluginVersion2Info*)plugin.moreinfo; stringstream str; str<<"Description: "<<info->description<<endl; str<<"Author: "<<info->author<<endl; str<<"URL: "<<info->url<<endl; str<<"Date: "<<info->date<<endl; return str.str(); } // Gets plugin information into a string string PluginManager::getInfo(Plugin plugin) { stringstream str; str<<"Name: "<<plugin.info.name<<endl; str<<"Version: "<<plugin.info.plugin_version<<endl; str<<"Client version: "<<plugin.info.client_version<<endl; switch (plugin.info.client_version) { case 1: str<<getVersion1Info(plugin); break; case 2: str<<getVersion2Info(plugin); break; default: str<<"Unknown version."<<endl; break; } return str.str(); } // Initialize plugin (funcion initialize) void PluginManager::pluginInit(Plugin &plugin) { initializePlugin pi; pi=(initializePlugin)dlsym(plugin.dlhandle, "initialize"); if (dlerror()!=NULL) throw new PluginManagerException(5, "Could not initialize plugin"); pi(); plugin.initialized=true; } // Destroy plugin (function destroy bool PluginManager::pluginDestroy(Plugin &plugin) { destroyPlugin pd; pd=(destroyPlugin)dlsym(plugin.dlhandle, "destroy"); if (dlerror()!=NULL) throw new PluginManagerException(5, "Could not destroy plugin"); pd(); plugin.initialized=false; return true; } string PluginManager::pluginCall(string plugin, string argument) { for (unsigned int i=0; i<loadedPlugins.size(); i++) { if (plugin==loadedPlugins[i].info.name) return pluginCall(loadedPlugins[i], argument); } return "Plugin "+plugin+" not found."; } string PluginManager::pluginCall(Plugin &plugin, string argument) { if (!plugin.initialized) pluginInit(plugin); pluginAction pa; pa=(pluginAction)dlsym(plugin.dlhandle, "action"); if (dlerror()!=NULL) throw new PluginManagerException(6, "Could not load action"); return pa(argument); } |
Como vemos, PluginManager se encargará de gestionar la carga dinámica, y los plugins de versión 1 y versión 2, en realidad es el único que se «pelea» con ellos, y por tanto, el programa principal, sólo tendrá que instanciar la clase y llamar a los métodos que aparecen.
El cliente
Hay algunas funciones de C, incluídas para facilitarnos un poco el trabajo, en concreto, ya que se trata de una línea de comandos, he incluido este ejemplo para separar las palabras que nos envía el usuario y facilitarnos la misión de trabajar con ellas.
Este cliente admitirá una serie de comandos por teclado:
- fin: termina el programa
- list: lista los plugins disponibles
- scan: Escanea de nuevo los plugins
- free: Libera los plugins
- info [plugin]: Muestra información sobre un plugin determinado
- Si no encuentra el comando, busca dentro de los nombres de los plugins
main.cpp
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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 | #include "pluginmanager.h" #include <string> #include <cstdlib> #include <iostream> #include <cstring> #define MAX_CADENA 100 #define MAX_ARGS 10 using namespace std; int extrae_argumentos(char *orig, char *delim, char args[][MAX_CADENA], int max_args) { char *tmp; int num=0; if (strlen(orig)==0) return 0; char *str = (char*)malloc(strlen(orig)+1); strcpy(str, orig); tmp=strtok(str, delim); do { if (num==max_args) return max_args+1; strcpy(args[num], tmp); num++; tmp=strtok(NULL, delim); } while (tmp!=NULL); return num; } int main(int argc, char *argv[]) { bool fin=false; PluginManager pm("plugins/"); string command; char args[MAX_ARGS][MAX_CADENA]; cout << "Introduzca comandos que serán interpretados:"<<endl<<endl; while (!fin) { cout << "> "; getline(cin, command); int nargs = extrae_argumentos((char*)command.c_str(), (char*)" ", args, MAX_ARGS); try { if (nargs>0) { if (strcmp("fin", args[0])==0) fin=true; else if (strcmp("list", args[0])==0) cout<< pm.pluginList()<<endl; else if (strcmp("scan", args[0])==0) pm.pluginScan(); else if (strcmp("info", args[0])==0) cout << pm.getInfo((string)args[1]) << endl; else if (strcmp("free", args[0])==0) pm.pluginFree(); else if (nargs>1) cout << pm.pluginCall((string)args[0], (string)args[1]) << endl; else cout << "Need at least one argument"<<endl; } } catch (PluginManagerException* e) { cout << "There was an error: "<<e->what()<<endl; } } return EXIT_SUCCESS; } |
Para compilar este programa principal, haremos lo siguiente:
g++ -o main main.cpp pluginmanager.cpp pluginmanagerexception.cpp -ldl
Muy importante incluir -ldl para que compile pluginmanager correctamente.
Propuestas de mejora
Como siempre, hay algunas cosas que podemos hacer para mejorar este sistema, que no deja de ser un ejemplo y por supuesto todos los mensajes que salen por pantalla de PluginManager y de los propios plugins no deben estar en un entorno de producción. Pero podríamos, fácilmente extender la funcionalidad de estos plugins si:
- Permitimos que un plugin, introduzca varios comandos a nuestro intérprete
- Permitimos un número variable de argumentos al plugin
- Ampliamos la información inicial, añadiendo información o ayuda sobre el comando
- Se nos puede ocurrir mil cosas más…
Descargar el código fuente
Todos los archivos que he puesto aquí los podéis descargar desde aquí (22kb)
Foto: chrstphre (Flickr) CC-A a 03/03/2013