C++ Factory Method with Shared Libraries

Suppose that you are working on a generic framework and you would like to allow the users of the framework to extend the framework with domain-specific functionality. A programming pattern that is typically deployed for this scenario is the so-called factory method pattern. The core components of this pattern are an (abstract) interface class, a set of derived classes and a factory method that generates the requested type cast to the interface class. A very simplistic coding of the factory method could be the following:

If you are working on a very small, possibly company-internal, framework, it might be acceptable to share your complete code base with all programmers and allow them to modify the above code when adding new type classes, e.g. ClassC, ClassD, etc. In turn, if your code base is fairly huge, e.g. with overall build-times larger than 30mins, or if you do not want to share the code base, e.g. due to IP restrictions, you might want users of your framework to provide the functionality for new types in terms of shared libraries built out-of-source and loaded at runtime when requested. This post sketches how this could be implemented for the prototypical use-case above in C++.

The first ingredient for our recipe is the interface definition which is the Interface class plus an evil registration macro to streamline and unify the plugin handling:

As we will see in the later course of this post, every shared library is required to have a unique and identical entry point to be loadable by our framework. This entry point is created by the macro PLUGIN_CLASS. Note the use of extern “C” that disables the C++ name mangling such that the function is exported as createPlugin. Every plugin is required to use the macro PLUGIN_CLASS once! Given the interface, we next define our shared library for ClassA:

Next, we compile the above code as shared library and inspect the exported symbols of the shared library with the nm(1) utility to check that our entry point is available:

Note that the capital T – for text – in the output of nm(1) indicates a defined and exported function. Having defined the prerequisites for our framework, we now take a look at the new factory method:

The main function is the same as before with the sole exception that our main program requires the file name of a shared library to be given as first program argument. The new factory method factoryMethod requires the file name of the shared library to be given, but still returns a unique pointer to the Interface class. Inside the factory method, the central parts are the calls to the functions dlopen(3) and dlsym(3) that allow to load a shared library at runtime and to query for a symbol’s address inside this library, respectively. But let’s look at the code line by line. In order to map the function’s address to C++ function pointers, i.e. std::function, we define the function signature (line 6) and the respective std::function type (line 7). The shared library is then opened in line 10 by dlopen(3) which returns an opaque library handle. The first parameter to dlopen(3) is the path or file name of the shared library, while the second parameters configures when unresolved symbols inside the shared library are tried to resolve. The two possible settings are RTLD_LAZY to perform the resolution only when the symbol is referenced or RTLD_NOW to perform the resolution immediately at load time. We choose the former one for performance reasons. In line 14 we finally query the shared library, identified by the opaque library handle, for the address of the function createPlugin which is cast to the std::function in line 16 and eventually called in line 17.

Compiling and running the code then yields the desired results:

Leave a Reply

Your email address will not be published. Required fields are marked *