Posts
Search
Contact
Cookies
About
RSS

Dynamic loading of functions

Added 20 Jan 2019, 12:46 a.m. edited 18 Jun 2023, 1:12 a.m.

Now why you might ask would you want to dynamically load functions ? Well if you want to implement some kind of plugin system it's kind of useful, while I've previously relied on glibs gmodule system I decided that it might an idea to see if Linux's dlopen functionality was any simpler.

Lets take a look at a very trivial library:

include <stdio.h>
void hello() {
printf("hello ");
}
int world(int i) {
printf("world\n");
return i*2;
}

We have a simple function and one with a parameter and a return value.

It's useful to make some prototypes for these functions

typedef void (hello_t)(); 
typedef int (world_t)(int);

Compiling our library is simple the only caveat is that the code must compiled as position independent code a simple Make rule will accomplish this for us.

hello.so: hello.c
gcc -shared -fPIC -o hello.so hello.c

No great mystery there is nothing special about a library really... Actually using the library isn't really too complicated either.

 #include <dlfcn.h>
#include <stdio.h>
#include "main.h"

int main()
{
void* handle = dlopen("./hello.so", RTLD_LAZY);
if (!handle) {
printf("Cannot open library: %s\n",dlerror());
return 1;
}

hello_t hello = (hello_t) dlsym(handle, "hello");
if (!hello) {
printf("Cannot load symbol 'hello': %s\n", dlerror());
dlclose(handle);
return 1;
}

world_t world = (world_t) dlsym(handle, "world");
if (!world) {
printf("Cannot load symbol 'world': %s\n", dlerror());
dlclose(handle);
return 1;
}

hello();
printf("world passed 2 returned %i\n",world(2));

dlclose(handle);
return 0;
}

Once we have a handle to the actual library (hello.so) we can lookup the functions we want. Having the convenience of our function types means that loading function pointer is nice and readable. As we're using a C compiler we don't have to worry about C++ issues like name mangling. Of course once finished with the library its good practice to close the library handle.

It's definitely worth looking at the gmodule (glib) alternative as this brings to the table cross platform compatibility, something a good programmer should pay attention to, and frankly there is really no added complexity

 #include <stdio.h>
#include "main.h"
#include <gmodule.h>

GModule *module;

int main() {
module = g_module_open ("./hello.so", G_MODULE_BIND_LAZY);
if (!module) {
printf("error: %s", g_module_error());
return -1;
}

hello_t hello;
if (!g_module_symbol (module, "hello", (gpointer *)&hello))
{
printf("error: %s", g_module_error());
return -1;
}

world_t world;
if (!g_module_symbol (module, "world", (gpointer *)&world))
{
printf("error: %s", g_module_error());
return -1;
}

hello();
printf("world passed 2 returned %i\n",world(2));

g_module_close(module);
}

There is in essence very little difference accept for the make rule, as depending on your environment glib could be in a number of locations, pkg-config is very useful indeed.

 main2: main2.c
gcc `pkg-config --cflags gmodule-2.0` main2.c -o main2 -lgmodule-2.0

notice the use of the back tick operator, if you're going to use this more than once, then you might be better assigning it to a variable for the sake of convenience and readability.

From here all that's really needed to implement a plugin system is some kind of structure to hold individual "plugin" handles and their function pointers. As each plugin has its own module handle then they can each have a uniform set of identical function names and pointers. You might find that parsing your applications settings from XML ends up being more complicated than loading up a set of plugins...