Publi

Enlazado dinámico en C++ (dynamic linking) IV: Permitir plug-ins en nuestra aplicación

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

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.

Y listo para usar!

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

También podría interesarte...

Only 1 comment left Ir a comentario

  1. Pingback: Bitacoras.com /

Leave a Reply