Skip to content

Nicer API to pass py::capsule destructor #752

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Mar 22, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions docs/advanced/misc.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
=====================================
Expand Down
2 changes: 1 addition & 1 deletion include/pybind11/eigen.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down
4 changes: 2 additions & 2 deletions include/pybind11/pybind11.h
Original file line number Diff line number Diff line change
Expand Up @@ -294,8 +294,8 @@ class cpp_function : public function {
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;
Expand Down
38 changes: 36 additions & 2 deletions include/pybind11/pytypes.h
Original file line number Diff line number Diff line change
Expand Up @@ -1004,10 +1004,44 @@ class capsule : public object {
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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe just deprecate this constructor for backward compatibility? (Just without the = nullptr default and explicit).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good idea!


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!");
Expand Down
18 changes: 18 additions & 0 deletions tests/test_python_types.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
21 changes: 21 additions & 0 deletions tests/test_python_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
"""