winapi_easy/
process.rs

1//! Processes, threads.
2
3use std::ffi::c_void;
4use std::io;
5use std::mem;
6
7use ntapi::ntpsapi::{
8    NtSetInformationProcess,
9    ProcessIoPriority,
10};
11use num_enum::{
12    IntoPrimitive,
13    TryFromPrimitive,
14};
15use windows::Wdk::System::Threading::{
16    NtQueryInformationProcess,
17    PROCESSINFOCLASS,
18};
19use windows::Win32::Foundation::{
20    HANDLE,
21    HMODULE,
22};
23use windows::Win32::System::Diagnostics::ToolHelp::{
24    CreateToolhelp32Snapshot,
25    Thread32First,
26    Thread32Next,
27    TH32CS_SNAPTHREAD,
28    THREADENTRY32,
29};
30use windows::Win32::System::LibraryLoader::GetModuleHandleExW;
31use windows::Win32::System::Threading;
32use windows::Win32::System::Threading::{
33    GetCurrentProcess,
34    GetCurrentProcessId,
35    GetCurrentThread,
36    GetCurrentThreadId,
37    GetProcessId,
38    GetThreadId,
39    OpenProcess,
40    OpenThread,
41    SetPriorityClass,
42    SetThreadPriority,
43    PROCESS_ALL_ACCESS,
44    PROCESS_CREATION_FLAGS,
45    PROCESS_MODE_BACKGROUND_BEGIN,
46    PROCESS_MODE_BACKGROUND_END,
47    THREAD_ALL_ACCESS,
48    THREAD_MODE_BACKGROUND_BEGIN,
49    THREAD_MODE_BACKGROUND_END,
50    THREAD_PRIORITY,
51};
52
53use crate::internal::{
54    custom_err_with_code,
55    AutoClose,
56    ReturnValue,
57};
58
59/// A Windows process.
60pub struct Process {
61    handle: AutoClose<HANDLE>,
62}
63
64impl Process {
65    /// Constructs a special handle that always points to the current process.
66    ///
67    /// When transferred to a different process, it will point to that process when used from it.
68    pub fn current() -> Self {
69        let pseudo_handle = unsafe { GetCurrentProcess() };
70        Self::from_maybe_null(pseudo_handle).expect("Pseudo process handle should never be null")
71    }
72
73    /// Tries to acquire a process handle from an ID.
74    ///
75    /// This may fail due to insufficient access rights.
76    pub fn from_id<I>(id: I) -> io::Result<Self>
77    where
78        I: Into<ProcessId>,
79    {
80        let raw_handle = unsafe { OpenProcess(PROCESS_ALL_ACCESS, false, id.into().0)? };
81        Ok(Self {
82            handle: raw_handle.into(),
83        })
84    }
85
86    /// Sets the current process to background processing mode.
87    ///
88    /// This will also lower the I/O priority of the process, which will lower the impact of heavy disk I/O on other processes.
89    pub fn begin_background_mode() -> io::Result<()> {
90        unsafe { SetPriorityClass(Self::current().handle.entity, PROCESS_MODE_BACKGROUND_BEGIN)? };
91        Ok(())
92    }
93
94    /// Ends background processing mode for the current process.
95    pub fn end_background_mode() -> io::Result<()> {
96        unsafe { SetPriorityClass(Self::current().handle.entity, PROCESS_MODE_BACKGROUND_END)? };
97        Ok(())
98    }
99
100    /// Sets the priority of the process.
101    ///
102    /// # Examples
103    ///
104    /// ```
105    /// use winapi_easy::process::{Process, ProcessPriority};
106    ///
107    /// Process::current().set_priority(ProcessPriority::Normal)?;
108    ///
109    /// # Result::<(), std::io::Error>::Ok(())
110    /// ```
111    pub fn set_priority(&mut self, priority: ProcessPriority) -> io::Result<()> {
112        unsafe { SetPriorityClass(self.handle.entity, priority.into())? };
113        Ok(())
114    }
115
116    /// Returns the I/O priority of the process.
117    ///
118    /// Will return `None` if it is an unknown value.
119    pub fn get_io_priority(&self) -> io::Result<Option<IoPriority>> {
120        let mut raw_io_priority: i32 = 0;
121        let mut return_length: u32 = 0;
122        let ret_val = unsafe {
123            NtQueryInformationProcess(
124                self.handle.entity,
125                ProcessInformationClass::ProcessIoPriority.into(),
126                &mut raw_io_priority as *mut i32 as *mut c_void,
127                mem::size_of::<i32>() as u32,
128                &mut return_length,
129            )
130        };
131        ret_val.0.if_non_null_to_error(|| {
132            custom_err_with_code("Getting IO priority failed", ret_val.0)
133        })?;
134        Ok(IoPriority::try_from(raw_io_priority as u32).ok())
135    }
136
137    pub fn set_io_priority(&mut self, io_priority: IoPriority) -> io::Result<()> {
138        let ret_val = unsafe {
139            NtSetInformationProcess(
140                self.handle.entity.0 as *mut ntapi::winapi::ctypes::c_void,
141                i32::from(ProcessInformationClass::ProcessIoPriority)
142                    .try_into()
143                    .unwrap(),
144                &mut u32::from(io_priority) as *mut u32 as *mut ntapi::winapi::ctypes::c_void,
145                mem::size_of::<u32>() as u32,
146            )
147        };
148        ret_val
149            .if_non_null_to_error(|| custom_err_with_code("Setting IO priority failed", ret_val))?;
150        Ok(())
151    }
152
153    pub fn get_id(&self) -> ProcessId {
154        let id = unsafe { GetProcessId(self.handle.entity) };
155        ProcessId(id)
156    }
157
158    #[allow(dead_code)]
159    fn from_non_null(handle: HANDLE) -> Self {
160        Self {
161            handle: handle.into(),
162        }
163    }
164
165    fn from_maybe_null(handle: HANDLE) -> Option<Self> {
166        if !handle.is_null() {
167            Some(Self {
168                handle: handle.into(),
169            })
170        } else {
171            None
172        }
173    }
174}
175
176impl TryFrom<ProcessId> for Process {
177    type Error = io::Error;
178
179    fn try_from(value: ProcessId) -> Result<Self, Self::Error> {
180        Process::from_id(value)
181    }
182}
183
184/// ID of a [`Process`].
185#[derive(Copy, Clone, Eq, PartialEq, Debug)]
186pub struct ProcessId(pub(crate) u32);
187
188impl ProcessId {
189    /// Returns the current process ID.
190    pub fn current() -> Self {
191        Self(unsafe { GetCurrentProcessId() })
192    }
193}
194
195/// A thread inside a Windows process.
196pub struct Thread {
197    handle: AutoClose<HANDLE>,
198}
199
200impl Thread {
201    /// Constructs a special handle that always points to the current thread.
202    ///
203    /// When transferred to a different thread, it will point to that thread when used from it.
204    pub fn current() -> Self {
205        let pseudo_handle = unsafe { GetCurrentThread() };
206        Self::from_maybe_null(pseudo_handle).expect("Pseudo thread handle should never be null")
207    }
208
209    /// Tries to acquire a thread handle from an ID.
210    ///
211    /// This may fail due to insufficient access rights.
212    pub fn from_id<I>(id: I) -> io::Result<Self>
213    where
214        I: Into<ThreadId>,
215    {
216        let raw_handle = unsafe { OpenThread(THREAD_ALL_ACCESS, false, id.into().0)? };
217        Ok(Self {
218            handle: raw_handle.into(),
219        })
220    }
221
222    /// Sets the current thread to background processing mode.
223    ///
224    /// This will also lower the I/O priority of the thread, which will lower the impact of heavy disk I/O on other threads and processes.
225    pub fn begin_background_mode() -> io::Result<()> {
226        unsafe { SetThreadPriority(Self::current().handle.entity, THREAD_MODE_BACKGROUND_BEGIN)? };
227        Ok(())
228    }
229
230    /// Ends background processing mode for the current thread.
231    pub fn end_background_mode() -> io::Result<()> {
232        unsafe { SetThreadPriority(Self::current().handle.entity, THREAD_MODE_BACKGROUND_END)? };
233        Ok(())
234    }
235
236    /// Sets the priority of the thread.
237    ///
238    /// # Examples
239    ///
240    /// ```
241    /// use winapi_easy::process::{Thread, ThreadPriority};
242    ///
243    /// Thread::current().set_priority(ThreadPriority::Normal)?;
244    ///
245    /// # Result::<(), std::io::Error>::Ok(())
246    /// ```
247    pub fn set_priority(&mut self, priority: ThreadPriority) -> Result<(), io::Error> {
248        unsafe { SetThreadPriority(self.handle.entity, priority.into())? };
249        Ok(())
250    }
251
252    pub fn get_id(&self) -> ThreadId {
253        let id = unsafe { GetThreadId(self.handle.entity) };
254        ThreadId(id)
255    }
256
257    #[allow(dead_code)]
258    fn from_non_null(handle: HANDLE) -> Self {
259        Self {
260            handle: handle.into(),
261        }
262    }
263
264    fn from_maybe_null(handle: HANDLE) -> Option<Self> {
265        if !handle.is_null() {
266            Some(Self {
267                handle: handle.into(),
268            })
269        } else {
270            None
271        }
272    }
273}
274
275impl TryFrom<ThreadId> for Thread {
276    type Error = io::Error;
277
278    fn try_from(value: ThreadId) -> Result<Self, Self::Error> {
279        Thread::from_id(value)
280    }
281}
282
283/// ID of a [`Thread`].
284#[derive(Copy, Clone, Eq, PartialEq, Debug)]
285pub struct ThreadId(pub(crate) u32);
286
287impl ThreadId {
288    /// Returns the current thread ID.
289    pub fn current() -> Self {
290        Self(unsafe { GetCurrentThreadId() })
291    }
292}
293
294/// Infos about a [`Thread`].
295#[derive(Copy, Clone, Debug)]
296pub struct ThreadInfo {
297    raw_entry: THREADENTRY32,
298}
299
300impl ThreadInfo {
301    /// Returns all threads of all processes.
302    pub fn all_threads() -> io::Result<Vec<Self>> {
303        #[inline(always)]
304        fn get_empty_thread_entry() -> THREADENTRY32 {
305            THREADENTRY32 {
306                dwSize: mem::size_of::<THREADENTRY32>().try_into().unwrap(),
307                ..Default::default()
308            }
309        }
310        let mut result: Vec<Self> = Vec::new();
311        let snapshot: AutoClose<HANDLE> =
312            unsafe { CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0)? }.into();
313
314        let mut thread_entry = get_empty_thread_entry();
315        unsafe {
316            Thread32First(snapshot.entity, &mut thread_entry)?;
317        }
318        result.push(Self::from_raw(thread_entry));
319        loop {
320            let mut thread_entry = get_empty_thread_entry();
321            let next_ret_val = unsafe { Thread32Next(snapshot.entity, &mut thread_entry) };
322            if next_ret_val.is_ok() {
323                result.push(Self::from_raw(thread_entry));
324            } else {
325                break;
326            }
327        }
328        Ok(result)
329    }
330
331    /// Returns all threads of the given process.
332    pub fn all_process_threads<P>(process_id: P) -> io::Result<Vec<Self>>
333    where
334        P: Into<ProcessId>,
335    {
336        let pid: ProcessId = process_id.into();
337        let result = Self::all_threads()?
338            .into_iter()
339            .filter(|thread_info| thread_info.get_owner_process_id() == pid)
340            .collect();
341        Ok(result)
342    }
343
344    fn from_raw(raw_info: THREADENTRY32) -> Self {
345        ThreadInfo {
346            raw_entry: raw_info,
347        }
348    }
349
350    /// Returns the ID of the thread.
351    pub fn get_thread_id(&self) -> ThreadId {
352        ThreadId(self.raw_entry.th32ThreadID)
353    }
354
355    /// Returns the ID of the process that contains the thread.
356    pub fn get_owner_process_id(&self) -> ProcessId {
357        ProcessId(self.raw_entry.th32OwnerProcessID)
358    }
359}
360
361/// Process CPU priority.
362#[derive(IntoPrimitive, Clone, Copy, Eq, PartialEq, Debug)]
363#[repr(u32)]
364pub enum ProcessPriority {
365    Idle = Threading::IDLE_PRIORITY_CLASS.0,
366    BelowNormal = Threading::BELOW_NORMAL_PRIORITY_CLASS.0,
367    Normal = Threading::NORMAL_PRIORITY_CLASS.0,
368    AboveNormal = Threading::ABOVE_NORMAL_PRIORITY_CLASS.0,
369    High = Threading::HIGH_PRIORITY_CLASS.0,
370    Realtime = Threading::REALTIME_PRIORITY_CLASS.0,
371}
372
373impl From<ProcessPriority> for PROCESS_CREATION_FLAGS {
374    fn from(value: ProcessPriority) -> Self {
375        PROCESS_CREATION_FLAGS(value.into())
376    }
377}
378
379/// Thread CPU priority.
380#[derive(IntoPrimitive, Clone, Copy, Eq, PartialEq, Debug)]
381#[repr(i32)]
382pub enum ThreadPriority {
383    Idle = Threading::THREAD_PRIORITY_IDLE.0,
384    Lowest = Threading::THREAD_PRIORITY_LOWEST.0,
385    BelowNormal = Threading::THREAD_PRIORITY_BELOW_NORMAL.0,
386    Normal = Threading::THREAD_PRIORITY_NORMAL.0,
387    AboveNormal = Threading::THREAD_PRIORITY_ABOVE_NORMAL.0,
388    Highest = Threading::THREAD_PRIORITY_HIGHEST.0,
389    TimeCritical = Threading::THREAD_PRIORITY_TIME_CRITICAL.0,
390}
391
392impl From<ThreadPriority> for THREAD_PRIORITY {
393    fn from(value: ThreadPriority) -> Self {
394        THREAD_PRIORITY(value.into())
395    }
396}
397
398/// Process or thread IO priority. This is independent of the standard CPU priorities.
399#[derive(IntoPrimitive, TryFromPrimitive, Clone, Copy, Eq, PartialEq, Debug)]
400#[repr(u32)]
401pub enum IoPriority {
402    VeryLow = 0,
403    Low = 1,
404    Normal = 2,
405}
406
407#[derive(IntoPrimitive, Clone, Copy, Debug)]
408#[repr(i32)]
409enum ProcessInformationClass {
410    ProcessIoPriority = ProcessIoPriority as i32,
411}
412
413impl From<ProcessInformationClass> for PROCESSINFOCLASS {
414    fn from(value: ProcessInformationClass) -> Self {
415        PROCESSINFOCLASS(value.into())
416    }
417}
418
419/// A handle to a module (EXE or DLL).
420pub struct ModuleHandle {
421    #[allow(dead_code)]
422    raw_handle: HMODULE,
423}
424
425impl ModuleHandle {
426    /// Returns the module handle of the currently executed code.
427    pub fn get_current() -> io::Result<Self> {
428        let raw_handle = unsafe {
429            let mut h_module: HMODULE = Default::default();
430            GetModuleHandleExW(0, None, &mut h_module)?;
431            h_module.if_null_get_last_error()?
432        };
433        Ok(ModuleHandle { raw_handle })
434    }
435}
436
437#[cfg(test)]
438mod tests {
439    use more_asserts::*;
440
441    #[cfg(feature = "ui")]
442    use crate::ui::WindowHandle;
443
444    use super::*;
445
446    #[test]
447    fn get_all_threads() -> io::Result<()> {
448        let all_threads = ThreadInfo::all_threads()?;
449        assert_gt!(all_threads.len(), 0);
450        Ok(())
451    }
452
453    #[test]
454    fn get_all_process_threads() -> io::Result<()> {
455        let all_threads = ThreadInfo::all_process_threads(Process::current().get_id())?;
456        assert_gt!(all_threads.len(), 0);
457        Ok(())
458    }
459
460    #[cfg(feature = "ui")]
461    #[test]
462    fn get_all_threads_and_windows() -> io::Result<()> {
463        let all_threads = ThreadInfo::all_threads()?;
464        let all_windows: Vec<WindowHandle> = all_threads
465            .into_iter()
466            .flat_map(|thread_info| WindowHandle::get_nonchild_windows(thread_info.get_thread_id()))
467            .collect();
468        assert_gt!(all_windows.len(), 0);
469        Ok(())
470    }
471
472    #[test]
473    fn set_get_io_priority() -> io::Result<()> {
474        let mut curr_process = Process::current();
475        let target_priority = IoPriority::Low;
476        curr_process.set_io_priority(target_priority)?;
477        let priority = curr_process.get_io_priority()?.unwrap();
478        assert_eq!(priority, target_priority);
479        Ok(())
480    }
481}