Skip to main content

il2cpp_bridge_rs/api/core/runtime/
thread.rs

1//! IL2CPP thread attachment and lifecycle helpers.
2use super::super::api;
3#[cfg(dev_release)]
4use crate::logger;
5use std::ffi::c_void;
6use std::ptr;
7
8/// Wrapper around an IL2CPP VM thread attachment.
9///
10/// Use this type whenever your code touches IL2CPP from a thread that was not
11/// created by the crate's initialization flow. The common pattern is scoped
12/// attachment via [`Thread::attach`] with `auto_detach = true`.
13pub struct Thread {
14    /// Pointer to the internal IL2CPP thread
15    thread_ptr: *mut c_void,
16    /// Whether to automatically detach the thread on drop
17    auto_detach: bool,
18}
19
20impl Thread {
21    /// Creates a Thread wrapper from a raw pointer
22    ///
23    /// # Arguments
24    /// * `thread_ptr` - The raw IL2CPP thread pointer
25    /// * `auto_detach` - Whether to detach the thread when this struct is dropped
26    ///
27    /// # Safety
28    /// * `thread_ptr` must be a valid IL2CPP thread pointer.
29    pub unsafe fn from_ptr(thread_ptr: *mut c_void, auto_detach: bool) -> Self {
30        Self {
31            thread_ptr,
32            auto_detach,
33        }
34    }
35
36    /// Returns the raw pointer to the thread
37    pub fn as_ptr(&self) -> *mut c_void {
38        self.thread_ptr
39    }
40
41    /// Manually detaches the thread from the IL2CPP domain
42    ///
43    /// This will detach the thread immediately and prevent auto-detachment on drop.
44    pub fn detach(mut self) {
45        if !self.thread_ptr.is_null() {
46            unsafe {
47                api::thread_detach(self.thread_ptr);
48            }
49            #[cfg(dev_release)]
50            logger::info("Thread manually detached from IL2CPP");
51            self.thread_ptr = ptr::null_mut();
52        }
53        self.auto_detach = false;
54    }
55
56    /// Checks if the thread is a VM thread
57    pub fn is_vm_thread(&self) -> bool {
58        if self.thread_ptr.is_null() {
59            return false;
60        }
61        unsafe { api::is_vm_thread(self.thread_ptr) }
62    }
63
64    /// Gets the current attached thread
65    ///
66    /// # Returns
67    /// * `Option<Self>` - The current thread wrapper if attached, or None
68    pub fn current() -> Option<Self> {
69        unsafe {
70            let current = api::thread_current();
71            if !current.is_null() {
72                Some(Self::from_ptr(current, false))
73            } else {
74                None
75            }
76        }
77    }
78
79    /// Attaches the current OS thread to the IL2CPP domain.
80    ///
81    /// If the thread is already attached, the existing thread handle is reused.
82    /// When `auto_detach` is `true`, dropping the returned value detaches the
83    /// thread automatically.
84    ///
85    /// Returns `None` if the IL2CPP domain could not be resolved or the runtime
86    /// rejected the attachment request.
87    pub fn attach(auto_detach: bool) -> Option<Self> {
88        unsafe {
89            if Self::is_attached() {
90                #[cfg(dev_release)]
91                logger::info("Thread already attached to IL2CPP, returning existing thread");
92                return Self::current();
93            }
94
95            let domain_ptr = api::domain_get();
96
97            if domain_ptr.is_null() {
98                #[cfg(dev_release)]
99                logger::error("Failed to get IL2CPP domain for thread attachment");
100                return None;
101            }
102
103            let thread_ptr = api::thread_attach(domain_ptr);
104
105            if thread_ptr.is_null() {
106                #[cfg(dev_release)]
107                logger::error("Failed to attach thread to IL2CPP");
108                None
109            } else {
110                #[cfg(dev_release)]
111                logger::info("Thread successfully attached to IL2CPP");
112                Some(Self::from_ptr(thread_ptr, auto_detach))
113            }
114        }
115    }
116
117    /// Checks if the current thread is attached to the IL2CPP domain
118    pub fn is_attached() -> bool {
119        unsafe {
120            let current = api::thread_current();
121            !current.is_null()
122        }
123    }
124
125    /// Gets all attached threads
126    ///
127    /// # Returns
128    /// * `Vec<Thread>` - A list of all threads currently attached to the IL2CPP domain
129    pub fn all() -> Vec<Thread> {
130        unsafe {
131            let mut size: usize = 0;
132            let threads_ptr = api::thread_get_all_attached_threads(&mut size as *mut usize);
133
134            if threads_ptr.is_null() || size == 0 {
135                return Vec::new();
136            }
137
138            let mut threads = Vec::with_capacity(size);
139            for i in 0..size {
140                let thread_ptr = *threads_ptr.add(i);
141                if !thread_ptr.is_null() {
142                    threads.push(Thread::from_ptr(thread_ptr, false));
143                }
144            }
145
146            threads
147        }
148    }
149}
150
151impl Drop for Thread {
152    /// Detaches the thread from the IL2CPP domain if auto-detach is enabled
153    fn drop(&mut self) {
154        if self.auto_detach && !self.thread_ptr.is_null() {
155            unsafe {
156                api::thread_detach(self.thread_ptr);
157            }
158            #[cfg(dev_release)]
159            logger::info("Thread automatically detached from IL2CPP");
160            self.thread_ptr = ptr::null_mut();
161        }
162    }
163}
164
165unsafe impl Send for Thread {}
166unsafe impl Sync for Thread {}