-
Notifications
You must be signed in to change notification settings - Fork 3
Plugins in CPP: Dynamically Linking Shared Objects
Dynamically linking shared objects (*.so or *.dll) at run time, as opposed to compile time in C is easy with <dlfcn.h>, this has been discussed all over the web by others.
The technique works in C++ too however, with a caveat; ISO C++ forbids casting between pointer-to-function and pointer-to-object, a limitation the user will invariably encounter when trying to load a object from the dynamically loaded library (the plugin).
Whilst casting between pointer-to-function and pointer-to-object is not defined for ISO C++, it is defined for POSIX so user code will compile, albeit with a warning.
Here we will quickly cover three techniques for either suppressing the warning or avoiding it with function redeclarations or some appropriate function pointer castings (one of which is the POSIX.1-2003 Technical Corrigendum 1 suggested work-around).
Further, we will create a template template<class T> class Plugin for loading objects of type T from a dynamically loaded library.
Here we have the fundamental example usage of <dlfcn.h>.
First off, a main function opens a shared object using dlopen with RTLD_LAZY so we resolve symbols as the code is executed.
Using dlsym, we find the function we want to execute in our shared object; finally, we execute the function in the shared object, and close it.
File main.cpp:
#include <iostream>
#include <dlfcn.h>
typedef void (*func_t)();
int main() {
void * shared_object = dlopen("./test.so", RTLD_LAZY);
func_t func = (func_t) dlsym(shared_object, "func");
func();
dlclose(shared_object);
}
In the target function in the shared object we just need extern "C" to avoid mangling thereby allowing dlsynm to find function by its name.
File test.cpp:
#include <iostream>
extern "C" void func() {
std::cout << "Called func in shared_object." << std::endl;
}
When we compile main, we need to link against the dl (dynamic link) library as shown below.
The code should compile without errors, (however we will get a point-to-function cast warning mentioned previously), and when run Called func in shared_object. should be printed to the terminal:
>> g++ -pedantic -fPIC -o example main.cpp -ldl
main.cpp: In function ‘int main()’:
main.cpp:9:55: warning: ISO C++ forbids casting
between pointer-to-function and
pointer-to-object [enabled by default]
>> g++ -fPIC -shared -o test.so test.cpp
>> ./example
Called func in shared_object.
This is a very basic example without any sort of error checking during the loading of the shared object or symbol searching.
If the shared object or a symbol is not found, we will get a Segmentation fault; we will incorporate some error checking later on.
Agram's Agnostic Agony shows that a redeclaration of dlsym can make the warnings go away.
If you want a quick and easy fix for pre-existing code, this will do the trick:
#define dlsym __ #include <dlfcn.h> #undef dlsym extern "C" void *(*dlsym(void *handle, const char *symbol))();
In an example on the dlsym man page (towards the bottom) we see that:
... the C99 standard leaves casting from "void *" to a function pointer undefined. The assignment used below is the POSIX.1-2003 (Technical Corrigendum 1) workaround...
Whilst I hav not yet encountered this, toidinamaiblog suggests that the previous method may result in warning such as dereferencing type-punned pointer will break strict-aliasing rules.
By casting the return type of dlsym, this waring can be avoid:
typedef hook_fn (*hook_dlsym_t)(void *, const char *); f = ((hook_dlsym_t)(dlsym))(h, "hook");
Using the POSIX.1-2003 Technical Corrigendum 1 Work-around for dynamically linking against a shared object, we do the following:
TargetObject* (*PluginEntry)();
void (*PluginExit)(TargetObject*);
void * plugin = dlopen("plugin.so", RTLD_LAZY);
*(void **) (&PluginEntry) = dlsym(plugin, "create");
*(void **) (&PluginExit) = dlsym(plugin, "destroy");