Dynamically linking a shared library from a pybind11-wrapped code - c++

I am trying to add python bindings to a medium-sized C++ scientific code (some tens of thousands LOCs). I have managed to make it work without too many issues, but I have now incurred in an issue which I am incapable of solving myself. The code is organized as follows:
All the classes and data structures are compiled in a library libcommon.a
Executables are created by linking this library
pybind11 is used to create a core.so python module
The bindings for the "main" parts work fine. Indeed, simulations launched from the standalone code or from python give the exact same results.
However, the code also supports a plugin-like system which can load shared libraries at runtime. These shared libraries contain classes that inherit from interfaces defined in the main code. It turns out that if I try to link these shared libraries from python I get the infamous "undefined symbol" errors. I have checked that these symbols are in the core.so module (using nm -D). In fact, simulations that perform the dynamic linking with the standalone code works perfectly (within the same folder and with the same input). Somehow, the shared lib cannot find the right symbols when called through python, but it has no issues when loaded by the standalone code. I am using CMake to build the system.
What follows is a MCE. Copy each file in a folder, copy (or link) the pybind11 folder in the same place and use the following commands:
mkdir build
cd build
cmake ..
make
which will generate a standalone binary and a python module. The standalone executable will produce the correct output. By contrast, using the following commands in python3 (that, at least in my head, should be equivalent) yields an error:
import core
b = core.load_plugin()
main.cpp
#include "Base.h"
#include "plugin_loader.h"
#include <iostream>
int main() {
Base *d = load_plugin();
if(d == NULL) {
std::cerr << "No lib found" << std::endl;
return 1;
}
d->foo();
return 0;
}
Base.h
#ifndef BASE
#define BASE
struct Base {
Base();
virtual ~Base();
virtual void foo();
};
#endif
Base.cpp
#include "Base.h"
#include <iostream>
Base::Base() {}
Base::~Base() {}
void Base::foo() {
std::cout << "Hey, it's Base!" << std::endl;
}
plugin_loader.h
#ifndef LOADER
#define LOADER
#include "Base.h"
Base *load_plugin();
#endif
plugin_loader.cpp
#include "plugin_loader.h"
#include <dlfcn.h>
#include <iostream>
typedef Base* make_base();
Base *load_plugin() {
void *handle = dlopen("./Derived.so", RTLD_LAZY | RTLD_GLOBAL);
const char *dl_error = dlerror();
if(dl_error != nullptr) {
std::cerr << "Caught an error while opening shared library: " << dl_error << std::endl;
return NULL;
}
make_base *entry = (make_base *) dlsym(handle, "make");
return (Base *) entry();
}
Derived.h
#include "Base.h"
struct Derived : public Base {
Derived();
virtual ~Derived();
void foo() override;
};
extern "C" Base *make() {
return new Derived();
}
Derived.cpp
#include "Derived.h"
#include <iostream>
Derived::Derived() {}
Derived::~Derived() {}
void Derived::foo() {
std::cout << "Hey, it's Derived!" << std::endl;
}
bindings.cpp
#include <pybind11/pybind11.h>
#include "Base.h"
#include "plugin_loader.h"
PYBIND11_MODULE(core, m) {
pybind11::class_<Base, std::shared_ptr<Base>> base(m, "Base");
base.def(pybind11::init<>());
base.def("foo", &Base::foo);
m.def("load_plugin", &load_plugin);
}
CMakeLists.txt
PROJECT(foobar)
# compile the library
ADD_LIBRARY(common SHARED Base.cpp plugin_loader.cpp)
TARGET_LINK_LIBRARIES(common ${CMAKE_DL_LIBS})
SET_TARGET_PROPERTIES(common PROPERTIES POSITION_INDEPENDENT_CODE ON)
# compile the standalone code
ADD_EXECUTABLE(standalone main.cpp)
TARGET_LINK_LIBRARIES(standalone common)
# compile the "plugin"
SET(CMAKE_SHARED_LIBRARY_PREFIX "")
ADD_LIBRARY(Derived SHARED Derived.cpp)
# compile the bindings
ADD_SUBDIRECTORY(pybind11)
INCLUDE_DIRECTORIES( ${PROJECT_SOURCE_DIR}/pybind11/include )
FIND_PACKAGE( PythonLibs 3 REQUIRED )
INCLUDE_DIRECTORIES( ${PYTHON_INCLUDE_DIRS} )
ADD_LIBRARY(_oxpy_lib STATIC bindings.cpp)
TARGET_LINK_LIBRARIES(_oxpy_lib ${PYTHON_LIBRARIES} common)
SET_TARGET_PROPERTIES(_oxpy_lib PROPERTIES POSITION_INDEPENDENT_CODE ON)
pybind11_add_module(core SHARED bindings.cpp)
TARGET_LINK_LIBRARIES(core PRIVATE _oxpy_lib)

You are right, symbols from imported library are not visible because core loaded without RTLD_GLOBAL flag set. You can fix that with a couple of extra lines on python side:
import sys, os
sys.setdlopenflags(os.RTLD_GLOBAL | os.RTLD_LAZY)
import core
b = core.load_plugin()
From sys.setdlopenflags() doc:
To share symbols across extension modules, call as sys.setdlopenflags(os.RTLD_GLOBAL). Symbolic names for the flag values can be found in the os module (RTLD_xxx constants, e.g. os.RTLD_LAZY).

Related

C++ DLLs: how to create the corresponding include headers?

Problem
I have a C++ project and create a library A from it. If I now link another project B with this library A, I of course also have to provide an include path for A's headers, so I just use A's source folder. But A's headers contain symbols that aren't exported. I feel like this is not the correct way to do it, but don't know better. A specific thing that makes me feel like this is incorrect is that my IDE suggests the symbols that aren't exported.
I'd guess the solution would be to create an include folder besides the source folder where the same headers are in but only with the exported symbols. So at build-time, every symbol with PROJECTAPI should be automatically copied over to the corresponding headers in the include folder. But if I google, I don't find such a function for e.g. cmake.
So what would be the recommended way here? Is there a functionality to create such an include folder?
Example
example.cpp of project B
#include <A/main.hpp>
int main() {
ex::World w("Earth");
w.say_hello();
//IDE wouldn't see this as error: w.private();
}
main.cpp of project A
#include <iostream>
#include "main.hpp"
namespace ex {
void World::say_hello() {
std::cout << "Hello, World from " << m_name << std::endl;
}
World::World(std::string name)
: m_name(name)
{}
void World::hidden() {
std::cout << "Not exported" << std::endl;
}
}
main.hpp of project A
#include <string>
#ifndef PROJECTAPI
# ifdef example_EXPORTS
# define PROJECTAPI __declspec(dllexport)
# else
# define PROJECTAPI __declspec(dllimport)
# endif
#endif
namespace ex {
class World {
private:
std::string m_name;
public:
void PROJECTAPI say_hello();
PROJECTAPI World(std::string name);
void hidden();
};
}
Edit: private isn't a good method name
You are looking for the PIMPL idiom. "PIMPL" is short for "Pointer to IMPLementation". The idea is that, at the cost of a pointer indirection, you hide the implementation data and private methods in an inner class whose definition is opaque to the API consumer.
This approach is especially effective if you need to provide ABI stability.
Herb Sutter has a great GOTW on this here: https://herbsutter.com/gotw/_100/
Here is a full example that's close-ish to your code:
$ tree
.
├── CMakeLists.txt
├── include
│   └── world.h
├── main.cpp
└── src
├── world.cpp
└── world_priv.h
In ./include/world.h (the public header)
#ifndef WORLD_H
#define WORLD_H
#include <memory>
#include <string>
#include "world_export.h"
namespace ex {
class World {
public:
WORLD_EXPORT World(std::string name);
WORLD_EXPORT ~World() /* = default */;
void WORLD_EXPORT say_hello();
private:
class Impl;
std::unique_ptr<Impl> pImpl;
};
} // namespace ex
#endif
In ./src/world_priv.h:
#ifndef WORLD_PRIV_H
#define WORLD_PRIV_H
#include "world.h"
namespace ex {
class World::Impl {
public:
Impl(std::string name) : name(std::move(name)) {}
void say_hello();
void hidden();
private:
std::string name;
};
} // namespace ex
#endif
In ./src/world.cpp:
#include <iostream>
#include "world_priv.h"
namespace ex {
World::World(std::string name)
: pImpl(std::make_unique<Impl>(std::move(name))) {}
World::~World() = default;
void World::say_hello() { pImpl->say_hello(); }
void World::Impl::say_hello() {
std::cout << "Hello, World from " << name << "\n";
}
void World::Impl::hidden() { std::cout << "Not exported" << std::endl; }
} // namespace ex
In main.cpp:
#include <world.h>
int main() {
ex::World w("Earth");
w.say_hello();
}
Finally, here's the build:
cmake_minimum_required(VERSION 3.21)
project(pimpl_example)
option(BUILD_SHARED_LIBS "Build world as shared rather than static" ON)
# Library
include(GenerateExportHeader)
set(CMAKE_CXX_VISIBILITY_PRESET hidden)
set(CMAKE_VISIBILITY_INLINES_HIDDEN 1)
add_library(world src/world.cpp src/world_priv.h include/world.h)
add_library(world::world ALIAS world)
target_include_directories(
world PRIVATE "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src>"
PUBLIC "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>"
)
generate_export_header(world EXPORT_FILE_NAME include/world_export.h)
target_compile_definitions(
world PUBLIC "$<$<NOT:$<BOOL:${BUILD_SHARED_LIBS}>>:WORLD_STATIC_DEFINE>")
target_include_directories(
world PUBLIC "$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/include>")
# Application
add_executable(app main.cpp)
target_link_libraries(app PRIVATE world::world)
This doesn't include install rules or anything, but it's ready for those to be written.
Building it:
$ cmake -G Ninja -S . -B build -DCMAKE_BUILD_TYPE=RelWithDebInfo
...
$ cmake --build build
$ ./build/app
Hello, World from Earth
$ $ nm ./build/libworld.so | c++filt | grep ' T ' | uniq
0000000000001460 T ex::World::say_hello()
00000000000012e0 T ex::World::World(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >)
00000000000013c0 T ex::World::~World()
You can see that only the API of ex::World is exported by the library. All the private details are hidden both in the code and in the library itself.
On Windows:
>dumpbin /EXPORTS build\world.dll
Microsoft (R) COFF/PE Dumper Version 14.28.29915.0
Copyright (C) Microsoft Corporation. All rights reserved.
Dump of file build\world.dll
File Type: DLL
Section contains the following exports for world.dll
00000000 characteristics
FFFFFFFF time date stamp
0.00 version
1 ordinal base
3 number of functions
3 number of names
ordinal hint RVA name
1 0 00001390 ??0World#ex##QEAA#V?$basic_string#DU?$char_traits#D#std##V?$allocator#D#2##std###Z
2 1 00001550 ??1World#ex##QEAA#XZ
3 2 000015D0 ?say_hello#World#ex##QEAAXXZ
Summary
1000 .data
1000 .pdata
2000 .rdata
1000 .reloc
1000 .rsrc
2000 .text
Normally, a simple way to tackle that would be to create separate public and private headers ahead of time, and only expose the public ones to the user.
Here's a simple project structure that would accomplish that:
- lib_a
- include
- main.hpp
- src
- main_private.hpp
- main.cpp
Now, obviously, that won't work for the code you posted, since the declarations you want to separate belong to the same class. But that's just a symptom of the fact that what you are trying to do is unfortunately not allowed.
From the standard basic.def.odr:
There can be more than one definition of a
(13.1) class type ([class]),
[...]
in a program provided that each definition appears in a different translation unit and the definitions satisfy the following requirements.
[...]
Each such definition shall consist of the same sequence of tokens [...]
In other words, if you put a class in a public header, it has to be identical to the one that was used when compiling the library.
As much as it would be convenient, putting "half-a-class" in a public header is just not allowed.

Windows C++ static library fails to access external method during initialization

I am converting a large Windows C++ application from a large set of source files to
a smaller core application linked to several static libraries (to which many
of the original source files are moved).
Each library requires access to a 'registration method' in the core application.
Each library should call that method during global initialization, but that is
not happening. That is my problem.
The code works fine in the original form where libraries are not used.
I guess I am omitting a necessary link option for the libraries, but
I don't know which.
I have created a minimal, workable example. I developed this
on Windows 10 using:
CMake 3.14.5
MSVC 2019
Here's CMakeLists.txt:
cmake_minimum_required(VERSION 2.8.9)
project (CMakeLinkTest)
add_library(myLibrary STATIC MyStar.cpp)
add_executable(CMakeLinkTest StarFactory.cpp main.cpp)
target_link_libraries(CMakeLinkTest myLibrary)
The application contains main.cpp:
#include <iostream>
int main(int argc, char *argv[]){
std::cout << "Hello World!" << std::endl;
return 0;
}
and a singleton class called StarFactory.
StarFactory.h:
#include<string>
class StarFactory
{
public:
static StarFactory* instance();
~StarFactory() {};
std::string registerStarType(std::string a_type);
private:
StarFactory() {};
static StarFactory* mp_instance; // Singleton instance
};
StarFactory.cpp:
#include <iostream>
#include "StarFactory.h"
StarFactory* StarFactory::mp_instance = 0;
StarFactory* StarFactory::instance()
{
if ( mp_instance==0 )
mp_instance = new StarFactory;
return mp_instance;
}
std::string StarFactory::registerStarType(std::string a_type)
{
std::cout << "registerStarType: " << a_type << std::endl;
return a_type;
}
Finally, a static library contains class MyStar which registers itself with
the singleton at global initialisation.
MyStar.cpp:
#include<string>
#include "StarFactory.h"
class MyStar
{
public:
MyStar() {
StarFactory* s = StarFactory::instance();
//s->registerStarType("MyStar");
};
};
MyStar myStar;
std::string starName = StarFactory::instance()->registerStarType("MyStar");
Now for what happens. If I link MyStar.cpp directly into the application I see:
>CMakeLinkTest.exe
registerStarType: MyStar
Hello World!
If link MyStar.cpp into MyLibrary.lib and link that to the application I see:
>CMakeLinkTest.exe
Hello World!
So, the library's call (last line of MyStar.cpp) to the application's singleton is not working.
Can anyone explain this please?
As stated by engf-010, if a symbol defined in your static library is not used, the linker won't put it in the final binary.
One way to solve the problem using CMake would be to use an OBJECT library instead of a STATIC library.
The default behavior for linker is to not include static library that is not referenced.
You can either:
force linker to include the library anyway - you can use cmake's add_link_options or equivalent
not use a static library - just link the object like as in your first example
reference the code in static library
use a shared object (dynamic library) instead

CMake link not subfolder

I’m new to cmake.
I want to create code to create instances of some classes (like ClassA) and collect them in a handler class. For this i have created a template class Creator.
In each class implementation a instance of this class is created with Creator class. (see ClassA.cpp line 8)
I have following folder structure
├── CMakeLists.txt
├── main.cpp
└── SubFolder
├── ClassA.cpp
├── ClassA.h
├── CMakeLists.txt
└── Creator.h
./main.cpp
#include <iostream>
#include "SubFolder/ClassA.h"
int main(int argc, char **argv) {
//classA a;
std::cout << std::endl << "Hello, world!" << std::endl;
return 0;
}
./CMakeLists.txt
cmake_minimum_required(VERSION 2.8)
project(teststaticcmake)
add_executable(teststaticcmake main.cpp)
add_subdirectory(SubFolder)
target_link_libraries(teststaticcmake SubFolder)
install(TARGETS teststaticcmake RUNTIME DESTINATION bin)
SubFolder/ClassA.h
#ifndef __CLASSA__
#define __CLASSA__
class classA
{
public:
classA();
};
#endif //__CLASSA__
SubFolder/ClassA.cpp
#include "ClassA.h"
#include "Creator.h"
classA::classA()
{
}
classA* pClassA = Creator<classA>::create();
SubFolder/Creator.h
#ifndef __CREATOR__
#define __CREATOR__
#include <iostream>
template<typename T>
class Creator
{
public:
static T* create()
{
T* p = new T();
// Do Something here
// ... like output
std::cout << std::endl << "created: " << p;
return p;
}
};
#endif //__CREATOR__
SubFolder/CMakeLists.txt
add_library(SubFolder ClassA.cpp)
I compile this project and run it. So I get only the output "Hello, world!".
When I remove the comment (main.cpp line 5) a instance of ClassA is used. So I get also the output of class Creator. The code for ClassA is linked.
When I move the class ClassA to root directory it works also.
I have also tried to use parameters like PUBLIC_LINK, debug and general for target_link_libraries. But nothing works.
My intention use a Collection Class in this main.cpp file and get the instanced object from the collection. In the main.ccp file i don't want to know each instanced class because all class ClassA ... ClassZ have the same interface (not shown in this example).
How can i force the link of "unused" code?
Edit: Do don't know if it's neccessary. I use KDevelop4.
See How to force gcc to link an unused static library
I've tested your code with GNU 4.8.1 compilers and in your example just replace your target_link_libraries() line with:
target_link_libraries(
teststaticcmake
PRIVATE
"-Wl,--whole-archive"
SubFolder
"-Wl,--no-whole-archive"
)
From target_link_libraries() documentation:
A link flag: Item names starting with -, but not -l or -framework, are treated as linker flags. Note that such flags will be treated like any other library link item for purposes of transitive dependencies, so they are generally safe to specify only as private link items that will not propagate to dependents.
More References
How to force inclusion of an object file in a static library when linking into executable?

Can't load dynamic shared library at runtime including another shared library in C++

I'm trying to implement a design similar to the one described here:
http://www.linuxjournal.com/article/3687
I've got a core projects which compiles into an executable binary. I also want to have shared libraries loaded at runtime using dlopen(). Now I'm facing the problem that there are some classes which should be known in the core project and also in all of the loaded libraries. I thought I'd put them together in another shared library which gets linked to all of them at compilation. Everything seems to compile fine but during loading the shared library ConnectionModule.so dlopen returns no handle and dlerror says 'undefined symbol: _ZTV17PipelineProcessor' where PipelineProcessor is a class defined and implemented in the shared library which should be compiled in every addition shared library.
Does anyone have an idea of what is wrong with my implementation or is this design just doomed to fail?
some code here:
loading of library at runtime
#include "../SharedHeaders/SharedInEveryLibrary.h"
// ...
map<string, maker_t*, less<string> > module_library;
// ...
void *hndl = dlopen(library_file_path, RTLD_NOW | RTLD_GLOBAL);
if (hndl == nullptr) {
Logger::error << "Could not load library " << library_file_name << ": " << dlerror() << endl;
exit(-1);
}
ConditionModule.h (should be loaded at runtime)
#pragma once
#include "../SharedHeaders/SharedInEveryLibrary.h"
class ConditionModule : public B {
public:
A* processRequest(X* data) override; // implemented at ConditionModule.cpp
};
extern "C" {
A *maker() {
return new ConditionModule;
}
class proxy {
public:
proxy() {
module_library["condition_module"] = maker;
}
};
proxy p;
}
... and of course aome of the content of SharedInEveryLibrary.h
class Z; // gets implemented later, I guess it's not important
struct X {
int something;
// ...
};
class A {
public:
virtual void setSomeData(X* v_some_data);
virtual A* process(Z* data) = 0;
protected:
X* some_data;
};
class B : public A {
public:
A* process(Z* data) override;
virtual A* processRequest(X* data) = 0;
};
typedef A* maker_t();
extern map<string, maker_t*, less<string> > module_library;
EDIT -----
Forgot to mention that I currently develop only with Linux as compilation target.
There is nothing wrong with this design. But when you dlopen() a shared library, all it's symbols need to be resolved. Your error means that you do not have the path to .so your library depends on in LD_LIBRARY_PATH. To check what library it needs, use
ldd <your library.so>
Than add the directory where this library resides to LD_LIBRARY_PATH.

C++ Dynamic Shared Library on Linux

This is a follow-up to Dynamic Shared Library compilation with g++.
I'm trying to create a shared class library in C++ on Linux. I'm able to get the library to compile, and I can call some of the (non-class) functions using the tutorials that I found here and here. My problems start when I try to use the classes that are defined in the library. The second tutorial that I linked to shows how to load the symbols for creating objects of the classes defined in the library, but stops short of using those objects to get any work done.
Does anyone know of a more complete tutorial for creating shared C++ class libraries that also shows how to use those classes in a separate executable? A very simple tutorial that shows object creation, use (simple getters and setters would be fine), and deletion would be fantastic. A link or a reference to some open source code that illustrates the use of a shared class library would be equally good.
Although the answers from codelogic and nimrodm do work, I just wanted to add that I picked up a copy of Beginning Linux Programming since asking this question, and its first chapter has example C code and good explanations for creating and using both static and shared libraries. These examples are available through Google Book Search in an older edition of that book.
myclass.h
#ifndef __MYCLASS_H__
#define __MYCLASS_H__
class MyClass
{
public:
MyClass();
/* use virtual otherwise linker will try to perform static linkage */
virtual void DoSomething();
private:
int x;
};
#endif
myclass.cc
#include "myclass.h"
#include <iostream>
using namespace std;
extern "C" MyClass* create_object()
{
return new MyClass;
}
extern "C" void destroy_object( MyClass* object )
{
delete object;
}
MyClass::MyClass()
{
x = 20;
}
void MyClass::DoSomething()
{
cout<<x<<endl;
}
class_user.cc
#include <dlfcn.h>
#include <iostream>
#include "myclass.h"
using namespace std;
int main(int argc, char **argv)
{
/* on Linux, use "./myclass.so" */
void* handle = dlopen("myclass.so", RTLD_LAZY);
MyClass* (*create)();
void (*destroy)(MyClass*);
create = (MyClass* (*)())dlsym(handle, "create_object");
destroy = (void (*)(MyClass*))dlsym(handle, "destroy_object");
MyClass* myClass = (MyClass*)create();
myClass->DoSomething();
destroy( myClass );
}
On Mac OS X, compile with:
g++ -dynamiclib -flat_namespace myclass.cc -o myclass.so
g++ class_user.cc -o class_user
On Linux, compile with:
g++ -fPIC -shared myclass.cc -o myclass.so
g++ class_user.cc -ldl -o class_user
If this were for a plugin system, you would use MyClass as a base class and define all the required functions virtual. The plugin author would then derive from MyClass, override the virtuals and implement create_object and destroy_object. Your main application would not need to be changed in any way.
The following shows an example of a shared class library shared.[h,cpp] and a main.cpp module using the library. It's a very simple example and the makefile could be made much better. But it works and may help you:
shared.h defines the class:
class myclass {
int myx;
public:
myclass() { myx=0; }
void setx(int newx);
int getx();
};
shared.cpp defines the getx/setx functions:
#include "shared.h"
void myclass::setx(int newx) { myx = newx; }
int myclass::getx() { return myx; }
main.cpp uses the class,
#include <iostream>
#include "shared.h"
using namespace std;
int main(int argc, char *argv[])
{
myclass m;
cout << m.getx() << endl;
m.setx(10);
cout << m.getx() << endl;
}
and the makefile that generates libshared.so and links main with the shared library:
main: libshared.so main.o
$(CXX) -o main main.o -L. -lshared
libshared.so: shared.cpp
$(CXX) -fPIC -c shared.cpp -o shared.o
$(CXX) -shared -Wl,-soname,libshared.so -o libshared.so shared.o
clean:
$rm *.o *.so
To actual run 'main' and link with libshared.so you will probably need to specify the load path (or put it in /usr/local/lib or similar).
The following specifies the current directory as the search path for libraries and runs main (bash syntax):
export LD_LIBRARY_PATH=.
./main
To see that the program is linked with libshared.so you can try ldd:
LD_LIBRARY_PATH=. ldd main
Prints on my machine:
~/prj/test/shared$ LD_LIBRARY_PATH=. ldd main
linux-gate.so.1 => (0xb7f88000)
libshared.so => ./libshared.so (0xb7f85000)
libstdc++.so.6 => /usr/lib/libstdc++.so.6 (0xb7e74000)
libm.so.6 => /lib/libm.so.6 (0xb7e4e000)
libgcc_s.so.1 => /usr/lib/libgcc_s.so.1 (0xb7e41000)
libc.so.6 => /lib/libc.so.6 (0xb7cfa000)
/lib/ld-linux.so.2 (0xb7f89000)
On top of previous answers, I'd like to raise awareness about the fact that you should use the RAII (Resource Acquisition Is Initialisation) idiom to be safe about handler destruction.
Here is a complete working example:
Interface declaration: Interface.hpp:
class Base {
public:
virtual ~Base() {}
virtual void foo() const = 0;
};
using Base_creator_t = Base *(*)();
Shared library content:
#include "Interface.hpp"
class Derived: public Base {
public:
void foo() const override {}
};
extern "C" {
Base * create() {
return new Derived;
}
}
Dynamic shared library handler: Derived_factory.hpp:
#include "Interface.hpp"
#include <dlfcn.h>
class Derived_factory {
public:
Derived_factory() {
handler = dlopen("libderived.so", RTLD_NOW);
if (! handler) {
throw std::runtime_error(dlerror());
}
Reset_dlerror();
creator = reinterpret_cast<Base_creator_t>(dlsym(handler, "create"));
Check_dlerror();
}
std::unique_ptr<Base> create() const {
return std::unique_ptr<Base>(creator());
}
~Derived_factory() {
if (handler) {
dlclose(handler);
}
}
private:
void * handler = nullptr;
Base_creator_t creator = nullptr;
static void Reset_dlerror() {
dlerror();
}
static void Check_dlerror() {
const char * dlsym_error = dlerror();
if (dlsym_error) {
throw std::runtime_error(dlsym_error);
}
}
};
Client code:
#include "Derived_factory.hpp"
{
Derived_factory factory;
std::unique_ptr<Base> base = factory.create();
base->foo();
}
Note:
I put everything in header files for conciseness. In real life you should of course split your code between .hpp and .cpp files.
To simplify, I ignored the case where you want to handle a new/delete overload.
Two clear articles to get more details:
C++ dlopen mini how-to
C++ Dynamic Loading of Shared Objects at Runtime
Basically, you should include the class' header file in the code where you want to use the class in the shared library. Then, when you link, use the '-l' flag to link your code with the shared library. Of course, this requires the .so to be where the OS can find it. See 3.5. Installing and Using a Shared Library
Using dlsym is for when you don't know at compile time which library you want to use. That doesn't sound like it's the case here. Maybe the confusion is that Windows calls the dynamically loaded libraries whether you do the linking at compile or run-time (with analogous methods)? If so, then you can think of dlsym as the equivalent of LoadLibrary.
If you really do need to dynamically load the libraries (i.e., they're plug-ins), then this FAQ should help.