基于Rust的Windows平台进程遍历
字数 1128 2025-08-23 18:31:34
Rust在Windows平台实现进程遍历的教学文档
1. 简介
本文介绍如何使用Rust语言在Windows平台上实现进程遍历功能,重点讲解如何通过windows和windows-sys这两个crate调用Windows API来获取系统进程列表。
2. 准备工作
2.1 依赖配置
在Cargo.toml中添加以下依赖:
[dependencies.windows]
version = "*"
features = [
"Win32_Foundation",
"Win32_System_Diagnostics_ToolHelp",
]
[dependencies.windows-sys]
version = "*"
features = [
"Win32_Foundation",
"Win32_System_Diagnostics_ToolHelp",
]
2.2 两个crate的区别
windows-sys: 直接调用原生Windows API,代码风格类似C/C++windows: 对API进行了Rust风格的封装,返回Result类型
3. 使用windows-sys实现进程遍历
fn test_in_windows_sys() {
use windows_sys::Win32::Foundation::{INVALID_HANDLE_VALUE, GetLastError};
use windows_sys::Win32::System::Diagnostics::ToolHelp::{
CreateToolhelp32Snapshot, Process32First, Process32Next, PROCESSENTRY32, TH32CS_SNAPPROCESS
};
unsafe {
// 获取进程快照
let handle = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if handle == INVALID_HANDLE_VALUE {
println!("CreateToolhelp32Snapshot Error: {}", GetLastError());
process::exit(1);
}
let mut pe32: PROCESSENTRY32 = zeroed();
pe32.dwSize = size_of_val(&pe32) as u32;
let mut b_ret = Process32First(handle, &mut pe32) != 0;
while b_ret {
// 将进程名转换为UTF-8
let name = String::from_utf8(pe32.szExeFile[..].to_vec()).unwrap_or_else(|e| {
println!("String::from_utf8 Error: {}", e);
process::exit(1);
});
println!("PID={},name={}", pe32.th32ProcessID, name);
b_ret = Process32Next(handle, &mut pe32) != 0;
}
}
}
4. 使用windows实现进程遍历
fn test_in_windows() {
use windows::Win32::System::Diagnostics::ToolHelp::{
CreateToolhelp32Snapshot, Process32First, Process32Next, PROCESSENTRY32, TH32CS_SNAPPROCESS
};
unsafe {
let handle = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0).unwrap_or_else(|e| {
println!("CreateToolhelp32Snapshot Error: {}", e);
process::exit(1);
});
let mut pe32: PROCESSENTRY32 = PROCESSENTRY32::default();
pe32.dwSize = size_of_val(&pe32) as u32;
Process32First(handle, &mut pe32).unwrap_or_else(|e| {
println!("Process32First Error: {}", e);
process::exit(1);
});
loop {
// 将进程名转换为UTF-8
let sz_exe_file = pe32.szExeFile.map(|x| x as u8).to_vec();
let name = String::from_utf8(sz_exe_file).unwrap_or_else(|e| {
println!("String::from_utf8 Error: {}", e);
process::exit(1);
});
println!("PID={},name={}", pe32.th32ProcessID, name);
if let Err(_) = Process32Next(handle, &mut pe32) {
break;
}
// 清空szExeFile数组
for item in pe32.szExeFile.iter_mut() {
*item = 0;
}
}
}
}
5. 关键问题与解决方案
5.1 进程名编码问题
Windows API Process32First和Process32Next将进程名以ASCII编码写入PROCESSENTRY32.szExeFile数组,但存在以下问题:
- 函数不会清空数组,只是在新进程名后添加一个0作为终止符
- Rust的
String::from_utf8会继续读取直到遇到连续的两个0
5.2 解决方案
在每次调用Process32Next之前手动清空szExeFile数组:
for item in pe32.szExeFile.iter_mut() {
*item = 0;
}
6. 实现原理分析
6.1 Windows API行为
Process32First和Process32Next的实现逻辑类似于:
void write_process_name(char szExeFile[], char newProName[]) {
DWORD dwProNameLen = strlen(newProName);
for(DWORD i = 0; i < dwProNameLen; i++) {
szExeFile[i] = newProName[i];
}
szExeFile[dwProNameLen] = 0;
}
6.2 问题重现
- 第一个进程名16字节:写入szExeFile[0-15]
- 第二个进程名6字节:写入szExeFile[0-5],szExeFile[6]=0
- szExeFile[7-15]保留第一个进程名的数据
- Rust会将这些残留数据也作为字符串内容处理
7. 注意事项
- 所有Windows API调用都需要在
unsafe块中进行 - 必须正确设置
PROCESSENTRY32.dwSize字段 - 注意错误处理,特别是
CreateToolhelp32Snapshot可能失败 - 此问题同样存在于其他类似API如
Module32First/Module32Next
8. 完整代码示例
use std::mem::{size_of_val, zeroed};
use std::process;
fn main() {
println!("Using windows-sys crate:");
test_in_windows_sys();
println!("\nUsing windows crate:");
test_in_windows();
}
// windows-sys实现
fn test_in_windows_sys() {
// ... 同上 ...
}
// windows实现
fn test_in_windows() {
// ... 同上 ...
}
9. 总结
通过本教程,我们学习了:
- 如何在Rust中使用
windows和windows-syscrate调用Windows API - 实现进程遍历的具体步骤
- 处理Windows API与Rust字符串编码的差异
- 解决Windows API实现细节带来的问题
虽然windows和windows-sys crate目前没有内置解决这个编码问题,但通过手动清空缓冲区可以确保正确获取进程名信息。