Skip to content

Plugins in CPP: Dynamically Linking Shared Objects

Christopher Poole edited this page Mar 23, 2025 · 3 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 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.

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

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