基于Rust的Windows平台进程遍历
字数 1128 2025-08-23 18:31:34

Rust在Windows平台实现进程遍历的教学文档

1. 简介

本文介绍如何使用Rust语言在Windows平台上实现进程遍历功能,重点讲解如何通过windowswindows-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 Process32FirstProcess32Next将进程名以ASCII编码写入PROCESSENTRY32.szExeFile数组,但存在以下问题:

  1. 函数不会清空数组,只是在新进程名后添加一个0作为终止符
  2. Rust的String::from_utf8会继续读取直到遇到连续的两个0

5.2 解决方案

在每次调用Process32Next之前手动清空szExeFile数组:

for item in pe32.szExeFile.iter_mut() {
    *item = 0;
}

6. 实现原理分析

6.1 Windows API行为

Process32FirstProcess32Next的实现逻辑类似于:

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 问题重现

  1. 第一个进程名16字节:写入szExeFile[0-15]
  2. 第二个进程名6字节:写入szExeFile[0-5],szExeFile[6]=0
  3. szExeFile[7-15]保留第一个进程名的数据
  4. Rust会将这些残留数据也作为字符串内容处理

7. 注意事项

  1. 所有Windows API调用都需要在unsafe块中进行
  2. 必须正确设置PROCESSENTRY32.dwSize字段
  3. 注意错误处理,特别是CreateToolhelp32Snapshot可能失败
  4. 此问题同样存在于其他类似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. 总结

通过本教程,我们学习了:

  1. 如何在Rust中使用windowswindows-sys crate调用Windows API
  2. 实现进程遍历的具体步骤
  3. 处理Windows API与Rust字符串编码的差异
  4. 解决Windows API实现细节带来的问题

虽然windowswindows-sys crate目前没有内置解决这个编码问题,但通过手动清空缓冲区可以确保正确获取进程名信息。

Rust在Windows平台实现进程遍历的教学文档 1. 简介 本文介绍如何使用Rust语言在Windows平台上实现进程遍历功能,重点讲解如何通过 windows 和 windows-sys 这两个crate调用Windows API来获取系统进程列表。 2. 准备工作 2.1 依赖配置 在 Cargo.toml 中添加以下依赖: 2.2 两个crate的区别 windows-sys : 直接调用原生Windows API,代码风格类似C/C++ windows : 对API进行了Rust风格的封装,返回 Result 类型 3. 使用windows-sys实现进程遍历 4. 使用windows实现进程遍历 5. 关键问题与解决方案 5.1 进程名编码问题 Windows API Process32First 和 Process32Next 将进程名以ASCII编码写入 PROCESSENTRY32.szExeFile 数组,但存在以下问题: 函数不会清空数组,只是在新进程名后添加一个0作为终止符 Rust的 String::from_utf8 会继续读取直到遇到连续的两个0 5.2 解决方案 在每次调用 Process32Next 之前手动清空 szExeFile 数组: 6. 实现原理分析 6.1 Windows API行为 Process32First 和 Process32Next 的实现逻辑类似于: 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. 完整代码示例 9. 总结 通过本教程,我们学习了: 如何在Rust中使用 windows 和 windows-sys crate调用Windows API 实现进程遍历的具体步骤 处理Windows API与Rust字符串编码的差异 解决Windows API实现细节带来的问题 虽然 windows 和 windows-sys crate目前没有内置解决这个编码问题,但通过手动清空缓冲区可以确保正确获取进程名信息。