前言
WIndows API官方文档 提供了C++的调用示例,最近想尝试用Rust去实现,本系列博客记录一下实现过程。
依赖
Rust调用Windows API需要引入依赖winapi
,在Cargo.toml
中添加依赖
winapi = "0.3.9"
调用不同的API集就需要使用相应的功能features
,很好的一个判断方式是你在微软官方文档中看到的是在哪个头文件内,就添加哪个feature,例如本篇文章需要使用 tlhelp32.h 和 processthreadsapi.h 那么就将这俩feature添加进去
winapi = { version = "0.3.9", features = ["tlhelp32", "processthreadsapi"] }
实现
大致步骤:
- 创建进程快照,拿到快照句柄
- 遍历快照中的进程(以迭代器的方式实现),得到每个进程的数据
- 释放快照句柄
创建快照句柄
创建进程快照需要用到 CreateToolhelp32Snapshot 方法,它在 tlhelp32.h 头文件中定义。
use winapi::um::tlhelp32::{CreateToolhelp32Snapshot, TH32CS_SNAPPROCESS};
use winapi::um::handleapi::INVALID_HANDLE_VALUE;
use winapi::um::errhandlingapi::GetLastError;/// 保存进程快照并返回进程信息迭代器 `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))
}
代码中的 ProcessInfomationIterator 是自定义的,为了结构更清晰,这里使用迭代器模式来读取。
如果保存进程快照失败,返回的句柄会是一个无效的值(这里用了两个条件或的关系去判断是否无效,其实任用其一都可以,他们都表示一个“空”内存或“空”指针),使用 GetLastError 方法可以获取错误代码,错误代码对应含义见系统错误代码说明,也可以通过API解析成可读文本,这个后面的文章再介绍,这里先用code简单表示一下。
实现迭代器
Rust中的迭代器模式实现方法这里就不多赘述,你只需要知道实现一个迭代器至少需要 一个迭代元素Item 和 一个实现了Iterator特征的迭代器 就可以了。
迭代元素Item
let vec = vec![1, 2]
for item in vec {...
}
上面代码的item就是迭代器中具体的元素,因为进程信息有很多,这里就使用一个结构体来存
use winapi::um::tlhelp32::PROCESSENTRY32;pub struct ProcessInformation {inner: PROCESSENTRY32,
}
这里并没有直接将进程的数据解析之后再存入结构体,而是直接将 PROCESSENTRY32 结构体做一个包装,这里是为了节省不必要的计算,从句柄中直接读取出来的 PROCESSENTRY32 并不是所有信息都是Rust直接可读的,在需要时才解析,并且通过getter方法读取数据更方便以后拓展,下面是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}}/// 获取进程IDpub fn get_pid(&self) -> u32 {self.inner.th32ProcessID as u32}/// 获取进程名pub fn get_name(&self) -> String {char_arr_to_string(&self.inner.szExeFile)}/// 获取父进程IDpub fn get_parent_pid(&self) -> u32 {self.inner.th32ParentProcessID as u32}/// 获取线程数量pub fn get_thread_count(&self) -> usize {self.inner.cntThreads as usize}/// 获取基础优先权值pub fn get_priority_base(&self) -> i64 {self.inner.pcPriClassBase as i64}/// 获取优先权类别,如果调用进程没有指定权限可能会获取失败,失败时返回 `None`pub fn get_priority_class(&self) -> Option<i32> {let mut priority_class = None;unsafe {let handle = OpenProcess(PROCESS_ALL_ACCESS, 0, self.inner.th32ProcessID);if !handle.is_null() {let class = GetPriorityClass(handle);CloseHandle(handle);priority_class = Some(class as i32);}}priority_class}
}
迭代器实现
迭代器中需要保存一些迭代遍历的状态,因此除了前面保存的快照句柄之外还要存储迭代的索引以及释放句柄的状态,迭代器是不可逆的。
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,}}
}
然后就是迭代器的具体实现
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 self.finished {return None;}self.index += 1;let mut entry: PROCESSENTRY32 = unsafe { std::mem::zeroed() };entry.dwSize = size_of::<PROCESSENTRY32>() as u32;// 读取快照中的第一个进程let res = unsafe {if self.index == 1 {Process32First(self.process_snapshot, &mut entry)} else {Process32Next(self.process_snapshot, &mut entry)}};if res == 0 {unsafe {CloseHandle(self.process_snapshot);}self.finished = true;return None;}Some(ProcessInformation::new(entry))}
}
上面的代码有几点需要说明一下:
- entry在初始化时需要先到一个全0值的内存空间,并不是分配为一个Rust引用空间,这里用 unsafe 方法
std::mem::zeroed()
- 在读取进程Entry之前需要先指定内存长度,这里用
size_of::<PROCESSENTRY32>()
来获取并赋值给entry.dwSize
- 遍历时第一个元素需要调用
Process32First
读取,后续的使用Process32Next
读取 - 遍历完时记得关闭快照剧本 使用
CloseHandle
接口
特殊情况处理:如果用户并没有迭代完,上面的代码实现可能会出现快照句柄未释放的情况,所以还需要给迭代器实现一个Drop特征,在释放迭代器时释放快照句柄
impl Drop for ProcessInformationIterator {fn drop(&mut self) {if self.finished {return;}// 释放快照句柄。unsafe {CloseHandle(self.process_snapshot);}self.finished = true;}
}
代码汇总
我在写的时候放在了自定义的utils::process::win
包下面,具体引用路径根据自己的情况调整
mod.rs
文件
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.rs
文件
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};/// [PROCESSENTRY32](https://learn.microsoft.com/zh-cn/windows/win32/api/tlhelp32/ns-tlhelp32-processentry32) 的Rust包装实现
pub struct ProcessInformation {inner: PROCESSENTRY32,
}impl ProcessInformation {pub(crate) fn new(entry: PROCESSENTRY32) -> ProcessInformation {ProcessInformation {inner: entry}}/// 获取进程IDpub fn get_pid(&self) -> u32 {self.inner.th32ProcessID as u32}/// 获取进程名pub fn get_name(&self) -> String {char_arr_to_string(&self.inner.szExeFile)}/// 获取父进程IDpub fn get_parent_pid(&self) -> u32 {self.inner.th32ParentProcessID as u32}/// 获取线程数量pub fn get_thread_count(&self) -> usize {self.inner.cntThreads as usize}/// 获取基础优先权值pub fn get_priority_base(&self) -> i64 {self.inner.pcPriClassBase as i64}/// 获取优先权类别,如果调用进程没有指定权限可能会获取失败,失败时返回 `None`pub fn get_priority_class(&self) -> Option<i32> {let mut priority_class = None;unsafe {let handle = OpenProcess(PROCESS_ALL_ACCESS, 0, self.inner.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) -> ProcessInformationIterator {ProcessInformationIterator {process_snapshot,index: 0,finished: false,}}
}impl Drop for ProcessInformationIterator {fn drop(&mut self) {if self.finished {return;}// 释放快照句柄。unsafe {CloseHandle(self.process_snapshot);}self.finished = true;}
}impl Iterator for ProcessInformationIterator {type Item = ProcessInformation;fn next(&mut self) -> Option<Self::Item> {if self.finished {return None;}self.index += 1;let mut entry: PROCESSENTRY32 = unsafe { std::mem::zeroed() };entry.dwSize = size_of::<PROCESSENTRY32>() as u32;// 读取快照中的第一个进程let res = unsafe {if self.index == 1 {Process32First(self.process_snapshot, &mut entry)} else {Process32Next(self.process_snapshot, &mut entry)}};if res == 0 {unsafe {CloseHandle(self.process_snapshot);}self.finished = true;return None;}Some(ProcessInformation::new(entry))}
}
测试
测试代码
#[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)}
}
结果
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 smss.exe 4 2 11 None
728 csrss.exe 712 11 13 None
824 wininit.exe 712 1 13 None
832 csrss.exe 816 15 13 None...12624 chrome.exe 12148 19 8 Some(32)
16352 chrome.exe 12148 18 4 Some(64)
14904 chrome.exe 12148 17 4 Some(64)
14672 wslinstaller.exe 892 2 8 None
11160 chrome.exe 12148 20 4 Some(64)
18048 chrome.exe 12148 19 4 Some(64)
5452 chrome.exe 12148 14 4 Some(64)
14468 svchost.exe 892 3 8 None
18060 chrome.exe 12148 14 4 Some(64)
17748 dllhost.exe 688 8 8 Some(32)
16084 vctip.exe 16648 27 8 Some(32)
9008 OpenConsole.exe 10644 6 8 Some(32)
15516 cargo.exe 10644 4 8 Some(32)
11312 cargo.exe 15516 4 8 Some(32)