SoFunction
Updated on 2025-03-04

How to get all running process information by calling the Windows API

Preface

Official Windows API DocumentationProvides C++ call examples. I want to try to implement it with Rust recently. This series of blogs records the implementation process.

rely

Rust calls to Windows API requires the introduction of dependencieswinapi,existAdd dependencies to

winapi = "0.3.9"

Calling different API sets requires the use of corresponding functionsfeaturesA good way to judge is to add the feature in which header file you see in Microsoft's official documentation, for example, this article needs to be used and Then add these two features to it

winapi = { version = "0.3.9", features = ["tlhelp32", "processthreadsapi"] }

accomplish

Approximate steps

  • Create a process snapshot and get the snapshot handle
  • Iterate through the processes in the snapshot (implemented in an iterator) to obtain the data of each process
  • Release the snapshot handle

Create a snapshot handle

Create a process snapshot to useCreateToolhelp32Snapshot Method, it's inDefined in the header file.

use winapi::um::tlhelp32::{CreateToolhelp32Snapshot, TH32CS_SNAPPROCESS};
use winapi::um::handleapi::INVALID_HANDLE_VALUE;
use winapi::um::errhandlingapi::GetLastError;
/// Save a process snapshot and return a process information iterator `ProcessInformationIterator`pub fn list() -> Result<ProcessInformationIterator, String> {
    let process_snapshot: HANDLE = unsafe { CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0) };
    if process_snapshot == INVALID_HANDLE_VALUE || process_snapshot.is_null() {
        unsafe {
            return Err(format!("Cannot list processes, code: {}", GetLastError()));
        }
    }
    Ok(ProcessInformationIterator::new(process_snapshot))
}

In the codeProcessInfomationIteratorIt is custom, and for clearer structure, iterator mode is used here to read.

If the process snapshot is saved, the returned handle will be an invalid value (there are two conditions or relationships used here to determine whether it is invalid. In fact, you can use either of them. They both represent an "empty" memory or an "empty" pointer).GetLastErrorThe method can obtain the error code, and the corresponding meaning of the error code is shown inSystem error code description, it can also be parsed into readable text through the API. This article will be introduced later. Here we will use code to simply express it.

Implement an iterator

I won't go into details about the iterator pattern implementation method in Rust. You only need to know that at least you need to implement an iterator.An iterative element ItemandAn iterator that implements the Iterator featureThat's it.

Iterative element Item

let vec = vec![1, 2]
for item in vec {
	...
}

The item in the above code is the specific element in the iterator. Because there is a lot of process information, a structure is used to store it here.

use winapi::um::tlhelp32::PROCESSENTRY32;
pub struct ProcessInformation {
    inner: PROCESSENTRY32,
}

Here, the process's data is not directly parsed and then stored in the structure, but directlyPROCESSENTRY32The structure is made into a wrapper. Here, in order to save unnecessary calculations, the PROCESSENTRY32 directly read from the handle is not all information that is directly readable by Rust and is parsed when needed. It is more convenient to read data through the getter method to expand later. The following is the specific method implementation of ProcessInformation.

use winapi::um::processthreadsapi::{GetPriorityClass, OpenProcess};
use winapi::um::errhandlingapi::GetLastError;
use winapi::um::tlhelp32::PROCESSENTRY32;
use winapi::um::winnt::{HANDLE, PROCESS_ALL_ACCESS};
pub(crate) fn char_arr_to_string(chars: &[i8]) -> String {
    chars.into_iter().map(|&c| c as u8 as char).collect()
}
impl ProcessInformation {
    pub(crate) fn new(entry: PROCESSENTRY32) -> ProcessInformation {
        ProcessInformation {
            inner: entry
        }
    }
    /// Get the process ID    pub fn get_pid(&self) -> u32 {
        .th32ProcessID as u32
    }
    /// Get the process name    pub fn get_name(&self) -> String {
        char_arr_to_string(&)
    }
    /// Get the parent process ID    pub fn get_parent_pid(&self) -> u32 {
        .th32ParentProcessID as u32
    }
    /// Get the number of threads    pub fn get_thread_count(&self) -> usize {
         as usize
    }
    /// Get the basic priority value    pub fn get_priority_base(&self) -> i64 {
         as i64
    }
    /// Get the priority category. If the calling process does not have specified permissions, it may fail to obtain. Return `None` if it fails.    pub fn get_priority_class(&self) -> Option<i32> {
        let mut priority_class = None;
        unsafe {
            let handle = OpenProcess(PROCESS_ALL_ACCESS, 0, .th32ProcessID);
            if !handle.is_null() {
                let class = GetPriorityClass(handle);
                CloseHandle(handle);
                priority_class = Some(class as i32);
            }
        }
        priority_class
    }
}

Iterator implementation

The iterator needs to save some iterative traversal states, so in addition to the snapshot handle saved earlier, the iterative index and the state of releasing the handle should also be stored.Iterators are irreversible

use winapi::um::winnt::HANDLE;
pub struct ProcessInformationIterator {
    process_snapshot: HANDLE,
    index: usize,
    finished: bool,
}
impl ProcessInformationIterator {
    pub(crate) fn new(process_snapshot: HANDLE) -> ProcessInformationIterator {
        ProcessInformationIterator {
            process_snapshot,
            index: 0,
            finished: false,
        }
    }
}

Then there is the specific implementation of the iterator

use winapi::um::winnt::HANDLE;
use winapi::um::tlhelp32::{Process32First, Process32Next, PROCESSENTRY32};
use winapi::um::handleapi::CloseHandle;
impl Iterator for ProcessInformationIterator {
    type Item = ProcessInformation;
    fn next(&mut self) -> Option<Self::Item> {
        if  {
            return None;
        }
         += 1;
        let mut entry: PROCESSENTRY32 = unsafe { std::mem::zeroed() };
         = size_of::<PROCESSENTRY32>() as u32;
        // Read the first process in the snapshot        let res = unsafe {
            if  == 1 {
                Process32First(self.process_snapshot, &mut entry)
            } else {
                Process32Next(self.process_snapshot, &mut entry)
            }
        };
        if res == 0 {
            unsafe {
                CloseHandle(self.process_snapshot);
            }
             = true;
            return None;
        }
        Some(ProcessInformation::new(entry))
    }
}

There are a few points to explain in the above code:

  • When initializing the entry, you need to first reach a memory space with all 0 values, and it is not allocated as a Rust reference space. Here, the unsafe method is used.std::mem::zeroed()
  • Before reading process Entry, you need to specify the memory length.size_of::<PROCESSENTRY32>()to obtain and assign value to
  • The first element needs to be called during traversalProcess32FirstRead, subsequent useProcess32NextRead
  • Remember to close the snapshot script when traversingCloseHandleinterface

Special case handling: If the user has not finished iteration, the above code implementation may cause the snapshot handle to not be released, so it is also necessary to implement a Drop feature for the iterator, and release the snapshot handle when the iterator is released.

impl Drop for ProcessInformationIterator {
    fn drop(&amp;mut self) {
        if  {
            return;
        }
        // Release the snapshot handle.        unsafe {
            CloseHandle(self.process_snapshot);
        }
         = true;
    }
}

Code summary

I put it in custom when I wrote itutils::process::winThe specific reference path is adjusted according to your own situation

document

use crate::utils::process::win::process_information::ProcessInformationIterator;
use winapi::um::tlhelp32::{CreateToolhelp32Snapshot, TH32CS_SNAPPROCESS};
use winapi::um::errhandlingapi::GetLastError;
use winapi::um::handleapi::INVALID_HANDLE_VALUE;
use winapi::um::winnt::HANDLE;
pub mod process_information;
pub fn list() -> Result<ProcessInformationIterator, String> {
    let process_snapshot: HANDLE = unsafe { CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0) };
    if process_snapshot == INVALID_HANDLE_VALUE || process_snapshot.is_null() {
        unsafe {
            return Err(format!("Cannot list processes, code: {}", GetLastError()));
        }
    }
    Ok(ProcessInformationIterator::new(process_snapshot))
}
pub(crate) fn char_arr_to_string(chars: &[i8]) -> String {
    chars.into_iter().map(|&c| c as u8 as char).collect()
}

process_information.rsdocument

use crate::utils::process::win::char_arr_to_string;
use crate::utils::process::win::process_module::ProcessModuleIterator;
use winapi::um::errhandlingapi::GetLastError;
use winapi::um::handleapi::CloseHandle;
use winapi::um::processthreadsapi::{GetPriorityClass, OpenProcess};
use winapi::um::tlhelp32::{Process32First, Process32Next, PROCESSENTRY32};
use winapi::um::winnt::{HANDLE, PROCESS_ALL_ACCESS};
//// Rust packaging implementation of [PROCESSENTRY32](/zh-cn/windows/win32/api/tlhelp32/ns-tlhelp32-processentry32)pub struct ProcessInformation {
    inner: PROCESSENTRY32,
}
impl ProcessInformation {
    pub(crate) fn new(entry: PROCESSENTRY32) -&gt; ProcessInformation {
        ProcessInformation {
            inner: entry
        }
    }
    /// Get the process ID    pub fn get_pid(&amp;self) -&gt; u32 {
        .th32ProcessID as u32
    }
    /// Get the process name    pub fn get_name(&amp;self) -&gt; String {
        char_arr_to_string(&amp;)
    }
    /// Get the parent process ID    pub fn get_parent_pid(&amp;self) -&gt; u32 {
        .th32ParentProcessID as u32
    }
    /// Get the number of threads    pub fn get_thread_count(&amp;self) -&gt; usize {
         as usize
    }
    /// Get the basic priority value    pub fn get_priority_base(&amp;self) -&gt; i64 {
         as i64
    }
    /// Get the priority category. If the calling process does not have specified permissions, it may fail to obtain. Return `None` if it fails.    pub fn get_priority_class(&amp;self) -&gt; Option&lt;i32&gt; {
        let mut priority_class = None;
        unsafe {
            let handle = OpenProcess(PROCESS_ALL_ACCESS, 0, .th32ProcessID);
            if !handle.is_null() {
                let class = GetPriorityClass(handle);
                CloseHandle(handle);
                priority_class = Some(class as i32);
            }
        }
        priority_class
    }
}
pub struct ProcessInformationIterator {
    process_snapshot: HANDLE,
    index: usize,
    finished: bool,
}
impl ProcessInformationIterator {
    pub(crate) fn new(process_snapshot: HANDLE) -&gt; ProcessInformationIterator {
        ProcessInformationIterator {
            process_snapshot,
            index: 0,
            finished: false,
        }
    }
}
impl Drop for ProcessInformationIterator {
    fn drop(&amp;mut self) {
        if  {
            return;
        }
        // Release the snapshot handle.        unsafe {
            CloseHandle(self.process_snapshot);
        }
         = true;
    }
}
impl Iterator for ProcessInformationIterator {
    type Item = ProcessInformation;
    fn next(&amp;mut self) -&gt; Option&lt;Self::Item&gt; {
        if  {
            return None;
        }
         += 1;
        let mut entry: PROCESSENTRY32 = unsafe { std::mem::zeroed() };
         = size_of::&lt;PROCESSENTRY32&gt;() as u32;
        // Read the first process in the snapshot        let res = unsafe {
            if  == 1 {
                Process32First(self.process_snapshot, &amp;mut entry)
            } else {
                Process32Next(self.process_snapshot, &amp;mut entry)
            }
        };
        if res == 0 {
            unsafe {
                CloseHandle(self.process_snapshot);
            }
             = true;
            return None;
        }
        Some(ProcessInformation::new(entry))
    }
}

test

Test code

#[test]
pub fn test_list() {
    println!("PID\tName\tParent PID\tThreads\tPriority Base\tPriority Class");
    let iter = list().unwrap();
    for process in iter {
        let pid = process.get_pid();
        let name = process.get_name();
        let parent_pid = process.get_parent_pid();
        let thread_count = process.get_thread_count();
        let priority_base = process.get_priority_base();
        let priority_class = process.get_priority_class();
        println!("{}\t{}\t{}\t{}\t{}\t{:?}", pid, name, parent_pid, thread_count, priority_base, priority_class)
    }
}

result

PID     Name    Parent PID      Threads Priority Base   Priority Class
0       [System Process]        0       6       0       None
4       System  0       236     8       None
64      Secure System   4       0       8       None
132     Registry        4       4       8       None
504             4       2       11      None
728            712     11      13      None
824          712     1       13      None
832            816     15      13      None
...
12624         12148   19      8       Some(32)
16352         12148   18      4       Some(64)
14904         12148   17      4       Some(64)
14672           892     2       8       None
11160         12148   20      4       Some(64)
18048         12148   19      4       Some(64)
5452          12148   14      4       Some(64)
14468        892     3       8       None
18060         12148   14      4       Some(64)
17748        688     8       8       Some(32)
16084          16648   27      8       Some(32)
9008     10644   6       8       Some(32)
15516          10644   4       8       Some(32)
11312          15516   4       8       Some(32)

This is the article about Rust calling the Windows API to obtain all running process information. For more related content on Rust calling the Windows API, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!