Interfacing C++ with Python - c++

I have a c++ (C++ 17) library and a function dependent on this library. I want to call this function from python(python 3). How can it be done?
I have read some tutorials using swig and ctypes but I don't know how can I make it work for an external shared library.
Details:
I have this libclickhouse-cpp-lib.so .so file and the header files for this library in /path/to/header/files directory.
/*File cpp_reader.h*/
#pragma once
int reader();
/* File cpp_reader.cpp*/
#include <clickhouse/client.h>
#include <iostream>
#include "cpp_reader.h"
using namespace clickhouse;
int reader()
{
Client client(ClientOptions().SetHost("localhost"));
int numrows=0;
client.Select("SELECT count(*) from test.test_table",
[&numrows] (const Block& block)
{
for (size_t i=0;i<block.GetRowCount();++i)
{
numrows+=block[0]->As<ColumnUInt64>()->At(i);
}
}
);
return(numrows);
}
I want to call this read function from python. I have gone through some posts using swig and ctypes but haven't been able to figure out. If it can be done easily using anything else, please suggest that too.
Additional Information:
This is how I run the code in c++
/*File: main.cpp*/
#include <clickhouse/client.h>
#include <iostream>
#include <Python.h>
using namespace clickhouse;
int main()
{
/// Initialize client connection.
Client client(ClientOptions().SetHost("localhost"));
int numrows=0;
client.Select("SELECT count(*) from test.test_table",
[&numrows] (const Block& block)
{
for (size_t i=0;i<block.GetRowCount();++i)
{
numrows+=block[0]->As<ColumnUInt64>()->At(i);
}
}
);
std::cout<<"Number of Rows: "<<numrows<<"\n";
}
Compilation:
g++ -std=c++1z main.cpp -I/path/to/header/files -I/usr/include/python3.6m/ -L. /path/to/libclickhouse-cpp-lib.so -o outfile
LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/path/to/so_file/directory
export LD_LIBRARY_PATH
/path/to/so_file/directory contains libclickhouse-cpp-lib.so
./outfile

In your case it may be best to keep it simple and not use binding generators, because all you have here is a simple function that doesn't receive any parameters and simply returns an int.
Your goal can be achieved as follows:
In cpp_reader.cpp, prepend the following line before the reader definition:
extern "C" __attribute__ ((visibility ("default")))
int reader()
{
....
Now from Python you can simply do:
from ctypes import cdll
import os
lib = cdll.LoadLibrary(os.path.abspath("libclickhouse-cpp-lib.so"))
num_rows = lib.reader()
Also don't forget to add -shared to the g++ commandline when compiling the shared object

Related

How to make and link when building a C executable from large C++ library

I am a beginner in C and slightly more advanced in C++. This is my first time using make.
I have a large C++ library (written by a third-party that I need to integrate into a C pipeline) and I am hoping to call this library from C. In order to call the C++ library from C, I have 3 files: a .cpp file implementing the calls to the C++ library with C-compatible data types, a .h C-compatible header file linking the C++ implementation functions to C, and a .c file with a main() function that calls the C++ function with C-appropriate data types.
The header file (random_forest.h):
#ifndef RANDOMFOREST_H_
#define RANDOMFOREST_H_
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stddef.h>
#include <string.h>
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
// opaque forward declared struct
struct random_forest_model;
// pointer to struct used by C code
typedef struct random_forest_model* random_forest_model_t;
random_forest_model_t random_forest_new(const char* model_file_path);
void random_forest_free(random_forest_model_t random_forest_model);
uint8_t *classify(
random_forest_model_t random_forest_model,
const double* independentVariableData,
const double* dependentVariableData,
const size_t numberRows,
const size_t numberColumns,
const char** independentVariableNames
);
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif /* RANDOMFOREST_H_ */
In my random_forest.cpp file:
#include <memory.h>
#include "/src/random_forest.h"
#include "/src/rf/src/Forest.h"
#include "/src/rf/src/globals.h"
#include "/src/rf/src/ForestClassificationPrediction.h"
#include "/src/rf/src/utility.h"
using namespace rf; /* comes from Forest.h file */
struct random_forest_model {
std::unique_ptr<rf::Forest> forest;
std::string model_file_path;
}
namespace {
std::unique_ptr<rf::Forest> random_forest_acquire(const char* model_file_path) {
try {
std::unique_ptr<rf::Forest> forest = make_unique<ForestClassificationPrediction>();
forest->InitPredictionModelCpp(model_file_path);
return forest;
} catch(...) {
return nullptr;
}
}
} /* anonymous namespace */
random_forest_model_t random_forest_new(const char* model_file_path) {
try {
auto forest = random_forest_acquire(model_file_path);
return new random_forest_model{std::move(forest), model_file_path};
} catch (...) {
return nullptr;
}
}
void random_forest_free(random_forest_model_t random_forest_model) {
delete random_forest_model;
}
uint8_t* classify(
ranger_random_forest_model_t ranger_random_forest_model,
const double* independentVariableData,
const double* dependentVariableData,
const size_t numberRows,
const size_t numberColumns,
const char** independentVariableNames
) {
try {
/* bunch of stuff here to convert data and run classification */
} catch(...) {
return nullptr;
}
}
Then in my random_forest_implement.c file:
#include "/src/random_forest.h"
int main() {
const char model_file_path[] = "path/to/model";
random_forest_model_t random_forest = random_forest_new(model_file_path);
/*
some code here to ingest a data file - outputting the data for random_forest_classify
yielding: X, y, numberRows, numberColumns, varNames
*/
uint8_t *classes = classify(
random_forest, X, y, numberRows, numberColumns, varNames
);
random_forest_free(random_forest);
free(X);
free(y);
free(varNames);
free(classes);
return EXIT_SUCCESS;
}
This is a very long-winded way to ask how to compile this program into a single executable. I've tried to compile with the following make file:
CC ?= gcc
CP ?= g++
random_forest_implement: random_forest_implement.o random_forest.o
$(CP) -o random_forest_implement random_forest_implement.o random_forest.o
random_forest.o: random_forest.cpp random_forest.h
$(CP) -c random_forest.cpp
random_forest_implement.o: random_forest_implement.c random_forest.h
$(CC) -c random_forest_implement.c random_forest.h
clean:
$(RM) *.o random_forest
When I try to make this (make -f random_forest_make.mk), I get three lines that appear (?) successful, and I receive a lot of errors about undefined reference, e.g.:
cc -c random_forest.c random_forest.h
g++ -c random_forest.cpp
g++ -o random_forest_implement random_forest_implement.o random_forest.o
/usr/bin/ld: random_forest.o: in function `(anonymous namespace)::random_forest_acquire(char const*)':
random_forest.cpp:(.text+0x44f): undefined reference to `rf::Forest::InitPredictionModelCpp(<bunch of args necessary to method>)
I'm not entirely sure where to go from here, but I suspect I'm not compiling everything correctly. As you can see, I have several files on which my random_forest.cpp file depends. Do I need to compile each of these? And their dependencies? Is there a best/efficient method for doing this, or do I need to write a make file that generates an object file for every .cpp file in the /src/rf/src/ directory?
You cannot build a C executable including C++ sources. You can only create C++ executables if you include any C++ code. C++ supports C source modules to be linked against for compatibility reasons but C compilers were not designed to link C++ modules (there’s no backwards compatibility between C and C++, C was created before)
For this reason, you can only use c++ compiler to link C++ mixed with C sources (even if main is defined in a C source file) because this action will make C++ compiler to call the linker in C++ mode and link both C/C++ modules and call the C++ standard library modules and run time. Despite of the similarities between both languages, the memory layout of a C++ executable is more complex than the layout of a simple C-only program

Lua: Cant open shared library when lua is wrapped in C++

EDIT: Nearly got the answer, I just dont completely understand it, see last paragraph.
I try to build a shared lua library and use it within a larger project. When calling the script which loads the shared library from shell everything works. However, when I wrap the script within another shell, I get a runtime error when loading the library. Dependent on the script it is just any call to a lua function from c (i.e. lua_pushnumber). Here is a minimal example.
totestlib.cpp:
extern "C" {
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
}
int init(lua_State *L) {
lua_toboolean(L, -1);
return 0;
}
static const struct luaL_Reg testlib[] = {
{"init", init},
{NULL, NULL}
};
extern "C"
int luaopen_libtotestlib(lua_State *L) {
luaL_newlib(L, testlib);
return 1;
}
Compiled with: g++ -shared -fPIC -I./lua-5.4.4/src -L./lua-5.4.4/src totestlib.cpp -o libtotestlib.so
testlib.lua (testing shared library):
testlib.lua
print("start")
testlib = require("libtotestlib")
print("done")
testlib.init(true)
print("called")
Calling the lua script using ./lua-5.4.4/src/lua testlib.lua works. Everything is printed. Wrapping script in the following c++ code does not work:
call_testlib.cpp
extern "C" {
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
}
#include <unistd.h>
static lua_State *L;
int main(int argc, char *argv[]) {
L = luaL_newstate();
luaL_openlibs(L);
int tmp = luaL_loadfile(L, "testlib.lua");
if(tmp != 0) {
return 1;
}
tmp = lua_pcall(L, 0, 0, 0);
if(tmp != 0) {
printf("error pcall\n");
return 1;
}
}
Compiled with g++ call_testlib.cpp -o ./call_testlib -I./lua-5.4.4/src -L./lua-5.4.4/src -llua it prints "error pcall". If I print the error message on the lua stack, I get:
string error loading module 'libtotestlib' from file './libtotestlib.so':
./libtotestlib.so: undefined symbol: luaL_checkversion_
In this case the undefined symbol is luaL_checkversion_ (which I dont call myself), but with other scripts it is usually the first lua_... function that I call.
I have tried several things to fix this. For example, linking -llua when compiling the shared library, but this does not work (and should not be the problem as calling the script itself works). I also tried to load preload the library from c++ (as done in this question) instead of from lua, but I guess it does not really make a difference and I am getting the same error. I also uninstalled all lua versions from my path to make sure I always use the same version.
What is the difference between calling the script directly from shell and calling it inside a c function? Am I doing something wrong?
EDIT: Nearly got the answer. When using MYCFLAGS= -fPIC when compiling lua I can link lua to the shared library. At least this one works, but does not seem like a good solution to me and does not really answer my question: Why can lua itself (from shell) somehow add these symbols to the library while the wrapped c version can not? Additionally, my program has lua once linked in the shared library and once in the compiled C++ project (not optimal imo).

Pybind11 - Where to put PYBIND11_MODULE

Im currently playing around with pybind11 a bit. Im trying to create a C++ class which then gets passed to a python interpreter embedded in my C++ source.
I created some dummy class just to test the basic functionality I kept everything in a single source file. This approach compiled and ran without any problems.
Now I separated my dummy Class Test into a Test.h and Test.cpp
Test.h
#pragma once
#include<iostream>
#include"pybind11\pybind11.h"
namespace py = pybind11;
class Test
{
public:
Test(const std::string &s);
~Test();
void printStr();
private:
std::string _s;
};
Test.cpp
#include "Test.h"
PYBIND11_MODULE(TestModule, m)
{
py::class_<Test>(m, "Test")
.def(py::init<const std::string &>())
.def("printStr", &Test::printStr);
}
Test::Test(const std::string &s) : _s(s)
{
}
Test::~Test()
{
}
void Test::printStr()
{
std::cout << "---> " << _s << std::endl;
}
main.cpp
#include"Test.h"
int main(int argc, char **argv)
{
PyImport_AppendInittab("TestModule", PyInit_TestModule);
Py_Initialize();
PyRun_SimpleString("import TestModule");
PyRun_SimpleString("t = TestModule.Test(\"str\")");
PyRun_SimpleString("t.printStr()");
Py_Finalize();
getchar();
return 1;
}
After putting the Class Test into a new file the Compiler cannot find the PyInit_TestModule (main.cpp line: 6) anymore since this is generated by the PYBIND11_MODULE Macro which lives in the Test.cpp file(MSVS2017 Error: C2065).
I tried putting the PYBIND11_MODULE Macro into the Test.h. This however resulted in a linker error which said that "_PyInit_TestModule" is already defined in main.obj (MSVS2017 Error: LNK2005)
Putting the PYBIND11_MODULE Macro in the main.cpp file works.
However I feel like this will become quite unreadable as soon as you put a lot of custom Module definitions into main.cpp or even worse you have multiple Python-Interpreter being started from different source files where you then
need to put the same definition in all those files which will be a mess and most likely turn into a linker error.
Has one of you faced the same Problem and how did you solve it?
I created a file of his own for the bindings, and compiled/linked it together with the original c++ file. This way:
1) Test.h + Test.cpp contain only c++ code of your class
2) Test-bindings.cpp contains the PYBIND11_MODULE and #include <Test.h>
3) Building (with cmake). You will get a PyTest.so file out of it, that you can load in python.
# c++ libray
add_library(TestLib SHARED /path/to/Test.h /path/to/Test.cpp)
# bindings
add_subdirectory(pybind11) # you must have downloaded this repo
include_directories(/path-only/to/Test.h)
pybind11_add_module(PyTest SHARED /path/to/Test-bindings.cpp /path/to/Test.cpp)
4) (I suggest you to) write the main in python, using the python-binding you just created
5) In your main.py
import PyTest
# do something

share data pointer between c and lua module compiled with SWIG

I need to provide data structure pointer in my main program, where I have Lua state defined, to the dynamically loaded Lua module created by wrapping a c++ code using SWIG.
This is my code example:
in SimpleStruct.h:
#pragma once
struct SimpleStruct
{
int a;
double b;
};
in exmaple.h (this one is compiled with SWIG) to Lua library:
#pragma once
#include "SimpleStruct.h"
#include <iostream>
class TestClass
{
public:
TestClass()
{
std::cout<<"TestClass created"<<std::endl;
}
~TestClass() {}
void ReadSimpleStruct(void * tmp)
{
std::cout<<"reading pointer: "<<std::endl;
SimpleStruct * pp = reinterpret_cast< SimpleStruct * >(tmp);
std::cout<<"Simple Struct: " << pp->a << " " << pp->b << std::endl;
}
};
in example.cpp only:
#include "example.h"
and this is my main program (LuaTest.cpp):
extern "C"
{
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
}
#include <iostream>
#include "SimpleStruct.h"
int main(int argc, char ** argv)
{
lua_State * L = luaL_newstate();
luaL_openlibs(L);
SimpleStruct * ss = new SimpleStruct();
ss->a = 1;
ss->b = 2;
lua_pushlightuserdata(L,ss);
lua_setglobal( L, "myptr");
int s = luaL_dostring(L, "require('example')");
s = luaL_dostring(L, "mc = example.TestClass()");
s = luaL_dostring(L, "mc:ReadSimpleStruct(myptr)");
if(s)
{
printf("Error: %s \n", lua_tostring(L, -1));
lua_pop(L, 1);
}
lua_close(L);
std::cout<<"done"<<std::endl;
return 0;
}
example.i (copied from Lua examples in SWIG):
/* File : example.i */
%module example
%{
#include "example.h"
%}
/* Let's just grab the original header file here */
%include "example.h"
and I compile everything as follows:
swig -c++ -lua example.i
g++ -c -fpic example.cpp example_wrap.cxx -I/usr/local/include -I/usr/include/lua5.2/
g++ -shared example.o example_wrap.o -o example.so
g++ LuaTest.cpp -o luatest -llua5.2 -I/usr/include/lua5.2/ -Wall
on Ubuntu 16.04 (and on osx, with different paths and the same result).
In the last line of Lua script I've got segmentation fault (when I try to access pp->a in "mc:ReadSimpleStruct(myptr)").
So my question is: how can I provide a pointer to c++ object to the loaded Lua library using Lua light userdata?
In general: I have in my main program a class with game parameters and objects, and I would like to provide a pointer to that class to other loaded Lua libraries compiled with a SWIG.
With use of a debugger (or just printing a little extra inside TestClass::ReadSimpleStruct) we can see at least the superficial cause of the segfault quite quickly. The value of the tmp argument to your function is 0x20 on my test setup. That's clearly not right, but understanding why and how to fix it takes a little more investigation.
As a starting point I added one more call to luaL_dostring(L, "print(myptr)") and used a debugger to check that the global variable was indead working as intended. For good measure I added some assert statements after each call to luaL_dostring, because you're actually only checking the return value of the last one, although here that didn't really make any difference.
Having not exactly written much Lua in my life I looked a the documentation for 'Light userdata', which I saw you were using but didn't know what it was. It sounds ideal:
A light userdatum is a value that represents a C pointer (that is, a void * value)
The problem is though that if we inspect the generated example_wrap.cxx file we can see that SWIG is actually trying to be more clever than that and, if we trace the code for arg2 before the generated call to (arg1)->ReadSimpleStruct(arg2) we can see that it's calling SWIG_ConvertPtr (which eventually calls SWIG_Lua_ConvertPtr), which then does:
lua_touserdata(L, index);
//... Some typing stuff from the macro
*ptr=usr->ptr; // BOOM!
I.e. what you're doing is not what SWIG expects to see for void *, SWIG is expecting to manage them all through its typing system as return values from other functions or SWIG managed globals. (I'm slightly surprised that SWIG let this get as far as a segfault without raising an error, but I think it's because void* is being special cased somewhat)
This old question served as quite a nice example to confirm my understanding of lua_pushlightuserdata. Basically we will need to write our own typemap to make this function argument get handled the way you're trying to use it (if you really do want to not let SWIG manage this?). What we want to do is very simple though. The usage case here is also substantially similar to the example I linked, except that the variable we're after when we call lua_touserdata is a function argument. That means it's at a positive offset into the stack, not a negative one. SWIG in fact can tell us what the offset inside our typemape with the $input substitution, so our typemap doesn't only work for the 1st argument to a member function.
So our typemap, which does this for any function argument void * tmp inside our modified example.i file becomes:
%module example
%{
#include "example.h"
%}
%typemap(in) void * tmp %{
$1 = lua_touserdata(L, $input);
%}
%include "example.h"
And that then compiles and runs with:
swig -c++ -lua example.i
g++ -fPIC example_wrap.cxx -I/usr/local/include -I/usr/include/lua5.2/ -shared -o example.so && g++ -Wall -Wextra LuaTest.cpp -o luatest -llua5.2 -I/usr/include/lua5.2/
./luatest
TestClass created
userdata: 0x11d0730
reading pointer: 0x11d0730
Simple Struct: 1 2
done

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.