Loading of a C++ class from a shared library (Modern C++)

Originally posted on February 26th, 2018

To go to the Github repository, click here.

There is already a lot of stuff on internet, related to dynamic loading of classes from a shared library. However I couldn’t find a simple example and explanation on how to do it with the following conditions :

  • Modern C++ (from c++11): Use of smart pointers to store the classes retrieved from the libraries

One of the best ways to make a C++ program accept plugins is to use dynamic loading of a class from a library. According to Wikipedia, dynamic loading is the process that allows to retrieve functions and variables from a library. It’s very powerful, for multiple reasons:

  • It requires no restart when you “load” or “unload” a shared library from an executable, because it isn’t statically linked.

Imagine programming an HTTP server, for example. It must ideally have several modules, with different goals for each one (SSL, PHP…). Instead of including all of them in one binary, why not making them shared libraries and load them with dynamic loading?

In this article, I’ll do my best to explain and show you a simple method to load classes from shared libraries, taking care of the conditions listed above.

A simple C++ program, running both on UNIX and Windows. It will have a Core part (the executable), responsible for checking the libraries existence, and then to load, use and unload them. The libraries are classes that greet the user from the different Star Wars planets. They share a common interface, IPlanet, which is also known by the Core.

Here is a basic diagram to show how it works:

Note that my goal is to show a simple implementation of all of this, therefore adding error handling and/or some optimizations might be good once you get the concept.

Let’s get started!

All the classes coming from the shared libraries will inherit from this interface. Having an interface is useful when you have multiple libraries, because the core won’t know every concrete class. The core is then able to manipulate each class through the interface.

Both the core AND the libraries must be aware of this class. Therefore it can be useful to keep it in a separated file and folder.

A long time ago, in a galaxy far, far away… Was Tatooine. The Tatooine header, in its simpliest form, is as follow :

There’s not a lot of things to say about it, the interesting part is in its .cpp

A few things here:

  • We use a conditional preprocessor macro : #ifdef to only enter the condition if we are compiling on a specific platform. Here it may be Windows (WIN32), MacOS (__APPLE__), or Linux (__linux__). We can't exactly use the same code for both platforms because of the next point.

To load something from a shared library, we need to know where to look at in the assembly file. That’s where symbols are useful. Symbols can be anything, like a function, a variable and, in our example, classes (remember Tatooine ?). As long as we know the name of the symbol we need, we can use a set of low-level functions : dlopen(), dlsym() and dlclose() for UNIX, LoadLibrary(), GetProcAdress() and FreeLibrary() for Windows. These functions allow us to load the shared library into the memory, then to retrieve the symbol, to get the class from it and to unload the library.

All of this could work in C for example, where there is no (or little) “ name mangling “. As said by Wikipedia, name mangling is the modification of variables names into the assembly file. It is required in languages implementing features like parametric polymorphism, templates, or namespaces. Like in C++!

Indeed, if several functions share the same name but with different signatures, the compiler must clearly differenciate them in the final assembly. Mangling is made by the compiler.

By wrapping our “entry code” with extern "C", we make sure that no name mangling will be done by the compiler. The symbols in the assembly file will be exactly the same as the ones in our source code. We'll be able to retrieve them more easily,simply by using their names !

So there are two entry points (symbols) for our class : allocator and deleter.

We will get an instance of the library class through it. However we can’t return a smart pointer because it isn’t valid C. We just return a C pointer, and we’ll turn it into a smart one on the dlloader-side.

This one will be called when we’ll need to destroy our class. It is important to have a destructor directly in the dynamic library, to ensure that the memory management will happen in it.

That’s about it for the libraries. I just included one library here, but the principle remains the same for any other one, as long as it implements the IPlanet interface.

Hold on, this is quite a long tutorial, but it’s worth it (I hope)! Now is another interesting part: the dynamic library loader, which is included in the core (the executable).

First, let’s resume the process. Here are the steps we need to have it working smoothly:

Remember that our code must run on UNIX as well as on Windows. We will use the same concept of separated compilation with OS macros, but in the CMakeLists this time.

We need two different source files, because the code to interact with shared libraries is very low level and OS-specific. One for UNIX, and one for Windows. Here though, they won’t be .cpp files, but .h files. It will be easier to put everything in the header because the class is templated.

Firstly we need to create an interface for the DLLoader. Both Linux’s concrete DLLoader and Windows’ one will inherit from it.

I believe the comments in the code are sufficient.

As said before, we need a handle to store the shared library in memory once it is loaded from its file. We also need two symbol holders : allocClassSymbol and deleteClassSymbol.

The DLOpenLib() function is quite simple, it is calling dlopen() to load the library in memory. The DLCloseLib() function is calling dlclose() to unload it.

The interesting stuff is below :

We use the return of dlsym() to get each symbol we need. This variable is then casted to a function pointer, to make it usable.

So we have:

  • Allocator entry point: function pointer of type allocClass & held by the variable allocFunc.

Finally, remember that we wanted to use smart pointers? With C++11 we should limit our usage of raw pointers. That’s why we build it directly in this function, with the allocator and the deleter. Take a look at the overloads of std::shared_ptr’s constructor. Note that we call the deleter through a lambda, to pass it the raw pointer that we have to delete.

That’s basically the same principle. System calls are not the same though, so don’t forget to change them. Here is the entire source code for this class.

The last part we need is the core, to link all of this !

For the sake of the example, the shared libraries paths are hard-coded. Don’t forget to compile the libraries individually, and put them in the executable’s folder.

In greet() and greetFromPlanet() we use everything we just coded. As you see, we're using shared pointers. Also, the core doesn't know anything about either the Tatooine library or the Bespin library. All it knows is the IPlanet interface.

That’s it, I hope you enjoyed learning this stuff as much as I did! Don’t hesitate to send me remarks or comments, I’ll be pleased to answer them.

Don’t forget to checkout the code hosted on Github for this tutorial as well, and may the force be with you.

Special thanks to Ylannl for making the code compatible with MacOS.

Revision of this article on May 29th, 2019.



Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store