Commit b16421ed by Wenzel Jakob Committed by GitHub

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)
parent ab26259c
......@@ -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
=====================================
......
......@@ -235,7 +235,7 @@ handle eigen_ref_array(Type &src, handle parent = none()) {
// not the Type of the pointer given is const.
template <typename props, typename Type, typename = enable_if_t<is_eigen_dense_plain<Type>::value>>
handle eigen_encapsulate(Type *src) {
capsule base(src, [](PyObject *o) { delete static_cast<Type *>(PyCapsule_GetPointer(o, nullptr)); });
capsule base(src, [](void *o) { delete static_cast<Type *>(o); });
return eigen_ref_array<props>(*src, base);
}
......
......@@ -294,8 +294,8 @@ protected:
rec->def->ml_meth = reinterpret_cast<PyCFunction>(*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;
......
......@@ -1004,10 +1004,44 @@ public:
PYBIND11_OBJECT_DEFAULT(capsule, object, PyCapsule_CheckExact)
PYBIND11_DEPRECATED("Use reinterpret_borrow<capsule>() or reinterpret_steal<capsule>()")
capsule(PyObject *ptr, bool is_borrowed) : object(is_borrowed ? object(ptr, borrowed) : object(ptr, stolen)) { }
explicit capsule(const void *value, void (*destruct)(PyObject *) = nullptr)
explicit capsule(const void *value)
: object(PyCapsule_New(const_cast<void *>(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<void*>(value), nullptr, destruct), stolen) {
if (!m_ptr) pybind11_fail("Could not allocate capsule object!");
if (!m_ptr)
pybind11_fail("Could not allocate capsule object!");
}
capsule(const void *value, void (*destructor)(void *)) {
m_ptr = PyCapsule_New(const_cast<void *>(value), nullptr, [](PyObject *o) {
auto destructor = reinterpret_cast<void (*)(void *)>(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<void *>(destructor), nullptr, [](PyObject *o) {
auto destructor = reinterpret_cast<void (*)()>(PyCapsule_GetPointer(o, nullptr));
destructor();
});
if (!m_ptr)
pybind11_fail("Could not allocate capsule object!");
}
template <typename T> operator T *() const {
T * result = static_cast<T *>(PyCapsule_GetPointer(m_ptr, nullptr));
if (!result) pybind11_fail("Unable to extract capsule contents!");
......
......@@ -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)
......
......@@ -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
"""
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment