qtbridge_runtime/qobjectholder.rs
1// Copyright (C) 2026 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only
3
4use std::cell::RefCell;
5use std::ptr::NonNull;
6use std::rc::Rc;
7
8use qtbridge_type_lib::{QObject, QVariant};
9use crate::qproxies::{QRustProxy, ConstructionMode};
10use crate::rustobjectgetter::get_rust_proxy;
11use crate::{DispatchMetaCall, QMetaInfo, QmlMethodInvoker};
12use std::collections::HashMap;
13
14
15pub trait QObjectHolder : DispatchMetaCall + QMetaInfo + Default {
16 /// Alias for the Rust proxy type corresponding to the user-defined type.
17 /// The Rust proxy is an intermediate layer between the Rust object and the C++ proxy,
18 /// forwarding calls in both directions and managing borrowing of the Rust object
19 /// during QAIM calls (and TBD for meta calls as well).
20 #[doc(hidden)]
21 type ProxyRust: QRustProxy<ProxyCppType = <Self as QMetaInfo>::CppProxy>;
22
23 #[doc(hidden)]
24 fn try_borrow_mut_proxies_map<F, R>(f: F) -> R
25 where
26 F: FnOnce( &mut HashMap<*const u8, *const u8>) -> R
27 {
28 thread_local! { static INSTANCES: RefCell<HashMap<*const u8, *const u8>> =
29 RefCell::new(HashMap::new());
30 }
31 INSTANCES.with_borrow_mut(f)
32 }
33
34 /// Return a pointer to the Rust proxy associated with the specified object,
35 /// or `None` if no proxy is registered.
36 #[doc(hidden)]
37 fn try_get_rust_proxy_ptr_from_ptr(rust_obj_ptr: *const Self) -> Option<*mut Self::ProxyRust> {
38 let proxy_ptr = Self::try_borrow_mut_proxies_map(|map| {
39 map.get(&rust_obj_ptr.cast::<u8>()).copied().unwrap_or_default()
40 });
41 NonNull::new(proxy_ptr as *mut Self::ProxyRust).map(|nn| nn.as_ptr())
42 }
43
44 /// Return a pointer to the Rust proxy associated with the specified object,
45 /// or `None` if no proxy is registered.
46 #[doc(hidden)]
47 fn try_get_rust_proxy_ptr(&self) -> Option<*mut Self::ProxyRust> {
48 Self::try_get_rust_proxy_ptr_from_ptr(std::ptr::from_ref(self))
49 }
50
51 /// Return `QObject` attached to the specified Rust object.
52 #[doc(hidden)]
53 fn get_qobject_ptr(&self) -> *mut QObject {
54 let Some(proxy_ptr) = Self::try_get_rust_proxy_ptr(self) else {
55 return std::ptr::null_mut()
56 };
57 let rust_proxy = unsafe { &*proxy_ptr };
58 let cpp_proxy = rust_proxy.get_cpp_proxy();
59 cpp_proxy as *mut QObject
60 }
61
62 /// Return `QObject` attached to the specified Rust object.
63 #[doc(hidden)]
64 fn rc_ref_cell_to_qobject(self_obj: &Rc<RefCell<Self>>) -> *const QObject {
65 let Some(proxy_ptr) = Self::try_get_rust_proxy_ptr_from_ptr(self_obj.as_ptr()) else {
66 return std::ptr::null_mut()
67 };
68 let rust_proxy = unsafe { &*proxy_ptr };
69 let cpp_proxy = rust_proxy.get_cpp_proxy();
70 cpp_proxy as *mut QObject
71 }
72
73 /// Return the Rust object attached to the specified `QObject`.
74 #[doc(hidden)]
75 unsafe fn qobject_to_rc_ref_cell(qobj_ptr: *const QObject) -> Rc<RefCell<Self>>
76 {
77 let qobj_ref = unsafe { qobj_ptr.as_ref() }
78 .expect("Input QObject is null");
79 let proxy_ptr = get_rust_proxy(qobj_ref);
80 debug_assert!(!proxy_ptr.is_null());
81
82 // Verify the QObject really is of type `Self` before reinterpreting
83 // its proxy/object as `Self`'s - otherwise the casts below are UB.
84 let qobj_meta_obj = qobj_ref.get_qmeta_object();
85 let self_meta_obj = <Self as QMetaInfo>::get_shared_dynamic_meta_object_data().get_meta_object();
86 if qobj_meta_obj != self_meta_obj {
87 let qobj_name = unsafe { qobj_meta_obj.as_ref() }.map_or("<null>".into(), |m| m.meta_type().name());
88 let self_name = unsafe { self_meta_obj.as_ref() }.map_or("<null>".into(), |m| m.meta_type().name());
89 panic!("Value of wrong type is assigned to property: '{qobj_name}' instead of '{self_name}'")
90 }
91
92 let proxy = unsafe { &*(proxy_ptr as *const Self::ProxyRust) };
93 let rc_adapter = proxy.get_rust_object_rc()
94 .expect("Rust object associated with given QObject was already dropped");
95
96 // SAFETY: the metatype check above proves the `QObject` - and therefore
97 // the allocation behind `rc_adapter` - was created as `RefCell<Self>`.
98 // The adapter `Rc` only layers a vtable over that same allocation, so
99 // its data pointer addresses a real `RefCell<Self>` with matching size
100 // and alignment; reinterpreting it back is sound. `into_raw` parks the
101 // `+1` produced by `get_rust_object_rc` and `from_raw` reclaims it, so
102 // the reference count stays balanced.
103 let raw_ref_cell = Rc::into_raw(rc_adapter) as *const u8 as *const RefCell<Self>;
104 unsafe { Rc::from_raw(raw_ref_cell) }
105 }
106
107 /// Returns a [`QmlMethodInvoker`] that can invoke methods on the underlying
108 /// `QObject` from any thread.
109 ///
110 /// # Example
111 ///
112 /// ```
113 /// # use qtbridge::{qobject, QObjectHolder};
114 /// # #[qobject]
115 /// # pub mod example {
116 /// # #[derive(Default)]
117 /// # pub struct Backend {}
118 /// # impl Backend {
119 /// # #[qsignal]
120 /// # pub fn data_ready(&mut self);
121 /// # }
122 /// # }
123 /// # use example::Backend;
124 /// let backend = Backend::default_with_attached_qobject();
125 /// let invoker = backend.borrow().get_qml_method_invoker();
126 /// invoker.invoke_method("dataReady");
127 /// ```
128 fn get_qml_method_invoker(&self) -> QmlMethodInvoker
129 {
130 QmlMethodInvoker::new(self)
131 }
132
133 /// This function has to be implemented on the specific type and
134 /// provides the conversion from the specific type to the dynamic
135 /// trait type.
136 ///
137 /// This function ensures that the type indeed implements the trait
138 /// specified by the [`QRustProxy`].
139 #[doc(hidden)]
140 fn as_adaptor_trait(rust_obj_rc: Rc<RefCell<Self>>) -> Rc<RefCell<<Self::ProxyRust as QRustProxy>::AdapterType>>;
141
142 /// Register the given Rust object instance in the multiton.
143 /// Create Rust and C++ proxies and links them to the Rust object.
144 /// If `construction` is `AtAddress`, the C++ proxy is created using
145 /// placement new operator at respective address
146 #[doc(hidden)]
147 fn register_instance_in_map(rust_obj_rc: Rc<RefCell<Self>>, construction: ConstructionMode) {
148 let key = (*rust_obj_rc).as_ptr() as *const u8;
149 let dyn_rc = Self::as_adaptor_trait(rust_obj_rc);
150 let dynamic_meta = <Self as QMetaInfo>::get_shared_dynamic_meta_object_data();
151 let proxy = Self::ProxyRust::new(&dyn_rc, dynamic_meta, construction, Box::new(move || Self::unregister_instance_in_map(key)));
152 Self::try_borrow_mut_proxies_map(|proxies| {
153 proxies.insert(key, proxy as *const u8);
154 })
155 }
156
157 /// Removes the entry associated with the specified Rust object from the multiton map.
158 #[doc(hidden)]
159 fn unregister_instance_in_map(rust_obj_ptr: *const u8) {
160 Self::try_borrow_mut_proxies_map(|proxies| proxies.remove(&rust_obj_ptr))
161 .expect("Proxy object for rust object is not registered")
162 .cast_mut();
163 }
164
165 /// Creates a default-initialized instance and attaches the required
166 /// [`QObject`], enabling its use in QML.
167 ///
168 /// The returned `Rc<RefCell<Self>>` owns the object. The QML side can access it only through
169 /// a weak pointer.
170 ///
171 /// The `drop` implementation of `Self` calls `detach_qobject` and thus destroys the associated
172 /// `QObject`, which removes it from the QML side as well. If `Self` has a custom [`Drop`]
173 /// implementation, you need to call [`Self::detach_qobject`] manually.
174 ///
175 /// The attached `QObject` is bound to this specific allocation, so the object's identity and
176 /// lifetime must both be preserved.
177 ///
178 /// **Do not move or replace the `Self` inside the `Rc<RefCell<Self>>`.**
179 /// Operations such as `Rc::try_unwrap`, `into_inner`, or `get_mut`-then-move will break the
180 /// connection between `self` and the associated `QObject`. Once the `Self` lives
181 /// elsewhere, the next call in either direction can no longer reach it.
182 fn default_with_attached_qobject() -> std::rc::Rc<std::cell::RefCell<Self>> {
183 let instance = Default::default();
184 Self::attach_qobject(&instance);
185 instance
186 }
187
188 /// Attaches a dedicated [`QObject`] to an existing `instance`,
189 /// enabling its use in QML.
190 fn attach_qobject(instance: &std::rc::Rc<std::cell::RefCell<Self>>) {
191 Self::register_instance_in_map(
192 instance.clone(),
193 ConstructionMode::Weak
194 );
195 }
196
197 /// Detaches and removes the dedicated [`QObject`] from this instance.
198 ///
199 /// Called automatically by the [`Drop`] implementation generated by the
200 /// `qobject` macro.
201 fn detach_qobject(&self) {
202 let qobj_ptr = self.get_qobject_ptr();
203 if !qobj_ptr.is_null() {
204 QObject::delete(qobj_ptr);
205 }
206 }
207
208 /// Returns a [`QVariant`] containing a pointer to this object.
209 fn as_qvariant(&self) -> QVariant {
210 let qobj_ptr = self.get_qobject_ptr();
211 assert!(!qobj_ptr.is_null(), "QObject is not attached");
212 qobj_ptr.into()
213 }
214
215}