Обертка ctypes для функции, возвращаемой объектами-значениями класса C ++ с деструктором - PullRequest
0 голосов
/ 25 июня 2018

Может ли 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 Ответ

0 голосов
/ 26 июня 2018

Компиляция с включенными предупреждениями, и я получаю:

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
...