use crate::error::{CrosswinError, Result};
use crate::windows::handles::Handle;
#[cfg(feature = "win32")]
use windows::Win32::System::Diagnostics::ToolHelp::{
CreateToolhelp32Snapshot, Thread32First, Thread32Next, THREADENTRY32, TH32CS_SNAPTHREAD,
};
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ThreadInfo {
pub thread_id: u32,
pub process_id: u32,
pub base_priority: i32,
}
#[derive(Debug)]
pub struct Thread {
handle: Option<Handle>,
}
impl Thread {
pub fn current() -> Result<Self> {
Ok(Thread { handle: None })
}
pub fn handle(&self) -> Option<&Handle> {
self.handle.as_ref()
}
}
pub async fn list_threads(pid: u32) -> Result<Vec<ThreadInfo>> {
#[cfg(feature = "win32")]
{
tokio::task::spawn_blocking(move || enumerate_threads_blocking(pid))
.await
.map_err(|e| CrosswinError::win32("list_threads", 0, format!("join error: {}", e)))?
}
#[cfg(not(feature = "win32"))]
{
let _ = pid;
Ok(Vec::new())
}
}
#[cfg(feature = "win32")]
fn enumerate_threads_blocking(pid: u32) -> Result<Vec<ThreadInfo>> {
unsafe {
let snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0).map_err(|e| {
CrosswinError::win32(
"CreateToolhelp32Snapshot",
e.code().0 as u32,
e.to_string(),
)
})?;
let _guard = Handle::from_windows_handle(snapshot);
let mut entry = THREADENTRY32::default();
entry.dwSize = std::mem::size_of::<THREADENTRY32>() as u32;
if Thread32First(snapshot, &mut entry).is_err() {
return Ok(Vec::new());
}
let mut threads = Vec::new();
loop {
if entry.th32OwnerProcessID == pid {
threads.push(ThreadInfo {
thread_id: entry.th32ThreadID,
process_id: entry.th32OwnerProcessID,
base_priority: entry.tpBasePri,
});
}
if Thread32Next(snapshot, &mut entry).is_err() {
break;
}
}
Ok(threads)
}
}
#[cfg(test)]
mod tests {
#[cfg(feature = "win32")]
#[tokio::test]
async fn list_threads_current_process() {
let pid = std::process::id();
let threads = super::list_threads(pid).await.expect("list_threads failed");
assert!(!threads.is_empty(), "current process must have at least one thread");
for t in &threads {
assert_eq!(t.process_id, pid);
}
}
}