From b16421edb143bceb812f8644281029e6224deeac Mon Sep 17 00:00:00 2001 From: Wenzel Jakob Date: Wed, 22 Mar 2017 22:04:00 +0100 Subject: [PATCH] Nicer API to pass py::capsule destructor (#752) * nicer py::capsule destructor mechanism * added destructor-only version of capsule & tests * added documentation for module destructors (fixes #733) --- docs/advanced/misc.rst | 14 +++++++++++++ include/pybind11/eigen.h | 2 +- include/pybind11/pybind11.h | 4 ++-- include/pybind11/pytypes.h | 40 ++++++++++++++++++++++++++++++++++--- tests/test_python_types.cpp | 18 +++++++++++++++++ tests/test_python_types.py | 21 +++++++++++++++++++ 6 files changed, 93 insertions(+), 6 deletions(-) diff --git a/docs/advanced/misc.rst b/docs/advanced/misc.rst index 163fb0ee9..d98466512 100644 --- a/docs/advanced/misc.rst +++ b/docs/advanced/misc.rst @@ -170,6 +170,20 @@ would be then able to access the data behind the same pointer. .. [#f6] https://docs.python.org/3/extending/extending.html#using-capsules +Module Destructors +================== + +pybind11 does not provide an explicit mechanism to invoke cleanup code at +module destruction time. In rare cases where such functionality is required, it +is possible to emulate it using Python capsules with a destruction callback. + +.. code-block:: cpp + + auto cleanup_callback = []() { + // perform cleanup here -- this function is called with the GIL held + }; + + m.add_object("_cleanup", py::capsule(cleanup_callback)); Generating documentation using Sphinx ===================================== diff --git a/include/pybind11/eigen.h b/include/pybind11/eigen.h index e9c44e565..6abe8c48f 100644 --- a/include/pybind11/eigen.h +++ b/include/pybind11/eigen.h @@ -235,7 +235,7 @@ handle eigen_ref_array(Type &src, handle parent = none()) { // not the Type of the pointer given is const. template ::value>> handle eigen_encapsulate(Type *src) { - capsule base(src, [](PyObject *o) { delete static_cast(PyCapsule_GetPointer(o, nullptr)); }); + capsule base(src, [](void *o) { delete static_cast(o); }); return eigen_ref_array(*src, base); } diff --git a/include/pybind11/pybind11.h b/include/pybind11/pybind11.h index 42a19acb9..5976a36d8 100644 --- a/include/pybind11/pybind11.h +++ b/include/pybind11/pybind11.h @@ -294,8 +294,8 @@ protected: rec->def->ml_meth = reinterpret_cast(*dispatcher); rec->def->ml_flags = METH_VARARGS | METH_KEYWORDS; - capsule rec_capsule(rec, [](PyObject *o) { - destruct((detail::function_record *) PyCapsule_GetPointer(o, nullptr)); + capsule rec_capsule(rec, [](void *ptr) { + destruct((detail::function_record *) ptr); }); object scope_module; diff --git a/include/pybind11/pytypes.h b/include/pybind11/pytypes.h index e8780912c..900c57564 100644 --- a/include/pybind11/pytypes.h +++ b/include/pybind11/pytypes.h @@ -1004,10 +1004,44 @@ public: PYBIND11_OBJECT_DEFAULT(capsule, object, PyCapsule_CheckExact) PYBIND11_DEPRECATED("Use reinterpret_borrow() or reinterpret_steal()") capsule(PyObject *ptr, bool is_borrowed) : object(is_borrowed ? object(ptr, borrowed) : object(ptr, stolen)) { } - explicit capsule(const void *value, void (*destruct)(PyObject *) = nullptr) - : object(PyCapsule_New(const_cast(value), nullptr, destruct), stolen) { - if (!m_ptr) pybind11_fail("Could not allocate capsule object!"); + + explicit capsule(const void *value) + : object(PyCapsule_New(const_cast(value), nullptr, nullptr), stolen) { + if (!m_ptr) + pybind11_fail("Could not allocate capsule object!"); } + + PYBIND11_DEPRECATED("Please pass a destructor that takes a void pointer as input") + capsule(const void *value, void (*destruct)(PyObject *)) + : object(PyCapsule_New(const_cast(value), nullptr, destruct), stolen) { + if (!m_ptr) + pybind11_fail("Could not allocate capsule object!"); + } + + capsule(const void *value, void (*destructor)(void *)) { + m_ptr = PyCapsule_New(const_cast(value), nullptr, [](PyObject *o) { + auto destructor = reinterpret_cast(PyCapsule_GetContext(o)); + void *ptr = PyCapsule_GetPointer(o, nullptr); + destructor(ptr); + }); + + if (!m_ptr) + pybind11_fail("Could not allocate capsule object!"); + + if (PyCapsule_SetContext(m_ptr, (void *) destructor) != 0) + pybind11_fail("Could not set capsule context!"); + } + + capsule(void (*destructor)()) { + m_ptr = PyCapsule_New(reinterpret_cast(destructor), nullptr, [](PyObject *o) { + auto destructor = reinterpret_cast(PyCapsule_GetPointer(o, nullptr)); + destructor(); + }); + + if (!m_ptr) + pybind11_fail("Could not allocate capsule object!"); + } + template operator T *() const { T * result = static_cast(PyCapsule_GetPointer(m_ptr, nullptr)); if (!result) pybind11_fail("Unable to extract capsule contents!"); diff --git a/tests/test_python_types.cpp b/tests/test_python_types.cpp index 399129fad..5696239b4 100644 --- a/tests/test_python_types.cpp +++ b/tests/test_python_types.cpp @@ -470,6 +470,24 @@ test_initializer python_types([](py::module &m) { m.def("return_none_bool", []() -> bool * { return nullptr; }); m.def("return_none_int", []() -> int * { return nullptr; }); m.def("return_none_float", []() -> float * { return nullptr; }); + + m.def("return_capsule_with_destructor", + []() { + py::print("creating capsule"); + return py::capsule([]() { + py::print("destructing capsule"); + }); + } + ); + + m.def("return_capsule_with_destructor_2", + []() { + py::print("creating capsule"); + return py::capsule((void *) 1234, [](void *ptr) { + py::print("destructing capsule: {}"_s.format((size_t) ptr)); + }); + } + ); }); #if defined(_MSC_VER) diff --git a/tests/test_python_types.py b/tests/test_python_types.py index 7ca020e89..7956c7cc6 100644 --- a/tests/test_python_types.py +++ b/tests/test_python_types.py @@ -512,3 +512,24 @@ def test_builtins_cast_return_none(): assert m.return_none_bool() is None assert m.return_none_int() is None assert m.return_none_float() is None + + +def test_capsule_with_destructor(capture): + import pybind11_tests as m + with capture: + a = m.return_capsule_with_destructor() + del a + pytest.gc_collect() + assert capture.unordered == """ + creating capsule + destructing capsule + """ + + with capture: + a = m.return_capsule_with_destructor_2() + del a + pytest.gc_collect() + assert capture.unordered == """ + creating capsule + destructing capsule: 1234 + """