Skip to main content

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}