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 {}