gsakkis
gsakkis

Reputation: 1649

ctypes wrapper for function returning by value objects of a C++ class with destructor

Can ctypes wrap functions that return objects (not pointers/references) of a C++ class with a destructor? The example below segfaults when calling 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)

Compile and run it with:

g++ -c -fPIC foo.cpp -o foo.o && g++ foo.o -shared -o foolib.so && python foo.py 

Output:

init_point_by_ref called
cleanup_point called
Point destructor called
init_point_by_value called
Segmentation fault (core dumped)

Upvotes: 0

Views: 986

Answers (1)

Mark Tolonen
Mark Tolonen

Reputation: 177406

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

Upvotes: 2

Related Questions