1

I try to write python bindings with pybind11. Since I also need non simple python types I have to create custom type casters. My problem is that whenever I call get_point from python the LPoint2d will be returned but the following command will lead to a segmentation fault.

As far as I understand the python reference counting may cause that error. But since I am quite new to pybind11 I am not sure. How do I avoid that error?

In the following example I used an LPoint2d from panda3d as an example type. But the error also occurs with other types (e.g. cv2.KeyPoint).

Python:

>>> from panda3d.core import LPoint2d
>>> import test_module as tm
>>> foo = tm.Test()
>>> foo.get_point()
LPoint2d(0, 0)
>>> bar = 42
Segmentation fault (core dumped)

c++ code:

#include <pybind11/pybind11.h>

#include <panda3d/lpoint2.h>

class Test
{
public:
    Test(){}

    LPoint2d getPoint(){
        return m_point;
    }

    void setPoint(LPoint2d &p){
        m_point = p;
    }

private:
    LPoint2d m_point;
};

namespace py = pybind11;

namespace pybind11 { namespace detail {

template <> struct type_caster<LPoint2d> {
    public:

    PYBIND11_TYPE_CASTER(LPoint2d, _("LPoint2d"));

        bool load(handle src, bool) {
            value = LPoint2d(4,2);
            return true;
        }

        static handle cast(const LPoint2d &lp, return_value_policy, handle defval) {
            py::object p3d = py::module::import("panda3d.core");
            py::object plp = p3d.attr("LPoint2d")(lp.get_x(), lp.get_y());

            return {plp.ptr()};
        }
};

}}

PYBIND11_MODULE(test_module, m) {

  py::class_<Test>(m, "Test")
    .def(py::init<>())
    .def("get_point", &Test::getPoint)
    .def("set_point", &Test::setPoint)
    ;

}

1 Answers1

2

It seems that just increasing the reference counter solves this issue.

static handle cast(const LPoint2d &src, return_value_policy policy, handle parent) {
        py::object p3d = py::module::import("panda3d.core");
        py::object plp = p3d.attr("LPoint2d")(src.get_x(), src.get_y());
        plp.inc_ref();

        return plp.ptr();
    }

This is probably not a good solution but it seems to work for now.

  • I came across this answer when doing something similar in my code: importing a Python module, then returning a python object constructed from it. The reason this works is: 1. The Python module returns a `pybind11::object`, which will automatically decrease the reference count on destruction. 2. `pybind11::handle` does not automatically handle reference counting. 3. At the end of the function, without the manual reference count increase, the reference count would decreased to 0, which allows the object to be destroyed. So the inc_ref is there to cancel out the automatic dec_ref. – Alex Trotta Jul 30 '23 at 06:01
  • Also as a simpler alternative: `return plp.release()` - this essentially disables the automatic reference count decrement. – Alex Trotta Jul 30 '23 at 07:31