Skip to content

Commit 6dc5a7e

Browse files
committed
Add interoperability with other Python binding frameworks
1 parent 71bfc9c commit 6dc5a7e

18 files changed

+2275
-277
lines changed

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ option(NB_TEST_STABLE_ABI "Test the stable ABI interface?" OFF)
3232
option(NB_TEST_SHARED_BUILD "Build a shared nanobind library for the test suite?" OFF)
3333
option(NB_TEST_CUDA "Force the use of the CUDA/NVCC compiler for testing purposes" OFF)
3434
option(NB_TEST_FREE_THREADED "Build free-threaded extensions for the test suite?" ON)
35+
option(NB_TEST_NO_INTEROP "Build without framework interoperability support?" OFF)
3536

3637
if (NOT MSVC)
3738
option(NB_TEST_SANITIZERS_ASAN "Build tests with the address sanitizer?" OFF)

cmake/nanobind-config.cmake

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,7 @@ function (nanobind_build_library TARGET_NAME)
190190
${NB_DIR}/src/nb_static_property.cpp
191191
${NB_DIR}/src/nb_ft.h
192192
${NB_DIR}/src/nb_ft.cpp
193+
${NB_DIR}/src/nb_foreign.cpp
193194
${NB_DIR}/src/common.cpp
194195
${NB_DIR}/src/error.cpp
195196
${NB_DIR}/src/trampoline.cpp
@@ -236,6 +237,10 @@ function (nanobind_build_library TARGET_NAME)
236237
target_compile_definitions(${TARGET_NAME} PUBLIC NB_FREE_THREADED)
237238
endif()
238239

240+
if (TARGET_NAME MATCHES "-local")
241+
target_compile_definitions(${TARGET_NAME} PRIVATE NB_DISABLE_FOREIGN)
242+
endif()
243+
239244
# Nanobind performs many assertion checks -- detailed error messages aren't
240245
# included in Release/MinSizeRel/RelWithDebInfo modes
241246
target_compile_definitions(${TARGET_NAME} PRIVATE
@@ -330,7 +335,7 @@ endfunction()
330335

331336
function(nanobind_add_module name)
332337
cmake_parse_arguments(PARSE_ARGV 1 ARG
333-
"STABLE_ABI;FREE_THREADED;NB_STATIC;NB_SHARED;PROTECT_STACK;LTO;NOMINSIZE;NOSTRIP;MUSL_DYNAMIC_LIBCPP;NB_SUPPRESS_WARNINGS"
338+
"STABLE_ABI;FREE_THREADED;NB_STATIC;NB_SHARED;PROTECT_STACK;LTO;NOMINSIZE;NOSTRIP;MUSL_DYNAMIC_LIBCPP;NB_SUPPRESS_WARNINGS;NO_INTEROP"
334339
"NB_DOMAIN" "")
335340

336341
add_library(${name} MODULE ${ARG_UNPARSED_ARGUMENTS})
@@ -375,6 +380,10 @@ function(nanobind_add_module name)
375380
set(libname "${libname}-ft")
376381
endif()
377382

383+
if (ARG_NO_INTEROP)
384+
set(libname "${libname}-local")
385+
endif()
386+
378387
if (ARG_NB_DOMAIN AND ARG_NB_SHARED)
379388
set(libname ${libname}-${ARG_NB_DOMAIN})
380389
endif()

docs/api_cmake.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,10 @@ The high-level interface consists of just one CMake command:
110110
an optimization that nanobind does by default in this specific case).
111111
If this explanation sounds confusing, then you can ignore it. See the
112112
detailed description below for more information on this step.
113+
* - ``NO_INTEROP``
114+
- Remove support for interoperability with other Python binding
115+
frameworks. If you don't need it in your environment, this offers
116+
a minor performance and code size benefit.
113117

114118
:cmake:command:`nanobind_add_module` performs the following
115119
steps to produce bindings.

include/nanobind/nb_class.h

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -91,9 +91,6 @@ enum class type_init_flags : uint32_t {
9191
all_init_flags = (0x1f << 19)
9292
};
9393

94-
// See internals.h
95-
struct nb_alias_chain;
96-
9794
// Implicit conversions for C++ type bindings, used in type_data below
9895
struct implicit_t {
9996
const std::type_info **cpp;
@@ -114,7 +111,7 @@ struct type_data {
114111
const char *name;
115112
const std::type_info *type;
116113
PyTypeObject *type_py;
117-
nb_alias_chain *alias_chain;
114+
void *foreign_bindings;
118115
#if defined(Py_LIMITED_API)
119116
PyObject* (*vectorcall)(PyObject *, PyObject * const*, size_t, PyObject *);
120117
#endif
@@ -332,6 +329,19 @@ inline void *type_get_slot(handle h, int slot_id) {
332329
#endif
333330
}
334331

332+
// nanobind interoperability with other binding frameworks
333+
inline void set_foreign_type_defaults(bool export_all, bool import_all) {
334+
detail::nb_type_set_foreign_defaults(export_all, import_all);
335+
}
336+
template <class T = void>
337+
inline void import_foreign_type(handle type) {
338+
detail::nb_type_import(type.ptr(),
339+
std::is_void_v<T> ? nullptr : &typeid(T));
340+
}
341+
inline void export_type_to_foreign(handle type) {
342+
detail::nb_type_export(type.ptr());
343+
}
344+
335345
template <typename Visitor> struct def_visitor {
336346
protected:
337347
// Ensure def_visitor<T> can only be derived from, not constructed

include/nanobind/nb_error.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ NB_EXCEPTION(next_overload)
125125

126126
inline void register_exception_translator(detail::exception_translator t,
127127
void *payload = nullptr) {
128-
detail::register_exception_translator(t, payload);
128+
detail::register_exception_translator(t, payload, /*at_end=*/false);
129129
}
130130

131131
template <typename T>
@@ -142,7 +142,7 @@ class exception : public object {
142142
} catch (T &e) {
143143
PyErr_SetString((PyObject *) payload, e.what());
144144
}
145-
}, m_ptr);
145+
}, m_ptr, /*at_end=*/false);
146146
}
147147
};
148148

include/nanobind/nb_lib.h

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -341,10 +341,12 @@ NB_CORE const std::type_info *nb_type_info(PyObject *t) noexcept;
341341
NB_CORE void *nb_inst_ptr(PyObject *o) noexcept;
342342

343343
/// Check if a Python type object wraps an instance of a specific C++ type
344-
NB_CORE bool nb_type_isinstance(PyObject *obj, const std::type_info *t) noexcept;
344+
NB_CORE bool nb_type_isinstance(PyObject *obj, const std::type_info *t,
345+
bool foreign_ok) noexcept;
345346

346-
/// Search for the Python type object associated with a C++ type
347-
NB_CORE PyObject *nb_type_lookup(const std::type_info *t) noexcept;
347+
/// Search for a Python type object associated with a C++ type
348+
NB_CORE PyObject *nb_type_lookup(const std::type_info *t,
349+
bool foreign_ok) noexcept;
348350

349351
/// Allocate an instance of type 't'
350352
NB_CORE PyObject *nb_inst_alloc(PyTypeObject *t);
@@ -386,6 +388,15 @@ NB_CORE void nb_inst_set_state(PyObject *o, bool ready, bool destruct) noexcept;
386388
/// Query the 'ready' and 'destruct' flags of an instance
387389
NB_CORE std::pair<bool, bool> nb_inst_state(PyObject *o) noexcept;
388390

391+
// Set whether types will be shared with other binding frameworks by default
392+
NB_CORE void nb_type_set_foreign_defaults(bool export_all, bool import_all);
393+
394+
// Teach nanobind about a type bound by another binding framework
395+
NB_CORE void nb_type_import(PyObject *pytype, const std::type_info *cpptype);
396+
397+
// Teach other binding frameworks about a type bound by nanobind
398+
NB_CORE void nb_type_export(PyObject *pytype);
399+
389400
// ========================================================================
390401

391402
// Create and install a Python property object
@@ -500,7 +511,8 @@ NB_CORE void print(PyObject *file, PyObject *str, PyObject *end);
500511
typedef void (*exception_translator)(const std::exception_ptr &, void *);
501512

502513
NB_CORE void register_exception_translator(exception_translator translator,
503-
void *payload);
514+
void *payload,
515+
bool at_end);
504516

505517
NB_CORE PyObject *exception_new(PyObject *mod, const char *name,
506518
PyObject *base);

include/nanobind/nb_types.h

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -667,15 +667,19 @@ class iterable : public object {
667667

668668
/// Retrieve the Python type object associated with a C++ class
669669
template <typename T> handle type() noexcept {
670-
return detail::nb_type_lookup(&typeid(detail::intrinsic_t<T>));
670+
return detail::nb_type_lookup(&typeid(detail::intrinsic_t<T>), false);
671+
}
672+
template <typename T> handle maybe_foreign_type() noexcept {
673+
return detail::nb_type_lookup(&typeid(detail::intrinsic_t<T>), true);
671674
}
672675

673676
template <typename T>
674-
NB_INLINE bool isinstance(handle h) noexcept {
677+
NB_INLINE bool isinstance(handle h, bool foreign_ok = false) noexcept {
675678
if constexpr (std::is_base_of_v<handle, T>)
676679
return T::check_(h);
677680
else if constexpr (detail::is_base_caster_v<detail::make_caster<T>>)
678-
return detail::nb_type_isinstance(h.ptr(), &typeid(detail::intrinsic_t<T>));
681+
return detail::nb_type_isinstance(h.ptr(), &typeid(detail::intrinsic_t<T>),
682+
foreign_ok);
679683
else
680684
return detail::make_caster<T>().from_python(h, 0, nullptr);
681685
}

include/nanobind/stl/unique_ptr.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,11 @@ struct type_caster<std::unique_ptr<T, Deleter>> {
9494
// Stash source python object
9595
src = src_;
9696

97+
// Don't accept foreign types; they can't relinquish ownership
98+
if (!src.is_none() && !inst_check(src)) {
99+
return false;
100+
}
101+
97102
/* Try casting to a pointer of the underlying type. We pass flags=0 and
98103
cleanup=nullptr to prevent implicit type conversions (they are
99104
problematic since the instance then wouldn't be owned by 'src') */

src/error.cpp

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -217,12 +217,28 @@ builtin_exception::~builtin_exception() { }
217217

218218
NAMESPACE_BEGIN(detail)
219219

220-
void register_exception_translator(exception_translator t, void *payload) {
221-
nb_translator_seq *cur = &internals->translators,
222-
*next = new nb_translator_seq(*cur);
223-
cur->next = next;
224-
cur->payload = payload;
225-
cur->translator = t;
220+
void register_exception_translator(exception_translator t,
221+
void *payload,
222+
bool at_end) {
223+
// We will insert the new translator so it is pointed to by `*insert_at`,
224+
// i.e., so that it is executed just before the current `*insert_at`
225+
nb_maybe_atomic<nb_translator_seq *> *insert_at = &internals->translators;
226+
if (at_end) {
227+
// Insert before the default exception translator (which is last in
228+
// the list)
229+
nb_translator_seq *next = insert_at->load_acquire();
230+
while (next && next->next.load_relaxed()) {
231+
insert_at = &next->next;
232+
next = insert_at->load_acquire();
233+
}
234+
}
235+
nb_translator_seq *new_head = new nb_translator_seq{};
236+
nb_translator_seq *cur_head = insert_at->load_relaxed();
237+
new_head->payload = payload;
238+
new_head->translator = t;
239+
do {
240+
new_head->next = cur_head;
241+
} while (!insert_at->compare_exchange_weak(cur_head, new_head));
226242
}
227243

228244
NB_CORE PyObject *exception_new(PyObject *scope, const char *name,

src/nb_abi.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
/// Tracks the version of nanobind's internal data structures
1616
#ifndef NB_INTERNALS_VERSION
17-
# define NB_INTERNALS_VERSION 16
17+
# define NB_INTERNALS_VERSION 17
1818
#endif
1919

2020
#if defined(__MINGW32__)

0 commit comments

Comments
 (0)