Here is an alternate approach.
The application itself exports one or more plugin item registration functions. For example:
int register_plugin_item(const char *const text,
const char *const icon,
void (*enter)(void *),
void (*click)(void *),
void (*leave)(void *),
void *data);
Per registered item, there are two string slots (text
and icon
), three function slots (enter
, click
, and leave
), and an opaque reference that is given to the functions as a parameter when called.
(Note that you'll need to use the -rdynamic
compiler option when compiling the main application (the object file implementing the above function), to make sure the linker adds the register_plugin_item
symbol to the dynamic symbol table.)
Each plugin calls the register_plugin_item()
function for each of the items it wants, in a constructor function (that is automatically run at library load time). It is possible, and often useful, for the function to first examine the environment it runs in to determine which features to register, or which optimized variants of functions to use for each plugin item.
Here is a trivial example plugin. Note how all the symbols are static
, so that the plugin does not pollute the dynamic symbol table, or cause any symbol conflicts.
#include <stdlib.h>
#include <stdio.h>
extern int register_plugin_item(const char *const,
const char *const,
void (*enter)(void *),
void (*click)(void *),
void (*leave)(void *),
void *);
static void enter(void *msg)
{
fprintf(stderr, "Plugin: Enter '%s'\n", (char *)msg);
}
static void leave(void *msg)
{
fprintf(stderr, "Plugin: Leave '%s'\n", (char *)msg);
}
static void click(void *msg)
{
fprintf(stderr, "Plugin: Click '%s'\n", (char *)msg);
}
static void init(void) __attribute__((constructor));
static void init(void)
{
register_plugin_item("one", "icon-one.gif",
enter, leave, click,
"1");
register_plugin_item("two", "icon-two.gif",
enter, leave, click,
"2");
}
The above plugin exports two items. For testing, create at least a couple of variants of the above; you will see that there are no symbol conflicts even if the plugins use same (static) variables and function names.
Here is an example application that loads the specified plugins, and tests each registered item:
#include <stdlib.h>
#include <dlfcn.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>
struct item {
struct item *next;
const char *text;
const char *icon;
void *data;
void (*enter)(void *);
void (*leave)(void *);
void (*click)(void *);
};
static struct item *list = NULL;
int register_plugin_item(const char *const text,
const char *const icon,
void (*enter)(void *),
void (*click)(void *),
void (*leave)(void *),
void *data)
{
struct item *curr;
curr = malloc(sizeof *curr);
if (!curr)
return ENOMEM;
curr->text = text;
curr->icon = icon;
curr->data = data;
curr->enter = enter;
curr->leave = leave;
curr->click = click;
/* Prepend to list */
curr->next = list;
list = curr;
return 0;
}
int main(int argc, char *argv[])
{
int arg;
void *handle;
struct item *curr;
if (argc < 2 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
fprintf(stderr, "\n");
fprintf(stderr, "Usage: %s [ -h | --help ]\n", argv[0]);
fprintf(stderr, " %s PLUGIN.so ... \n", argv[0]);
fprintf(stderr, "\n");
fprintf(stderr, "Please supply full plugin paths, unless\n");
fprintf(stderr, "the plugins reside in a standard library directory,\n");
fprintf(stderr, "or in a directory listed in LD_LIBRARY_PATH.\n");
fprintf(stderr, "\n");
return 1;
}
for (arg = 1; arg < argc; arg++) {
handle = dlopen(argv[arg], RTLD_NOW);
if (handle != NULL)
fprintf(stderr, "%s: Loaded.\n", argv[arg]);
else
fprintf(stderr, "%s.\n", dlerror());
/* Note: We deliberately "leak" the handle,
* so that the plugin is not unloaded. */
}
for (curr = list; curr != NULL; curr = curr->next) {
if (curr->text)
printf("Item '%s':\n", curr->text);
else
printf("Unnamed item:\n");
if (curr->icon)
printf("\tIcon is '%s'\n", curr->icon);
else
printf("\tNo icon\n");
if (curr->data)
printf("\tCustom data at %p\n", curr->data);
else
printf("\tNo custom data\n");
if (curr->enter)
printf("\tEnter handler at %p\n", curr->enter);
else
printf("\tNo enter handler\n");
if (curr->click)
printf("\tClick handler at %p\n", curr->click);
else
printf("\tNo click handler\n");
if (curr->leave)
printf("\tLeave handler at %p\n", curr->leave);
else
printf("\tNo leave handler\n");
if (curr->enter || curr->click || curr->leave) {
printf("\tTest calls:\n");
if (curr->enter)
curr->enter(curr->data);
if (curr->click)
curr->click(curr->data);
if (curr->leave)
curr->leave(curr->data);
printf("\tTest calls done.\n");
}
}
return 0;
}
If the application is app.c
, and you have plugins plugin-foo.c
and plugin-bar.c
, you can compile them using e.g.
gcc -W -Wall -rdynamic app.c -ldl -o app
gcc -W -Wall -fpic -c plugin-foo.c
gcc -shared -Wl,-soname,plugin-foo.so plugin-foo.o -o plugin-foo.so
gcc -W -Wall -fpic -c plugin-bar.c
gcc -shared -Wl,-soname,plugin-bar.so plugin-bar.o -o plugin-bar.so
and run using e.g.
./app --help
./app ./plugin-foo.so
./app ./plugin-foo.so ./plugin-bar.so
Note that if the same plugin is defined more than once, the constructor is only executed once for that library. There will be no duplicate registrations.
The interface between the plugins and the application is completely up to you. In this example, there is only one function. A real application would probably have more. The application can also export other functions, for example for the plugin to query application configuration.
Designing a good interface is a whole different topic, and definitely deserves at least as much thought as you put in the implementation.
The Plux.NET plugin platform allows plugins to export their own slots, too. This alternate approach allows that in many ways. One of them is to export a plugin registration function -- that is, for registering plugins instead of individual items -- that takes a function pointer:
int register_plugin(const char *const name,
int (*extend)(const char *const, ...));
If the plugin provides slots, it provides its own registration function as the extend
function pointer. The application also exports a function, for example
int plugin_extend(const char *const name, ...);
that the plugins can use to call other plugins' registration functions. (The implementation of plugin_extend()
in the main application involves searching for a suitable extend
function already registered, then calling it/them.)
Implementation-wise, allowing plugins to export slots complicates implementation quite a bit. In particular, when and in which order should the slots exported by the plugins become available? Is there a specific order in which plugins must be loaded, to make sure all possible slots are exported? What happens if there is a circular dependency? Should plugins specify which other plugins they rely on before the registrations commence?
If each plugin is a separate entity that does not export any slots of its own, only plugs into main application slots, you avoid most of the complexity in the implementation.
The order in which registered items are examined is a detail you probably need to think about, though. The above example program uses a linked list, in which the items end up in reverse order compared to the registration order, and registration order is the same as the order in which the plugin file names are first specified on the command line. If you have a plugin directory, which is automatically scanned (using e.g. opendir()
/readdir()
/dlopen()
/closedir()
loop), then the plugin registration order is semi-random (depending on the filesystem; usually changing only when plugins are added or removed).
Corrections? Questions? Comments?
__attribute__((constructor))
, which executes a function exported by the application, to register the features provided by the plugin. This way you don't need any duplicate symbols. I can post an example if you want. Although it is not an answer to your question, it would solve the problem you are having. – Corneliuscornell