Consider these three files. First, jump.c
:
#include <stdio.h>
int jump(const double height)
{
fflush(stdout);
fprintf(stderr, "Jumping %.3g meters.\n", height);
fflush(stderr);
return 0;
}
Second, sit.c
:
#include <stdio.h>
int sit(void)
{
fflush(stdout);
fprintf(stderr, "Sitting down.\n");
fflush(stderr);
return 0;
}
Third, example.c
to use one or both of the above, depending on whether they (as libjump.so
or libsit.so
, respectively) exist in the current working directory:
#include <stdio.h>
#include <dlfcn.h>
static const char *jump_lib_path = "./libjump.so";
static int (*jump)(const double) = NULL;
static const char *sit_lib_path = "./libsit.so";
static int (*sit)(void) = NULL;
static void load_dynamic_libraries(void)
{
void *handle;
handle = dlopen(jump_lib_path, RTLD_NOW | RTLD_LOCAL);
if (handle) {
jump = dlsym(handle, "jump");
/* If no jump symbol, we don't need the library at all. */
if (!jump)
dlclose(handle);
}
handle = dlopen(sit_lib_path, RTLD_NOW | RTLD_LOCAL);
if (handle) {
sit = dlsym(handle, "sit");
/* If no sit symbol, the library is useless. */
if (!sit)
dlclose(handle);
}
}
int main(void)
{
int retval;
load_dynamic_libraries();
if (jump) {
printf("Calling 'jump(2.0)':\n");
retval = jump(2.0);
printf("Returned %d.\n\n", retval);
} else
printf("'jump()' is not available.\n\n");
if (sit) {
printf("Calling 'sit()':\n");
retval = sit();
printf("Returned %d.\n\n", retval);
} else
printf("'sit()' is not available.\n\n");
return 0;
}
Let's first compile and run the example program:
gcc -Wall -O2 example.c -ldl -o example
./example
The program outputs that neither jump() or sit() are available. Let's compile jump.c into a dynamic library, libjump.so, and then run the example again:
gcc -Wall -O2 -fPIC -shared jump.c -Wl,-soname,libjump.so -o libjump.so
./example
Now, the jump() function works. Let's compile sit.c, too, and run the example a final time:
gcc -Wall -O2 -fPIC -shared jump.c -Wl,-soname,libsit.so -o libsit.so
./example
Here, both functions get called, and everything just works.
In example.c
, jump
and sit
are function pointers. We initialize them to NULL, so that we can use if (jump)
to check if jump
points to a valid function.
The load_dynamic_libraries()
function uses dlopen()
and dlsym()
to obtain the function pointers. Note that if the dynamic library is opened successfully, and the necessary symbol is found, we do not dlclose()
it because we want to keep the dynamic library in memory. (We only dlclose()
it if it looks like it is not the kind of library we want.)
If you want to avoid the if (jump)
and if (sit)
clauses, you can use stubs like
int unsupported_jump(const double height)
{
return ENOTSUP;
}
int unsupported_sit(void)
{
return ENOTSUP;
}
and at the end of load_dynamic_libraries()
, divert the functions to the stubs instead of NULL pointers, i.e.
if (!jump)
jump = unsupported_jump;
if (!sit)
sit = unsupported_sit;
Note that function-like interfaces are easiest to use, because the function pointer acts as the effective prototype. If you need objects, I recommend using getter functions. Objects do work just fine, as long as you remember that dlsym()
returns a pointer to the object; using a getter function, that is explicit in the getter function pointer type.
Plug-in interfaces commonly have a single function (say, int properties(struct plugin *const props, const int version)
), which is used to populate a structure of function and object pointers. The application supplies the version of the structure it uses, and the plug-in function returns either success or failure, depending on whether it can populate the structure to accommodate that version.
As plug-ins are typically stored in a single directory (/usr/lib/yourapp/plugins/
is very common), you can trivially load all plugins by using opendir()
and readdir()
to scan the file names in the plug-in directory one by one, dlopen()
ing each one, obtaining the properties()
function pointer, and calling it to see what kinds of services the plugin provides; typically creating an array or a linked list of the plugin structures.
All of this is very, very simple and straightforward in Linux, as you can see. If you want a specific plug-in functionality example, I recommend you pose that as a separate question, with more details on what kind of functionality the interface should expose -- the exact data structures and function prototypes do depend very much on what kind of application we have at hand.
Questions? Comments?