Skip to content

Plugins in CPP: Dynamically Linking Shared Objects

Alec Chen edited this page May 6, 2015 · 2 revisions

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 cavete; ISO C++ forbids casting between pointer-to-function and pointer-to-object, a limitation the user will invariably encounter when tring 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, albiet with a warning. Here we will quickly cover three techniques for either suppressing the warning or avoiding it with function redeclarations or some appropirate 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.

Dynamic Linking

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 incorperate some error checking later on.

Warning Suppression by Redeclaring dlsym

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))();

POSIX.1-2003 Technical Corrigendum 1 Work-around

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...

Casting the dlsym Return Type

Whilst the author has not yet encoutered 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");

A Simple Plugin Interface

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");