Skip to content
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

feat: enable type-safe interoperability between different independent Python/C++ bindings systems. #5296

Merged
merged 49 commits into from
Sep 13, 2024
Merged
Show file tree
Hide file tree
Changes from 44 commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
3f522e5
`self.__cpp_transporter__()` proof of concept: Enable passing C++ poi…
Aug 11, 2024
900bc26
Include cleanup (mainly to resolve PyPy build failures).
Aug 11, 2024
2fbe930
Fix clang-tidy errors.
Aug 11, 2024
7c951c5
Merge branch 'master' into cpp_transporter
Aug 25, 2024
2dd6c7a
Resolve `error: extra
Aug 25, 2024
2617a04
Merge branch 'master' into cpp_transporter
Aug 27, 2024
87ebc39
factor out platform_abi_id.h from internals.h (no functional changes)
Aug 28, 2024
3ccea8c
factor out internals_version.h from internals.h (no functional changes)
Aug 28, 2024
a09d600
Update CMakeLists.txt, tests/extra_python_package/test_files.py
Aug 28, 2024
2fde1bd
Revert "factor out internals_version.h from internals.h (no functiona…
Aug 28, 2024
0a29d05
Remove internals_version.h from CMakeLists.txt, tests/extra_python_pa…
Aug 28, 2024
59f3036
Merge branch 'master' into cpp_transporter
Aug 28, 2024
d2479aa
`.__cpp_transporter__()` implementation: compare `pybind11_platform_a…
Aug 28, 2024
39ca2aa
Add PremiumTraveler
Aug 28, 2024
fc57b44
Rename test_cpp_transporter_traveler_type.h -> test_cpp_transporter_t…
Aug 28, 2024
3063c03
Expand tests: `PremiumTraveler`, `get_points()`
Aug 28, 2024
251bf91
Shuffle order of tests (no real changes).
Aug 28, 2024
2df540e
Move `__cpp_transporter__` lambda to `py::cpp_transporter()` regular …
Aug 28, 2024
757f885
Use `type_caster_generic::load(self)` instead of `cast<T *>(self)`
Aug 28, 2024
3951b63
Pass `const std::type_info *` via `py::capsule` (instead of `cpp_type…
Aug 29, 2024
14265bf
Merge branch 'master' into cpp_transporter
Aug 29, 2024
981a2a7
Make platform_abi_id.h completely stand-alone.
Aug 29, 2024
4e9a0c7
rename exo_planet.cpp -> exo_planet_pybind11.cpp
Aug 29, 2024
777ab9b
Add exo_planet_c_api.cpp (incomplete).
Aug 29, 2024
e220b42
Fix silly oversight (wrong filename in `#include`).
Aug 30, 2024
aa81066
Resolve clang-tidy errors:
Aug 30, 2024
caccc6f
Implement exo_planet_c_api GetLuggage(), GetPoints()
Aug 30, 2024
78c3c88
Move new code from test_cpp_transporter_traveler_bindings.h to pybind…
Aug 30, 2024
926c2ae
Fix oversight.
Aug 30, 2024
b442699
Unconditionally add `__cpp_transporter__` method to all `py::class_` …
Aug 30, 2024
e611471
Back out pybind11/detail/platform_abi_id.h for now. Maximizing reusab…
Aug 30, 2024
0a97c97
Small cleanup.
Aug 30, 2024
8a27b98
Restore and add to `test_call_cpp_transporter_*()`
Aug 30, 2024
80550a9
Ensure https://github.com/pybind/pybind11/issues/3788 does not bite a…
Aug 30, 2024
80ca683
`class_dunder_cpp_transporter()`: replace `obj.cast<std::string>()` w…
Aug 30, 2024
9cffdc9
Add (simple) copyright notices in all newly added files.
Aug 30, 2024
ce45db1
Globally replace cpp_transporter with cpp_conduit
Aug 30, 2024
4b77d6c
style: pre-commit fixes
pre-commit-ci[bot] Aug 30, 2024
1bfb369
IWYU fixes
Aug 31, 2024
afb30a6
Rename `class_dunder_cpp_conduit()` -> `cpp_conduit_method()`
Aug 31, 2024
bae9959
Change `pybind11_platform_abi_id`, `pointer_kind` argument types from…
Aug 31, 2024
31134bc
Systematically rename `cap_cpp_type_info` -> `cpp_type_info_capsule` …
Sep 1, 2024
8ccddce
Systematically replace `cpp_type_info_capsule` `name`: `"const std::t…
Sep 1, 2024
b85bf0f
Fix sort order accident in tests/CMakeLists.txt
Sep 1, 2024
4b69dd8
Apply suggestions from code review
henryiii Sep 12, 2024
19106f5
style: pre-commit fixes
pre-commit-ci[bot] Sep 12, 2024
69890bb
refactor: rename to _pybind_conduit_v1_
henryiii Sep 12, 2024
22dbfa6
Add test_home_planet_wrap_very_lonely_traveler(), test_exo_planet_pyb…
rwgk Sep 13, 2024
62acc6b
Resolve clang-tidy errors:
rwgk Sep 13, 2024
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
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ endif()
set(PYBIND11_HEADERS
include/pybind11/detail/class.h
include/pybind11/detail/common.h
include/pybind11/detail/cpp_conduit.h
include/pybind11/detail/descr.h
include/pybind11/detail/init.h
include/pybind11/detail/internals.h
Expand Down
77 changes: 77 additions & 0 deletions include/pybind11/detail/cpp_conduit.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// Copyright (c) 2024 The pybind Community.

#pragma once

#include <pybind11/pytypes.h>

#include "common.h"
#include "internals.h"

#include <typeinfo>

PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
PYBIND11_NAMESPACE_BEGIN(detail)

// Forward declaration needed here: Refactoring opportunity.
extern "C" inline PyObject *pybind11_object_new(PyTypeObject *type, PyObject *, PyObject *);

inline bool type_is_managed_by_our_internals(PyTypeObject *type_obj) {
#if defined(PYPY_VERSION)
auto &internals = get_internals();
return bool(internals.registered_types_py.find(type_obj)
!= internals.registered_types_py.end());
#else
return bool(type_obj->tp_new == pybind11_object_new);
#endif
}

inline bool is_instance_method_of_type(PyTypeObject *type_obj, PyObject *attr_name) {
PyObject *descr = _PyType_Lookup(type_obj, attr_name);
return bool((descr != nullptr) && PyInstanceMethod_Check(descr));
}

inline object try_get_cpp_conduit_method(PyObject *obj) {
if (PyType_Check(obj)) {
return object();
}
PyTypeObject *type_obj = Py_TYPE(obj);
str attr_name("__cpp_conduit__");
bool assumed_to_be_callable = false;
if (type_is_managed_by_our_internals(type_obj)) {
if (!is_instance_method_of_type(type_obj, attr_name.ptr())) {
return object();
}
assumed_to_be_callable = true;
}
PyObject *method = PyObject_GetAttr(obj, attr_name.ptr());
if (method == nullptr) {
PyErr_Clear();
return object();
}
if (!assumed_to_be_callable && PyCallable_Check(method) == 0) {
Py_DECREF(method);
return object();
}
return reinterpret_steal<object>(method);
}

inline void *try_raw_pointer_ephemeral_from_cpp_conduit(handle src,
const std::type_info *cpp_type_info) {
object method = try_get_cpp_conduit_method(src.ptr());
if (method) {
capsule cpp_type_info_capsule(const_cast<void *>(static_cast<const void *>(cpp_type_info)),
typeid(std::type_info).name());
object cpp_conduit = method(bytes(PYBIND11_PLATFORM_ABI_ID),
cpp_type_info_capsule,
bytes("raw_pointer_ephemeral"));
if (isinstance<capsule>(cpp_conduit)) {
return reinterpret_borrow<capsule>(cpp_conduit).get_pointer();
}
}
return nullptr;
}

#define PYBIND11_HAS_CPP_CONDUIT

PYBIND11_NAMESPACE_END(detail)
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)
10 changes: 6 additions & 4 deletions include/pybind11/detail/internals.h
Original file line number Diff line number Diff line change
Expand Up @@ -321,15 +321,17 @@ struct type_info {
# define PYBIND11_INTERNALS_KIND ""
#endif

#define PYBIND11_PLATFORM_ABI_ID \
PYBIND11_INTERNALS_KIND PYBIND11_COMPILER_TYPE PYBIND11_STDLIB PYBIND11_BUILD_ABI \
PYBIND11_BUILD_TYPE

#define PYBIND11_INTERNALS_ID \
"__pybind11_internals_v" PYBIND11_TOSTRING(PYBIND11_INTERNALS_VERSION) \
PYBIND11_INTERNALS_KIND PYBIND11_COMPILER_TYPE PYBIND11_STDLIB \
PYBIND11_BUILD_ABI PYBIND11_BUILD_TYPE "__"
PYBIND11_PLATFORM_ABI_ID "__"

#define PYBIND11_MODULE_LOCAL_ID \
"__pybind11_module_local_v" PYBIND11_TOSTRING(PYBIND11_INTERNALS_VERSION) \
PYBIND11_INTERNALS_KIND PYBIND11_COMPILER_TYPE PYBIND11_STDLIB \
PYBIND11_BUILD_ABI PYBIND11_BUILD_TYPE "__"
PYBIND11_PLATFORM_ABI_ID "__"

/// Each module locally stores a pointer to the `internals` data. The data
/// itself is shared among modules with the same `PYBIND11_INTERNALS_ID`.
Expand Down
40 changes: 40 additions & 0 deletions include/pybind11/detail/type_caster_base.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,17 @@
#include <pybind11/pytypes.h>

#include "common.h"
#include "cpp_conduit.h"
#include "descr.h"
#include "internals.h"
#include "typeid.h"
#include "value_and_holder.h"

#include <cstdint>
#include <cstring>
#include <iterator>
#include <new>
#include <stdexcept>
#include <string>
#include <type_traits>
#include <typeindex>
Expand Down Expand Up @@ -611,6 +614,13 @@ class type_caster_generic {
}
return false;
}
bool try_cpp_conduit(handle src) {
value = try_raw_pointer_ephemeral_from_cpp_conduit(src, cpptype);
if (value != nullptr) {
return true;
}
return false;
}
void check_holder_compat() {}

PYBIND11_NOINLINE static void *local_load(PyObject *src, const type_info *ti) {
Expand Down Expand Up @@ -742,6 +752,10 @@ class type_caster_generic {
return true;
}

if (convert && cpptype && this_.try_cpp_conduit(src)) {
return true;
}

return false;
}

Expand Down Expand Up @@ -769,6 +783,32 @@ class type_caster_generic {
void *value = nullptr;
};

inline object cpp_conduit_method(handle self,
const bytes &pybind11_platform_abi_id,
const capsule &cpp_type_info_capsule,
const bytes &pointer_kind) {
#ifdef PYBIND11_HAS_STRING_VIEW
using cpp_str = std::string_view;
#else
using cpp_str = std::string;
#endif
if (cpp_str(pybind11_platform_abi_id) != PYBIND11_PLATFORM_ABI_ID) {
return none();
}
if (std::strcmp(cpp_type_info_capsule.name(), typeid(std::type_info).name()) != 0) {
return none();
}
if (cpp_str(pointer_kind) != "raw_pointer_ephemeral") {
throw std::runtime_error("Invalid pointer_kind: \"" + std::string(pointer_kind) + "\"");
}
const auto *cpp_type_info = cpp_type_info_capsule.get_pointer<const std::type_info>();
type_caster_generic caster(*cpp_type_info);
if (!caster.load(self, false)) {
return none();
}
return capsule(caster.value, cpp_type_info->name());
}

/**
* Determine suitable casting operator for pointer-or-lvalue-casting type casters. The type caster
* needs to provide `operator T*()` and `operator T&()` operators.
Expand Down
6 changes: 4 additions & 2 deletions include/pybind11/pybind11.h
Original file line number Diff line number Diff line change
Expand Up @@ -610,7 +610,8 @@ class cpp_function : public function {
int index = 0;
/* Create a nice pydoc rec including all signatures and
docstrings of the functions in the overload chain */
if (chain && options::show_function_signatures()) {
if (chain && options::show_function_signatures()
&& strcmp(rec->name, "__cpp_conduit__") != 0) {
henryiii marked this conversation as resolved.
Show resolved Hide resolved
// First a generic signature
signatures += rec->name;
signatures += "(*args, **kwargs)\n";
Expand All @@ -619,7 +620,7 @@ class cpp_function : public function {
// Then specific overload signatures
bool first_user_def = true;
for (auto *it = chain_start; it != nullptr; it = it->next) {
if (options::show_function_signatures()) {
if (options::show_function_signatures() && strcmp(rec->name, "__cpp_conduit__") != 0) {
henryiii marked this conversation as resolved.
Show resolved Hide resolved
if (index > 0) {
signatures += '\n';
}
Expand Down Expand Up @@ -1652,6 +1653,7 @@ class class_ : public detail::generic_type {
= instances[std::type_index(typeid(type))];
});
}
def("__cpp_conduit__", cpp_conduit_method);
}

template <typename Base, detail::enable_if_t<is_base<Base>::value, int> = 0>
Expand Down
2 changes: 2 additions & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ set(PYBIND11_TEST_FILES
test_const_name
test_constants_and_functions
test_copy_move
test_cpp_conduit
test_custom_type_casters
test_custom_type_setup
test_docstring_options
Expand Down Expand Up @@ -218,6 +219,7 @@ tests_extra_targets("test_exceptions.py;test_local_bindings.py;test_stl.py;test_
# And add additional targets for other tests.
tests_extra_targets("test_exceptions.py" "cross_module_interleaved_error_already_set")
tests_extra_targets("test_gil_scoped.py" "cross_module_gil_utils")
tests_extra_targets("test_cpp_conduit.py" "exo_planet_pybind11;exo_planet_c_api")

set(PYBIND11_EIGEN_REPO
"https://gitlab.com/libeigen/eigen.git"
Expand Down
103 changes: 103 additions & 0 deletions tests/exo_planet_c_api.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
// Copyright (c) 2024 The pybind Community.
Copy link
Collaborator

Choose a reason for hiding this comment

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

I know it shows up a few other places, but I think this is frowned upon by lawyers from discussing this in context of other not-legally defined entities. What does the "The pybind Community" mean?


// THIS MUST STAY AT THE TOP!
#include <pybind11/pybind11.h> // EXCLUSIVELY for PYBIND11_PLATFORM_ABI_ID
// Potential future direction to maximize reusability:
// (e.g. for use from SWIG, Cython, PyCLIF, nanobind):
// #include <pybind11/compat/platform_abi_id.h>
// This would only depend on:
// 1. A C++ compiler, WITHOUT requiring -fexceptions.
// 2. Python.h

#include "test_cpp_conduit_traveler_types.h"

#include <Python.h>
#include <typeinfo>

namespace {

void *get_cpp_conduit_void_ptr(PyObject *py_obj, const std::type_info *cpp_type_info) {
PyObject *cpp_type_info_capsule
= PyCapsule_New(const_cast<void *>(static_cast<const void *>(cpp_type_info)),
typeid(std::type_info).name(),
nullptr);
if (cpp_type_info_capsule == nullptr) {
return nullptr;
}
PyObject *cpp_conduit = PyObject_CallMethod(py_obj,
"__cpp_conduit__",
"yOy",
PYBIND11_PLATFORM_ABI_ID,
cpp_type_info_capsule,
"raw_pointer_ephemeral");
Py_DECREF(cpp_type_info_capsule);
if (cpp_conduit == nullptr) {
return nullptr;
}
void *void_ptr = PyCapsule_GetPointer(cpp_conduit, cpp_type_info->name());
Py_DECREF(cpp_conduit);
if (PyErr_Occurred()) {
return nullptr;
}
return void_ptr;
}

template <typename T>
T *get_cpp_conduit_type_ptr(PyObject *py_obj) {
void *void_ptr = get_cpp_conduit_void_ptr(py_obj, &typeid(T));
if (void_ptr == nullptr) {
return nullptr;
}
return static_cast<T *>(void_ptr);
}

extern "C" PyObject *wrapGetLuggage(PyObject * /*self*/, PyObject *traveler) {
const auto *cpp_traveler
= get_cpp_conduit_type_ptr<pybind11_tests::test_cpp_conduit::Traveler>(traveler);
if (cpp_traveler == nullptr) {
return nullptr;
}
return PyUnicode_FromString(cpp_traveler->luggage.c_str());
}

extern "C" PyObject *wrapGetPoints(PyObject * /*self*/, PyObject *premium_traveler) {
const auto *cpp_premium_traveler
= get_cpp_conduit_type_ptr<pybind11_tests::test_cpp_conduit::PremiumTraveler>(
premium_traveler);
if (cpp_premium_traveler == nullptr) {
return nullptr;
}
return PyLong_FromLong(static_cast<long>(cpp_premium_traveler->points));
}

PyMethodDef ThisMethodDef[] = {{"GetLuggage", wrapGetLuggage, METH_O, nullptr},
{"GetPoints", wrapGetPoints, METH_O, nullptr},
{nullptr, nullptr, 0, nullptr}};

struct PyModuleDef ThisModuleDef = {
PyModuleDef_HEAD_INIT, // m_base
"exo_planet_c_api", // m_name
nullptr, // m_doc
-1, // m_size
ThisMethodDef, // m_methods
nullptr, // m_slots
nullptr, // m_traverse
nullptr, // m_clear
nullptr // m_free
};

} // namespace

#if defined(WIN32) || defined(_WIN32)
# define EXO_PLANET_C_API_EXPORT __declspec(dllexport)
#else
# define EXO_PLANET_C_API_EXPORT __attribute__((visibility("default")))
#endif

extern "C" EXO_PLANET_C_API_EXPORT PyObject *PyInit_exo_planet_c_api() {
PyObject *m = PyModule_Create(&ThisModuleDef);
if (m == nullptr) {
return nullptr;
}
return m;
}
10 changes: 10 additions & 0 deletions tests/exo_planet_pybind11.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// Copyright (c) 2024 The pybind Community.

#if defined(PYBIND11_INTERNALS_VERSION)
# undef PYBIND11_INTERNALS_VERSION
#endif
#define PYBIND11_INTERNALS_VERSION 900000001

#include "test_cpp_conduit_traveler_bindings.h"

PYBIND11_MODULE(exo_planet_pybind11, m) { pybind11_tests::test_cpp_conduit::wrap_traveler(m); }
1 change: 1 addition & 0 deletions tests/extra_python_package/test_files.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
detail_headers = {
"include/pybind11/detail/class.h",
"include/pybind11/detail/common.h",
"include/pybind11/detail/cpp_conduit.h",
"include/pybind11/detail/descr.h",
"include/pybind11/detail/init.h",
"include/pybind11/detail/internals.h",
Expand Down
21 changes: 21 additions & 0 deletions tests/test_cpp_conduit.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Copyright (c) 2024 The pybind Community.

#include "pybind11_tests.h"
#include "test_cpp_conduit_traveler_bindings.h"

#include <typeinfo>

namespace pybind11_tests {
namespace test_cpp_conduit {

TEST_SUBMODULE(cpp_conduit, m) {
m.attr("PYBIND11_PLATFORM_ABI_ID") = py::bytes(PYBIND11_PLATFORM_ABI_ID);
m.attr("cpp_type_info_capsule_Traveler")
= py::capsule(&typeid(Traveler), typeid(std::type_info).name());
m.attr("cpp_type_info_capsule_int") = py::capsule(&typeid(int), typeid(std::type_info).name());

wrap_traveler(m);
}

} // namespace test_cpp_conduit
} // namespace pybind11_tests
Loading