-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Enable type-safe interoperability between different independent Pytho…
…n/C++ bindings systems. (#5296) * `self.__cpp_transporter__()` proof of concept: Enable passing C++ pointers across extensions even if the `PYBIND11_INTERNALS_VERSION`s do not match. * Include cleanup (mainly to resolve PyPy build failures). * Fix clang-tidy errors. * Resolve `error: extra * factor out platform_abi_id.h from internals.h (no functional changes) * factor out internals_version.h from internals.h (no functional changes) * Update CMakeLists.txt, tests/extra_python_package/test_files.py * Revert "factor out internals_version.h from internals.h (no functional changes)" This reverts commit 3ccea8c. * Remove internals_version.h from CMakeLists.txt, tests/extra_python_package/test_files.py * `.__cpp_transporter__()` implementation: compare `pybind11_platform_abi_id`, `cpp_typeid_name` * Add PremiumTraveler * Rename test_cpp_transporter_traveler_type.h -> test_cpp_transporter_traveler_types.h * Expand tests: `PremiumTraveler`, `get_points()` * Shuffle order of tests (no real changes). * Move `__cpp_transporter__` lambda to `py::cpp_transporter()` regular function. * Use `type_caster_generic::load(self)` instead of `cast<T *>(self)` * Pass `const std::type_info *` via `py::capsule` (instead of `cpp_typeid_name`). * Make platform_abi_id.h completely stand-alone. * rename exo_planet.cpp -> exo_planet_pybind11.cpp * Add exo_planet_c_api.cpp (incomplete). * Fix silly oversight (wrong filename in `#include`). * Resolve clang-tidy errors: ``` /__w/pybind11/pybind11/tests/exo_planet_c_api.cpp:10:18: error: 'wrapGetLuggage' is a static definition in anonymous namespace; static is redundant here [readability-static-definition-in-anonymous-namespace,-warnings-as-errors] 10 | static PyObject *wrapGetLuggage(PyObject *, PyObject *) { return PyUnicode_FromString("TODO"); } | ~~~~~~ ^ /__w/pybind11/pybind11/tests/exo_planet_c_api.cpp:14:20: error: 'ThisMethodDef' is a static definition in anonymous namespace; static is redundant here [readability-static-definition-in-anonymous-namespace,-warnings-as-errors] 14 | static PyMethodDef ThisMethodDef[] | ~~~~~~ ^ /__w/pybind11/pybind11/tests/exo_planet_c_api.cpp:17:27: error: 'ThisModuleDef' is a static definition in anonymous namespace; static is redundant here [readability-static-definition-in-anonymous-namespace,-warnings-as-errors] 17 | static struct PyModuleDef ThisModuleDef = { | ~~~~~~ ^ ``` * Implement exo_planet_c_api GetLuggage(), GetPoints() * Move new code from test_cpp_transporter_traveler_bindings.h to pybind11/detail/type_caster_base.h, under the name `class_dunder_cpp_transporter()` * Fix oversight. * Unconditionally add `__cpp_transporter__` method to all `py::class_` objects, but do not include that magic method in docstring signatures. * Back out pybind11/detail/platform_abi_id.h for now. Maximizing reusability can be handled separately, later. * Small cleanup. * Restore and add to `test_call_cpp_transporter_*()` * Ensure #3788 does not bite again. * `class_dunder_cpp_transporter()`: replace `obj.cast<std::string>()` with `std::string(obj)` * Add (simple) copyright notices in all newly added files. * Globally replace cpp_transporter with cpp_conduit * style: pre-commit fixes * IWYU fixes * Rename `class_dunder_cpp_conduit()` -> `cpp_conduit_method()` * Change `pybind11_platform_abi_id`, `pointer_kind` argument types from `str` to `bytes`. This avoids the unicode decode/encode roundtrips: * More robust (no decode/encode errors). * Minor runtime optimization. * Systematically rename `cap_cpp_type_info` -> `cpp_type_info_capsule` (no functional changes). * Systematically replace `cpp_type_info_capsule` `name`: `"const std::type_info *"` -> `typeid(std::type_info).name()` (this IS a functional change). This provides an extra layer of protection against C++ ABI mismatches: * The first and most important layer is that the `PYBIND11_PLATFORM_ABI_ID`s must match between extensions. * The second layer is that the `typeid(std::type_info).name()`s must match between extensions. * Fix sort order accident in tests/CMakeLists.txt * Apply suggestions from code review Co-authored-by: Aaron Gokaslan <aaronGokaslan@gmail.com> * style: pre-commit fixes * refactor: rename to _pybind_conduit_v1_ Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com> * Add test_home_planet_wrap_very_lonely_traveler(), test_exo_planet_pybind11_wrap_very_lonely_traveler() * Resolve clang-tidy errors: ``` /__w/pybind11/pybind11/tests/test_cpp_conduit_traveler_bindings.h:39:32: error: parameter 'm' is passed by value and only copied once; consider moving it to avoid unnecessary copies [performance-unnecessary-value-param,-warnings-as-errors] 10 | py::class_<LonelyTraveler>(m, "LonelyTraveler"); | ^ | std::move( ) /__w/pybind11/pybind11/tests/test_cpp_conduit_traveler_bindings.h:43:52: error: parameter 'm' is passed by value and only copied once; consider moving it to avoid unnecessary copies [performance-unnecessary-value-param,-warnings-as-errors] 43 | py::class_<VeryLonelyTraveler, LonelyTraveler>(m, "VeryLonelyTraveler"); | ^ | std::move( ) ``` --------- Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com> Co-authored-by: Ralf W. Grosse-Kunstleve <rwgk@google.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Henry Schreiner <HenrySchreinerIII@gmail.com> Co-authored-by: Aaron Gokaslan <aaronGokaslan@gmail.com>
- Loading branch information
1 parent
54ab424
commit a5fcc56
Showing
14 changed files
with
524 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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("_pybind11_conduit_v1_"); | ||
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 1 | ||
|
||
PYBIND11_NAMESPACE_END(detail) | ||
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
// Copyright (c) 2024 The pybind Community. | ||
|
||
// 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, | ||
"_pybind11_conduit_v1_", | ||
"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; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
// 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" | ||
|
||
namespace pybind11_tests { | ||
namespace test_cpp_conduit { | ||
|
||
PYBIND11_MODULE(exo_planet_pybind11, m) { | ||
wrap_traveler(m); | ||
m.def("wrap_very_lonely_traveler", [m]() { wrap_very_lonely_traveler(m); }); | ||
} | ||
|
||
} // namespace test_cpp_conduit | ||
} // namespace pybind11_tests |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
// Copyright (c) 2024 The pybind Community. | ||
|
||
#include "test_cpp_conduit_traveler_bindings.h" | ||
|
||
namespace pybind11_tests { | ||
namespace test_cpp_conduit { | ||
|
||
PYBIND11_MODULE(home_planet_very_lonely_traveler, m) { | ||
m.def("wrap_very_lonely_traveler", [m]() { wrap_very_lonely_traveler(m); }); | ||
} | ||
|
||
} // namespace test_cpp_conduit | ||
} // namespace pybind11_tests |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
// 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); | ||
wrap_lonely_traveler(m); | ||
} | ||
|
||
} // namespace test_cpp_conduit | ||
} // namespace pybind11_tests |
Oops, something went wrong.