|
| 1 | +// Copyright (c) 2024 The pybind Community. |
| 2 | + |
| 3 | +/* The pybind11_conduit_v1 feature enables type-safe interoperability between |
| 4 | +
|
| 5 | +* different independent Python/C++ bindings systems, |
| 6 | +
|
| 7 | +* including pybind11 versions with different PYBIND11_INTERNALS_VERSION's. |
| 8 | +
|
| 9 | +The naming of the feature is a bit misleading: |
| 10 | +
|
| 11 | +* The feature is in no way tied to pybind11 internals. |
| 12 | +
|
| 13 | +* It just happens to originate from pybind11 and currently still lives there. |
| 14 | +
|
| 15 | +* The only external dependency is <Python.h>. |
| 16 | +
|
| 17 | +The implementation is a VERY light-weight dependency. It is designed to be |
| 18 | +compatible with any ISO C++11 (or higher) compiler, and does NOT require |
| 19 | +C++ Exception Handling to be enabled. |
| 20 | +
|
| 21 | +Please see https://github.com/pybind/pybind11/pull/5296 for more background. |
| 22 | +
|
| 23 | +The implementation involves a |
| 24 | +
|
| 25 | +def _pybind11_conduit_v1_( |
| 26 | + self, |
| 27 | + pybind11_platform_abi_id: bytes, |
| 28 | + cpp_type_info_capsule: capsule, |
| 29 | + pointer_kind: bytes) -> capsule |
| 30 | +
|
| 31 | +method that is meant to be added to Python objects wrapping C++ objects |
| 32 | +(e.g. pybind11::class_-wrapped types). |
| 33 | +
|
| 34 | +The design of the _pybind11_conduit_v1_ feature provides two layers of |
| 35 | +protection against C++ ABI mismatches: |
| 36 | +
|
| 37 | +* The first and most important layer is that the pybind11_platform_abi_id's |
| 38 | + must match between extensions. — This will never be perfect, but is the same |
| 39 | + pragmatic approach used in pybind11 since 2017 |
| 40 | + (https://github.com/pybind/pybind11/commit/96997a4b9d4ec3d389a570604394af5d5eee2557, |
| 41 | + PYBIND11_INTERNALS_ID). |
| 42 | +
|
| 43 | +* The second layer is that the typeid(std::type_info).name()'s must match |
| 44 | + between extensions. |
| 45 | +
|
| 46 | +The implementation below (which is shorter than this comment!), serves as a |
| 47 | +battle-tested specification. The main API is this one function: |
| 48 | +
|
| 49 | +auto *cpp_pointer = pybind11_conduit_v1::get_type_pointer_ephemeral<YourType>(py_obj); |
| 50 | +
|
| 51 | +It is meant to be a minimalistic reference implementation, intentionally |
| 52 | +without comprehensive error reporting. It is expected that major bindings |
| 53 | +systems will roll their own, compatible implementations, potentially with |
| 54 | +system-specific error reporting. The essential specifications all bindings |
| 55 | +systems need to agree on are merely: |
| 56 | +
|
| 57 | +* PYBIND11_PLATFORM_ABI_ID (const char* literal). |
| 58 | +
|
| 59 | +* The cpp_type_info capsule (see below: a void *ptr and a const char *name). |
| 60 | +
|
| 61 | +* The cpp_conduit capsule (see below: a void *ptr and a const char *name). |
| 62 | +
|
| 63 | +* "raw_pointer_ephemeral" means: the lifetime of the pointer is the lifetime |
| 64 | + of the py_obj. |
| 65 | +
|
| 66 | +*/ |
| 67 | + |
| 68 | +// THIS MUST STAY AT THE TOP! |
| 69 | +#include "pybind11_platform_abi_id.h" |
| 70 | + |
| 71 | +#include <Python.h> |
| 72 | +#include <typeinfo> |
| 73 | + |
| 74 | +namespace pybind11_conduit_v1 { |
| 75 | + |
| 76 | +inline void *get_raw_pointer_ephemeral(PyObject *py_obj, const std::type_info *cpp_type_info) { |
| 77 | + PyObject *cpp_type_info_capsule |
| 78 | + = PyCapsule_New(const_cast<void *>(static_cast<const void *>(cpp_type_info)), |
| 79 | + typeid(std::type_info).name(), |
| 80 | + nullptr); |
| 81 | + if (cpp_type_info_capsule == nullptr) { |
| 82 | + return nullptr; |
| 83 | + } |
| 84 | + PyObject *cpp_conduit = PyObject_CallMethod(py_obj, |
| 85 | + "_pybind11_conduit_v1_", |
| 86 | + "yOy", |
| 87 | + PYBIND11_PLATFORM_ABI_ID, |
| 88 | + cpp_type_info_capsule, |
| 89 | + "raw_pointer_ephemeral"); |
| 90 | + Py_DECREF(cpp_type_info_capsule); |
| 91 | + if (cpp_conduit == nullptr) { |
| 92 | + return nullptr; |
| 93 | + } |
| 94 | + void *raw_ptr = PyCapsule_GetPointer(cpp_conduit, cpp_type_info->name()); |
| 95 | + Py_DECREF(cpp_conduit); |
| 96 | + if (PyErr_Occurred()) { |
| 97 | + return nullptr; |
| 98 | + } |
| 99 | + return raw_ptr; |
| 100 | +} |
| 101 | + |
| 102 | +template <typename T> |
| 103 | +T *get_type_pointer_ephemeral(PyObject *py_obj) { |
| 104 | + void *raw_ptr = get_raw_pointer_ephemeral(py_obj, &typeid(T)); |
| 105 | + if (raw_ptr == nullptr) { |
| 106 | + return nullptr; |
| 107 | + } |
| 108 | + return static_cast<T *>(raw_ptr); |
| 109 | +} |
| 110 | + |
| 111 | +} // namespace pybind11_conduit_v1 |
0 commit comments