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. ...@@ -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 .. [#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 Generating documentation using Sphinx
===================================== =====================================
......
...@@ -235,7 +235,7 @@ handle eigen_ref_array(Type &src, handle parent = none()) { ...@@ -235,7 +235,7 @@ handle eigen_ref_array(Type &src, handle parent = none()) {
// not the Type of the pointer given is const. // not the Type of the pointer given is const.
template <typename props, typename Type, typename = enable_if_t<is_eigen_dense_plain<Type>::value>> template <typename props, typename Type, typename = enable_if_t<is_eigen_dense_plain<Type>::value>>
handle eigen_encapsulate(Type *src) { 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); return eigen_ref_array<props>(*src, base);
} }
......
...@@ -294,8 +294,8 @@ protected: ...@@ -294,8 +294,8 @@ protected:
rec->def->ml_meth = reinterpret_cast<PyCFunction>(*dispatcher); rec->def->ml_meth = reinterpret_cast<PyCFunction>(*dispatcher);
rec->def->ml_flags = METH_VARARGS | METH_KEYWORDS; rec->def->ml_flags = METH_VARARGS | METH_KEYWORDS;
capsule rec_capsule(rec, [](PyObject *o) { capsule rec_capsule(rec, [](void *ptr) {
destruct((detail::function_record *) PyCapsule_GetPointer(o, nullptr)); destruct((detail::function_record *) ptr);
}); });
object scope_module; object scope_module;
......
...@@ -1004,10 +1004,44 @@ public: ...@@ -1004,10 +1004,44 @@ public:
PYBIND11_OBJECT_DEFAULT(capsule, object, PyCapsule_CheckExact) PYBIND11_OBJECT_DEFAULT(capsule, object, PyCapsule_CheckExact)
PYBIND11_DEPRECATED("Use reinterpret_borrow<capsule>() or reinterpret_steal<capsule>()") 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)) { } 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) { : 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 { template <typename T> operator T *() const {
T * result = static_cast<T *>(PyCapsule_GetPointer(m_ptr, nullptr)); T * result = static_cast<T *>(PyCapsule_GetPointer(m_ptr, nullptr));
if (!result) pybind11_fail("Unable to extract capsule contents!"); if (!result) pybind11_fail("Unable to extract capsule contents!");
......
...@@ -470,6 +470,24 @@ test_initializer python_types([](py::module &m) { ...@@ -470,6 +470,24 @@ test_initializer python_types([](py::module &m) {
m.def("return_none_bool", []() -> bool * { return nullptr; }); m.def("return_none_bool", []() -> bool * { return nullptr; });
m.def("return_none_int", []() -> int * { return nullptr; }); m.def("return_none_int", []() -> int * { return nullptr; });
m.def("return_none_float", []() -> float * { 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) #if defined(_MSC_VER)
......
...@@ -512,3 +512,24 @@ def test_builtins_cast_return_none(): ...@@ -512,3 +512,24 @@ def test_builtins_cast_return_none():
assert m.return_none_bool() is None assert m.return_none_bool() is None
assert m.return_none_int() is None assert m.return_none_int() is None
assert m.return_none_float() 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