Here is an alternative approach.
The application itself exports one or more functions for registering plug-in elements. For instance:
int register_plugin_item(const char *const text, const char *const icon, void (*enter)(void *), void (*click)(void *), void (*leave)(void *), void *data);
For a registered item, there are two slots ( text and icon ), three functional slots ( enter , click and leave ) and an opaque link provided to functions as a parameter when called.
(Note that you need to use the -rdynamic compiler -rdynamic when compiling the main application (an object file that implements the above function) to ensure that the linker adds the register_plugin_item character to the dynamic symbol table.)
Each plugin calls the register_plugin_item() function for each of the elements it wants in the constructor function (which automatically starts when the library loads). It is perhaps often useful for a function to first check the environment in which it works in order to determine which functions to register or which optimized options for functions to use for each element of the plugin.
Here is a trivial plugin example. Note that all symbols are static , so the plugin does not pollute the dynamic symbol table or cause 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 elements. 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 the same (static) variables and function names.
Here is an example application that downloads the specified plugins and validates 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, for example,
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 execute for example
./app
Please note that if the same plugin is defined more than once, the constructor is executed only once for this library. There will be no duplicate registrations.
The interface between the plugins and the application is completely up to you. There is only one function in this example. A real application is likely to have more. An application can also export other functions, for example, for a plugin for requesting application configuration.
Designing a good interface is a completely different topic and certainly deserves at least as much thought as you put into the implementation.
The Plux.NET plugin platform allows plugins to also export their own slots. This alternative approach allows this in many ways. One of them is to export the plugin registration function, that is, to register plugins instead of individual elements, which takes a pointer to a function:
int register_plugin(const char *const name, int (*extend)(const char *const, ...));
If the plugin provides slots, it provides its registration function as a pointer to the extend function. The application also exports a function, for example
int plugin_extend(const char *const name, ...);
which plugins can use to call other plugin registration functions. (The implementation of plugin_extend() in the main application includes finding the appropriate extend function already registered, and then calling it / them.)
An implementation that allows plugins to export slots makes the implementation quite complicated. In particular, when and in what order should the slots exported by the plugins be available? Is there a specific order in which plugins should be loaded to make sure that all possible slots are exported? What happens if there is a circular addiction? Should plugins indicate which other plugins they use before registration starts?
If each plugin is a separate object that does not export a single slot, it only connects to the main application slots, you avoid most of the implementation complexity.
The order in which registered items are registered is a part that you probably need to think about. The above sample program uses a linked list in which the items end in reverse order than the registration order, and the 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 that is automatically scanned (using, for example, the opendir() / readdir() / dlopen() / closedir() ), then the registration order of the plugin is semi-random (depending on the file system, usually changing only when plugins are added or removed).
Corrections? Any questions? Comments?