@@ -258,19 +258,23 @@ T* instance(Args&&... args)
258258 return &it->second ;
259259}
260260
261- using DanglingReferencesStorage =
261+ // / A 'disowned' reference would be a reference that is not expected to be automatically destroyed,
262+ // / meaning we would have to manually call destroy on it. Such a storage enables us to associate
263+ // / such references to a type-erased value so that we can later retrieve them and destroy them
264+ // / manually.
265+ using DisownedReferencesStorage =
262266 boost::synchronized_value<std::map<void *, std::vector<AnyReference>>>;
263267
264- void storeDanglingReference (void * context, AnyReference ref) noexcept
268+ void storeDisownedReference (void * context, AnyReference ref) noexcept
265269{
266- auto * storage = instance<DanglingReferencesStorage >();
270+ auto * storage = instance<DisownedReferencesStorage >();
267271 auto syncStorage = storage->synchronize ();
268272 (*syncStorage)[context].push_back (ref);
269273}
270274
271- std::vector<AnyReference> unstoreDanglingReferences (void * context) noexcept
275+ std::vector<AnyReference> unstoreDisownedReferences (void * context) noexcept
272276{
273- auto * storage = instance<DanglingReferencesStorage >();
277+ auto * storage = instance<DisownedReferencesStorage >();
274278 auto syncStorage = storage->synchronize ();
275279 auto it = syncStorage->find (context);
276280 if (it == syncStorage->end ())
@@ -282,14 +286,40 @@ std::vector<AnyReference> unstoreDanglingReferences(void* context) noexcept
282286 return res;
283287}
284288
285- std::size_t destroyDanglingReferences (void * context) noexcept
289+ std::size_t destroyDisownedReferences (void * context) noexcept
286290{
287- const auto refs = unstoreDanglingReferences (context);
291+ const auto refs = unstoreDisownedReferences (context);
288292 for (auto ref : refs)
289293 ref.destroy ();
290294 return refs.size ();
291295}
292296
297+ // / Associates a value to a Python object, so that it shares its lifetime.
298+ template <typename T>
299+ AnyReference associateValueToObj (::py::object& obj, T value)
300+ {
301+ const auto res = AnyValue::from (std::move (value)).release ();
302+ auto pybindObjPtr = &obj;
303+ storeDisownedReference (pybindObjPtr, res);
304+
305+ // Create a weak reference to the target Python object that will track its lifetime and execute a
306+ // callback once it is destroyed.
307+ //
308+ // The callback ensures that all the references associated to the target object are destroyed, so
309+ // that we can leak them here and still ensure that they will be properly destroyed once needed.
310+ //
311+ // The callback also takes a handle to the weakref Python object itself. We need to make sure that
312+ // this weakref object lives as long as the target Python object so that the callback stays
313+ // attached to the lifetime of the target object. To do that, we leak the weakref Python object
314+ // by releasing it, and we manually decrement the reference count in the callback.
315+ auto weakref = ::py::weakref (obj, ::py::cpp_function ([=](::py::handle weakref) {
316+ destroyDisownedReferences (pybindObjPtr);
317+ weakref.dec_ref ();
318+ }));
319+ weakref.release ();
320+ return res;
321+ }
322+
293323} // namespace
294324
295325::py::object unwrapValue (AnyReference val)
@@ -337,7 +367,7 @@ class ObjectInterfaceBase : public Interface
337367
338368 void destroy (void * storage) override
339369 {
340- destroyDanglingReferences (storage);
370+ destroyDisownedReferences (storage);
341371 ::py::gil_scoped_acquire lock;
342372 delete asObjectPtr (&storage);
343373 }
@@ -363,7 +393,7 @@ class DynamicInterface : public ObjectInterfaceBase<Storage, qi::DynamicTypeInte
363393 {
364394 ::py::gil_scoped_acquire lock;
365395 const auto obj = this ->asObjectPtr (&storage);
366- return unwrapAsRef (obj);
396+ return unwrapAsRef (* obj);
367397 }
368398
369399 void set (void ** storage, AnyReference src) override
@@ -559,9 +589,9 @@ class ListInterface : public ObjectInterfaceBase<Storage, qi::ListTypeInterface>
559589 const ::py::object element = list[index];
560590
561591 auto ref = AnyReference::from (element).clone ();
562- // Store the dangling reference with the list as a context instead of the
592+ // Store the disowned reference with the list as a context instead of the
563593 // iterator because the reference might outlive the iterator.
564- storeDanglingReference (listStorage, ref);
594+ storeDisownedReference (listStorage, ref);
565595 return ref;
566596 }
567597
@@ -578,7 +608,7 @@ class ListInterface : public ObjectInterfaceBase<Storage, qi::ListTypeInterface>
578608 void * clone (void * storage) override { return DefaultImpl::clone (storage); }
579609 void destroy (void * storage) override
580610 {
581- destroyDanglingReferences (storage);
611+ destroyDisownedReferences (storage);
582612 return DefaultImpl::destroy (storage);
583613 }
584614 const TypeInfo& info () override { return DefaultImpl::info (); }
@@ -657,9 +687,9 @@ class DictInterface: public ObjectInterfaceBase<Storage, qi::MapTypeInterface>
657687 const auto element = ::py::reinterpret_borrow<::py::object>(it->second );
658688 const auto keyElementPair = std::make_pair (key, element);
659689 auto ref = AnyReference::from (keyElementPair).clone ();
660- // Store the dangling reference with the list as a context instead of the
690+ // Store the disowned reference with the list as a context instead of the
661691 // iterator because the reference might outlive the iterator.
662- storeDanglingReference (dictStorage, ref);
692+ storeDisownedReference (dictStorage, ref);
663693 return ref;
664694 }
665695
@@ -676,7 +706,7 @@ class DictInterface: public ObjectInterfaceBase<Storage, qi::MapTypeInterface>
676706 void * clone (void * storage) override { return DefaultImpl::clone (storage); }
677707 void destroy (void * storage) override
678708 {
679- destroyDanglingReferences (storage);
709+ destroyDisownedReferences (storage);
680710 return DefaultImpl::destroy (storage);
681711 }
682712
@@ -749,78 +779,87 @@ class DictInterface: public ObjectInterfaceBase<Storage, qi::MapTypeInterface>
749779 }
750780
751781 auto ref = AnyReference::from (value).clone ();
752- // Store the dangling reference with the list as a context instead of the
782+ // Store the disowned reference with the list as a context instead of the
753783 // iterator because the reference might outlive the iterator.
754- storeDanglingReference (storage, ref);
784+ storeDisownedReference (storage, ref);
755785 return ref;
756786 }
757787};
758788
759789} // namespace types
760790
761- AnyReference unwrapAsRef (pybind11::object* obj)
791+ AnyReference unwrapAsRef (pybind11::object& obj)
762792{
763- QI_ASSERT_NOT_NULL (obj);
764- QI_ASSERT_TRUE (*obj);
793+ QI_ASSERT_TRUE (obj);
765794
766795 ::py::gil_scoped_acquire lock;
767796
768- if (obj-> is_none ())
797+ if (obj. is_none ())
769798 // The "void" value in AnyValue has no storage, so we can just release it
770799 // without risk of a leak.
771800 return AnyValue::makeVoid ().release ();
772801
773802 if ( ::py::isinstance<::py::ellipsis>(*obj)
774- || PyComplex_CheckExact (obj-> ptr ())
803+ || PyComplex_CheckExact (obj. ptr ())
775804 || ::py::isinstance<::py::memoryview>(*obj)
776805 || ::py::isinstance<::py::slice>(*obj)
777806 || ::py::isinstance<::py::module >(*obj))
778807 {
779808 throw std::runtime_error (" The Python type " +
780- std::string (::py::str (obj-> get_type ())) +
809+ std::string (::py::str (obj. get_type ())) +
781810 " is not handled" );
782811 }
783812
784- if (PyLong_CheckExact (obj->ptr ()))
785- return AnyReference (instance<types::IntInterface<::py::object>>(), obj);
813+ // Pointer to the libpython C library python object.
814+ const auto pyObjPtr = obj.ptr ();
815+
816+ // Pointer to the pybind C++ library python object.
817+ const auto pybindObjPtr = &obj;
818+
819+ if (PyLong_CheckExact (pyObjPtr))
820+ return AnyReference (instance<types::IntInterface<::py::object>>(), pybindObjPtr);
786821
787- if (PyFloat_CheckExact (obj-> ptr () ))
788- return AnyReference (instance<types::FloatInterface<::py::object>>(), obj );
822+ if (PyFloat_CheckExact (pyObjPtr ))
823+ return AnyReference (instance<types::FloatInterface<::py::object>>(), pybindObjPtr );
789824
790- if (PyBool_Check (obj-> ptr () ))
791- return AnyReference (instance<types::BoolInterface<::py::object>>(), obj );
825+ if (PyBool_Check (pyObjPtr ))
826+ return AnyReference (instance<types::BoolInterface<::py::object>>(), pybindObjPtr );
792827
793- if (PyUnicode_CheckExact (obj-> ptr () ))
794- return AnyReference (instance<types::StrInterface<::py::object>>(), obj );
828+ if (PyUnicode_CheckExact (pyObjPtr ))
829+ return AnyReference (instance<types::StrInterface<::py::object>>(), pybindObjPtr );
795830
796- if (PyBytes_CheckExact (obj-> ptr ()) || PyByteArray_CheckExact (obj-> ptr () ))
797- return AnyReference (instance<types::StringBufferInterface<::py::object>>(), obj );
831+ if (PyBytes_CheckExact (pyObjPtr) || PyByteArray_CheckExact (pyObjPtr ))
832+ return AnyReference (instance<types::StringBufferInterface<::py::object>>(), pybindObjPtr );
798833
799- if (PyTuple_CheckExact (obj-> ptr () ))
834+ if (PyTuple_CheckExact (pyObjPtr ))
800835 return AnyReference (instance<types::StructuredIterableInterface<::py::object>>(
801- ::py::tuple (* obj).size()),
802- obj );
836+ ::py::tuple (obj).size()),
837+ pybindObjPtr );
803838
804839 // Checks if it is AnySet, meaning it can be a set or a frozenset.
805- if (PyAnySet_CheckExact (obj-> ptr () ))
840+ if (PyAnySet_CheckExact (pyObjPtr ))
806841 return AnyReference (instance<types::StructuredIterableInterface<::py::object>>(
807- ::py::set (*obj).size()),
808- obj);
809-
810- if (PyList_CheckExact (obj->ptr ()))
811- return AnyReference (instance<types::ListInterface<::py::object, ::py::list>>(), obj);
812-
813- if (PyDict_CheckExact (obj->ptr ()))
814- return AnyReference (instance<types::DictInterface<::py::object>>(), obj);
815-
816- auto qiObj = py::toObject (*obj);
817- const auto res = AnyValue::from (std::move (qiObj)).release ();
818- storeDanglingReference (obj, res);
819- ::py::weakref (*obj, ::py::cpp_function([=](::py::handle ref) {
820- destroyDanglingReferences (obj);
821- ref.dec_ref (); // release the reference to the weak reference itself.
822- })).release (); // leak the weak reference, it will be released by the callback.
823- return res;
842+ ::py::set (obj).size()),
843+ pybindObjPtr);
844+
845+ if (PyList_CheckExact (pyObjPtr) || PyDictViewSet_Check (pyObjPtr) || PyDictValues_Check (pyObjPtr))
846+ return AnyReference (instance<types::ListInterface<::py::object, ::py::list>>(), pybindObjPtr);
847+
848+ if (PyDict_CheckExact (pyObjPtr))
849+ return AnyReference (instance<types::DictInterface<::py::object>>(), pybindObjPtr);
850+
851+ // At the moment in libqi, the `LogLevel` type is not registered in the qi type system. If we use
852+ // `AnyValue::from` with a `LogLevel` value, we get a value with a dummy type that cannot be set
853+ // or read. Furthermore, when registered with the `QI_TYPE_ENUM` macro, enumeration types are
854+ // treated as `int` values. This forces us to cast it here as an `int` to try to stay compatible.
855+ if (::py::isinstance<LogLevel>(obj))
856+ {
857+ const auto logLevel = obj.cast <LogLevel>();
858+ const auto logLevelValue = static_cast <int >(logLevel);
859+ return associateValueToObj (obj, logLevelValue);
860+ }
861+
862+ return associateValueToObj (obj, py::toObject (obj));
824863}
825864
826865void registerTypes ()
0 commit comments