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

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 to 17) : Use of smart pointers to store the classes from the libraries

– Cross-platform : Works on Linux & Windows.

– Generic enough to be used by a wide range of programs.

Introduction

One of the best ways to make your C++ program accept plugins is to use dynamic loading of a class from a library. According toWikipedia, dynamic loading is the process that allows you to retrieve functions and variables into the library. That’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.

– The library’s content is not included in the binary, and therefore the developer doesn’t have to compile it each time he wants to update the binary.

– The developer is able to push further the separation between non-related parts of his program, and to reuse them easier.

Imagine for example, programming an HTTP server. It must ideally have several modules, with different goals for each (SSL, PHP…). Instead of including all of them in one binary, why not make them shared libraries, and use 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 at the top.

What we’ll be building

A simple C++ program, running both on Linux 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, which is IPlanet, and 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 !

The interface (API)

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

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

– Note that for Windows we have to include __declspec (dllexport) before the function prototype. More info on this here.

– There is this extern "C" line, wrapping some C-like functions. It is in this part that we will set our entry points to load and unload the library class right into and from our “core” program. To know why we need it, we must understand how dynamic loading works.

Note on dynamic loading process

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, or 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 linux, 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 a different signature, 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 ensure 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. Therefore we’ll be able to retrieve them easier. Simply by using their names !

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

Allocator

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 transform it on the dlloader-side.

Deleter

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.

Well, that’s about it for the libraries ! I just included one library here, but the principle is the same for another library, as long as it obeys to the IPlanet interface.

The Dynamic Library Loader (DLLoader)

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

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

Note on the compilation

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

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

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

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

So we have :– Allocator entry point : function pointer of type allocClass & held by the variable allocFunc.– Deleter entry point : function pointer of type deleteClass & held by the variable deleteFunc.

Finally, remember that we wanted to use smart pointers ? We shouldn’t use raw pointer anymore. 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.

Windows’ DLLoader class

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

In greet() and greetFromAPlanet() 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.