How to use C++ classes with ctypes?
The short story is that there is no standard binary interface for C++ in the way that there is for C. Different compilers output different binaries for the same C++ dynamic libraries, due to name mangling and different ways to handle the stack between library function calls.
So, unfortunately, there really isn't a portable way to access C++ libraries in general. But, for one compiler at a time, it's no problem.
This blog post also has a short overview of why this currently won't work. Maybe after C++0x comes out, we'll have a standard ABI for C++? Until then, you're probably not going to have any way to access C++ classes through Python's ctypes
.
Python, using ctypes to create C++ class wrapper
Everything works fine... but you forgot to pass along the return value of the C function in the method MyMath.FunAdd!
def FunAdd(self, a, b):
return self.FunMath.MyAdd(self.obj, a, b)
^^^^^^
Using Ctypes to call complicated c++ code
(1) Did I use extern "C" correctly?
Don't put extern ”C"
around the method definitions.
The purpose of extern "C"
is to tell the compiler that some function needs to be accessible as a C function—that is, called by C code, or by ctypes
or some other library that loads and calls functions out of shared objects.
The only function you want to call through ctypes
is printStuff
, so only that function should be extern "C"
.
Trying to extern "C"
the methods will probably harmlessly do nothing with g++
(they'll still end up named something like __ZN3foo3barEv
in the export table), but you may get a warning, or even a non-running library, with a different compiler.
(2) How should I compile these three files into a shared library? I use g++.
You probably want to write a Makefile
or use some other build system, but if you want to do it manually:
g++ -shared -o foo.so foo.cpp mainFunc.cpp
Depending on your platform, you may also need to manually specify -fPIC
or -fpic
.1
If you want to do the steps separately:
g++ -c foo.cpp
g++ -c mainFunc.cpp
g++ -shared -o foo.so foo.o mainFunc.o
In this case, if a PIC flag is needed, it goes on the first two lines.
And now, to use it from Python:
$ python3
>>> import ctypes
>>> foo = ctypes.cdll.LoadLibrary('foo.so')
>>> foo.printStuff(10)
Printing...
15
>>> foo.printStuff("abc")
Printing...
116480373
Of course it's generally better to set the argtypes
and restype
instead of making ctypes
guess. It guessed that the Python int
10 should be converted to the C int
10, which worked great, but it also guessed that the Python str
"abc" should be converted to a C const char *
, and you end up with the low 32 bits of the pointer to the string buffer being used as an int
. So:
>>> foo.printStuff.argtypes = [ctypes.c_int]
>>> foo.printStuff.restype = None
>>> foo.printStuff(10)
Printing...
15
>>> foo.printStuff("abc")
ArgumentError: argument 1: <class 'TypeError'>: wrong type
And you may want to write a foo.py
that wraps that up:
import ctypes
foo = ctypes.cdll.LoadLibrary('foo.so')
foo.printStuff.argtypes = [ctypes.c_int]
foo.printStuff.restype = None
printStuff = foo.printStuff
1. From quick tests, I didn't need it for x86_64 macOS, x86 macOS, or x86_64 Linux, but PowerPC Linux needs -fpic
and ARM64 Linux needs -fPIC
. However, I didn't actually run all of those. And, other than macOS (where I made sure to test with Homebrew gcc 8.2 and Apple Clang 9.1) I don't know which compiler version I had.
Calling Functions from within class using CTypes
ctypes
understands C linkage and a C++ library normally needs to create extern "C"
wrapper functions. In this special case you can force ctypes
to load the name-decorated C++ symbol for the static method, but it isn't recommended.
Here's both demonstrated:
test.cpp
#include <stdio.h>
#ifdef _WIN32
# define API __declspec(dllexport) // Windows requires explicit export of functions.
#else
# define API
#endif
class API Clibrary // exported to see name decoration, but not needed
{
public:
Clibrary() {};
~Clibrary() {};
static void prompt() { printf("hello world\n"); }
};
// wrapper function with C linkage
extern "C" API void prompt() { Clibrary::prompt(); }
List of symbols exported from the DLL:
ordinal hint RVA name
1 0 00001020 ??0Clibrary@@QEAA@XZ
2 1 00001030 ??1Clibrary@@QEAA@XZ
3 2 00001040 ??4Clibrary@@QEAAAEAV0@AEBV0@@Z
4 3 00001050 ?prompt@Clibrary@@SAXXZ
5 4 00001000 prompt
test.py
import ctypes
libObject = ctypes.CDLL('./test')
# Hack but works. Decorated name is not standard and could
# be different using a different compiler.
other = getattr(libObject,'?prompt@Clibrary@@SAXXZ')
other()
libObject.prompt() # best and portable
Output:
hello world
hello world
Loading C++ class in Python
I've found a solution starting from @Scheff comment.
On Linux (I've tried on Ubuntu 16, GCC 4.4.0 and Python 3.6), my question code works well without modifications (both on the code and on the compilation instructions).
On Windows, I modified the extern "C"
block in this way:
extern "C"
{
__declspec(dllexport) Foo* Foo_new(int n) {return new Foo(n);}
__declspec(dllexport) void Foo_bar(Foo* foo) {foo->bar();}
__declspec(dllexport) int Foo_foobar(Foo* foo, int n) {return foo->foobar(n);}
}
and I recompiled as before.
Thereafter I was able to import the module and the C++ class as described in the question link.
For implementing the C++ print function please see this question.
Using ctypes to call a C++ method with parameters from Python results in Don't know how to convert parameter error
You have to match the arguments exactly. Set the .argtypes
and .restype
of every function you use so ctypes
can properly marshal the parameters to C and back again. If you do not set .restype
ctypes
assumes the return value is c_int
(typically a signed 32-bit integer) instead of a (possibly 64-bit) pointer.
Here's a working example. I didn't flesh out every function because one should be sufficient. Tested on both 32- and 64-bit Python.
test.cpp (built with MS compiler, cl /LD /EHsc /W4 test.cpp):
#include <stdio.h>
// Needed to export functions on Windows
#ifdef _WIN32
# define API __declspec(dllexport)
#else
# define API
#endif
class CMatrix
{
public:
CMatrix(int d1) : s1(d1) { m = new float[d1]; }
~CMatrix() { delete [] m; }
const float* Get(int& s) { s = s1; return m; }
void Set(int x, float f) { m[x] = f; }
int s1;
float *m;
};
extern "C" {
API CMatrix* CMatrix_new(int i) {return new CMatrix(i); }
API const float* CMatrix_Get(CMatrix* cm, int& x) { return cm->Get(x); }
API void CMatrix_Set(CMatrix* cm, int x, float f) { cm->Set(x, f); }
API void CMatrix_delete(CMatrix* cm) { delete cm; }
}
test.py
import ctypes as ct
# For type checking the returned pointer.
class _CMatrix(ct.c_void_p) : pass
PCMatrix = ct.POINTER(_CMatrix)
class CMatrix:
_dll = ct.CDLL('./test')
_dll.CMatrix_new.argtypes = ct.c_int,
_dll.CMatrix_new.restype = PCMatrix
_dll.CMatrix_Get.argtypes = PCMatrix, ct.POINTER(ct.c_int)
_dll.CMatrix_Get.restype = ct.POINTER(ct.c_float)
_dll.CMatrix_Set.argtypes = PCMatrix, ct.c_int, ct.c_float
_dll.CMatrix_Set.restype = None
_dll.CMatrix_delete.argtypes = PCMatrix,
_dll.CMatrix_delete.restype = None
def __init__(self, i):
self.obj = self._dll.CMatrix_new(i)
def Set(self, x, f):
self._dll.CMatrix_Set(self.obj, x, f)
def Get(self):
size = ct.c_int()
m = self._dll.CMatrix_Get(self.obj, ct.byref(size))
return m[:size.value]
def __del__(self):
self._dll.CMatrix_delete(self.obj)
cm = CMatrix(2)
cm.Set(0, 1.5)
cm.Set(1, 2.5)
print(cm.Get())
Output:
[1.5, 2.5]
ctypes wrapper for function returning by value objects of a C++ class with destructor
Compile with warnings enabled and I get:
x.cpp(17): warning C4190: 'init_point_by_value' has C-linkage specified, but returns UDT 'Point'
which is incompatible with C
This is due to the object having a destructor. Remove the destructor and it should accept it.
Another issue is the return type of init_point_by_value
is incorrect. It isn't a POINTER(Point)
but just a Point
:
lib.init_point_by_value.restype = Point
Finally, don't try to free the returned-by-value object.
Result with the fixes as follows (adapted slightly for my Windows system):
test.cpp
#include <iostream>
#define API __declspec(dllexport) // Windows-specific export
extern "C" {
using namespace std;
struct Point {
int x;
int y;
};
API Point init_point_by_value(int x, int y) {
cout << "init_point_by_value called" << endl;
Point p;
p.x = x;
p.y = y;
return p;
}
API Point& init_point_by_ref(int x, int y) {
cout << "init_point_by_ref called" << endl;
Point* p = new Point;
p->x = x;
p->y = y;
return *p;
}
API void cleanup_point(Point* point) {
cout << "cleanup_point called" << endl;
if (point) {
delete point;
}
}
}
test.py
import ctypes
class Point(ctypes.Structure):
_fields_ = [
('x', ctypes.c_int),
('y', ctypes.c_int),
]
def setup_lib(lib_path):
lib = ctypes.cdll.LoadLibrary(lib_path)
lib.cleanup_point.argtypes = [ctypes.POINTER(Point)]
lib.init_point_by_value.argtypes = [ctypes.c_int, ctypes.c_int]
lib.init_point_by_value.restype = Point
lib.init_point_by_ref.argtypes = [ctypes.c_int, ctypes.c_int]
lib.init_point_by_ref.restype = ctypes.POINTER(Point)
return lib
lib = setup_lib('test')
p1 = lib.init_point_by_ref(3, 4)
print(p1.contents.x,p1.contents.y)
lib.cleanup_point(p1)
p2 = lib.init_point_by_value(5, 6)
print(p2.x,p2.y)
Output
init_point_by_ref called
3 4
cleanup_point called
init_point_by_value called
5 6
Python ctypes: wraping c++ class with operators
Problem is, you're trying to cast a Python object to a void *
, rather than the void *
you already attached to that object's obj
attribute.
It should be as simple as changing...
def __eq__(self, other):
return lib.compare_edge(self.obj, c_void_p(other))
...to...
def __eq__(self, other):
return lib.compare_edge(self.obj, other.obj)
The explicit call to c_void_p
should be unnecessary, since you already declared the types in the line...
lib.compare_edge.argtypes = [c_void_p, c_void_p]
Related Topics
G++ Linking Order Dependency When Linking C Code to C++ Code
Size of Array Passed to C++ Function
C++ Overload Static Function with Non-Static Function
C++ Warning: Deprecated Conversion from String Constant to 'Char*' [-Wwrite-Strings]
Replacement for Deprecated Register Keyword C++ 11
May I Take the Address of the One-Past-The-End Element of an Array
Access Variable Value Using String Representing Variable's Name in C++
Using Regex Lookbehinds in C++11
One VS2010 Bug ? Allowing Binding Non-Const Reference to Rvalue Without Even a Warning
Is Auto as a Parameter in a Regular Function a Gcc 4.9 Extension
Using Float Gives "Call to Overloaded Function Is Ambiguous" Error
What Is the Purpose of Allocating a Specific Amount of Memory for Arrays in C++
Why Does Gcc Compiler Output Pow(10,2) as 99 Not 100
Should We Pass a Shared_Ptr by Reference or by Value
Best Documentation for Boost:Asio
Is Adding to a "Char *" Pointer Ub, When It Doesn't Actually Point to a Char Array