Обертка 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
Другие вопросы по тегам