Обертка ctypes для функции, возвращаемой объектами-значениями класса C++ с деструктором
Можно ctypes
обернуть функции, которые возвращают объекты (не указатели / ссылки) класса C++ с деструктором? Пример ниже segfaults при вызове lib.init_point_by_value
:
foo.cpp:
#include <iostream>
extern "C" {
using namespace std;
struct Point {
int x;
int y;
~Point();
};
Point::~Point() {
cout << "Point destructor called" << endl;
}
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;
}
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;
}
void cleanup_point(Point* point) {
cout << "cleanup_point called" << endl;
if (point) {
delete point;
}
}
}
foo.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 = ctypes.POINTER(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('./foolib.so')
p1 = lib.init_point_by_ref(3, 4)
lib.cleanup_point(p1)
# seg faults
p2 = lib.init_point_by_value(5, 6)
lib.cleanup_point(p2)
Скомпилируйте и запустите:
g++ -c -fPIC foo.cpp -o foo.o && g++ foo.o -shared -o foolib.so && python foo.py
Выход:
init_point_by_ref called
cleanup_point called
Point destructor called
init_point_by_value called
Segmentation fault (core dumped)
1 ответ
Решение
Скомпилируйте с включенными предупреждениями, и я получу:
x.cpp(17): warning C4190: 'init_point_by_value' has C-linkage specified, but returns UDT 'Point'
which is incompatible with C
Это связано с тем, что объект имеет деструктор. Удалите деструктор, и он должен принять его.
Другая проблема заключается в типе возврата init_point_by_value
это неверно. Это не POINTER(Point)
но просто Point
:
lib.init_point_by_value.restype = Point
Наконец, не пытайтесь освободить возвращаемый по значению объект.
Результат с исправлениями следующим образом (немного адаптирован для моей системы Windows):
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)
Выход
init_point_by_ref called
3 4
cleanup_point called
init_point_by_value called
5 6