使用 windows api 获取 FileInfo

Get FileInfo with windows api

我对 Rust 有点陌生。所以我试图从 windows 板条箱中获取 FileDescription。我无法将 descriptionBuffer 从 VerQueryValueA 转换为 utf8 字符串。无法弄清楚我做错了什么。

fn get_proc_data(pid: u32) -> Option<String> {
    let mut path = None;
    unsafe {
        let h_snap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE | TH32CS_SNAPMODULE32, pid);

        if h_snap != INVALID_HANDLE_VALUE {
            let mut mod_entry: MODULEENTRY32 = MODULEENTRY32 {
                ..Default::default()
            };
            mod_entry.dwSize = size_of_val(&mod_entry) as u32;
            if Module32First(h_snap, &mut mod_entry).as_bool() {
                let char_vec = mod_entry.szExePath.iter().map(|f| f.0).collect::<Vec<u8>>();

                path = match from_utf8(&char_vec) {
                    Ok(s) => Some(String::from(s.to_string().trim_end_matches(char::from(0)))),
                    Err(_) => None,
                };
            }
        }
        CloseHandle(h_snap);

        if path.is_some() {
            let mut infoBuffer: [u8; 2048] = [0; 2048];
            let pat = path.as_ref().unwrap();
            let lpvoid: *mut c_void = infoBuffer.as_mut_ptr() as *mut c_void;
            let c_str = CString::new(pat.as_str()).unwrap();
            let pstr = PSTR(c_str.as_ptr() as *const u8);
            let verInfoLen = GetFileVersionInfoSizeA(pstr, &mut 0);
            let ok = GetFileVersionInfoA(pstr, 0, verInfoLen, lpvoid);

            let mut descriptionBuffer: [u8; 256] = [0; 256];
            let descriptionPtr: *mut *mut c_void =
                descriptionBuffer.as_mut_ptr() as *mut *mut c_void;
            let mut descriptionLen = 0;
            if ok.as_bool()
                && VerQueryValueA(
                    lpvoid,
                    "\StringFileInfo\040904E4\FileDescription",
                    descriptionPtr,
                    &mut descriptionLen,
                )
                .as_bool()
            {
                info!("{:?}", descriptionBuffer);

                let res = from_utf8_lossy(&descriptionBuffer);
                info!("{:?} {:?}", path, res);
            }
        }
    }
    return path;
}

示例输出

info!("{:?}", descriptionBuffer);

信息 - [180, 162, 146, 165, 206, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

info!("{:?} {:?}", path, res);

INFO - Some("C:\Users\acoop\AppData\Local\Amazon Music\Amazon Music.exe") "������\u{0}\u{0}\u{0} \u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0} \u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0} \u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0} \u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0} \u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0} \u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0} \u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0} \u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0} \u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0} \u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0} \u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0} \u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0} \u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0} \u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0} \u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0} \u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0} \u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0} \u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0} \u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0} \u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0} \u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0} \u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0} \u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0} \u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0} \u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0} \u{0}"

查询文件信息属性是一个multi-step过程:

  1. 通过调用GetFileVersionInfoSizeW
  2. 确定版本信息大小
  3. 分配足够大小的缓冲区; a Vec 提供所需的一切(连续内存、运行时动态大小、低堆栈内存开销)
  4. 通过调用 GetFileVersionInfoW
  5. 将整个版本信息读入分配的缓冲区
  6. 使用VerQueryValueW查询感兴趣的信息;成功时函数 returns 一个 pointer/size 对进入上面分配的缓冲区

提供的代码最终出错的是最后一步。 API 需要存储结果的指针变量的地址。然而,代码传递了 descriptionBuffer 的第一个元素的地址,这就是 API 写入的位置(大概,代码是为 64 位目标编译的,这意味着前 8 个字节是指针值)。

本质上,代码确实成功地生成了 pointer/size 对,但未能根据 API 合同解释它们。查询二进制文件描述的改进版本可能如下所示:

fn get_file_description(path: impl AsRef<Path>) -> Result<String, Box<dyn Error>> {
    // Determine version info size
    let size = unsafe { GetFileVersionInfoSizeW(path.as_ref().as_os_str(), null_mut()) };
    if size == 0 {
        return Err(core::Error::from_win32().into());
    }

    // Allocate buffer
    let mut buffer = vec![0u8; size as usize];
    // Read version info
    unsafe {
        GetFileVersionInfoW(
            path.as_ref().as_os_str(),
            0,
            size,
            buffer.as_mut_ptr() as *mut std::ffi::c_void,
        )
    }
    .ok()?;

    // Declare pointer/size pair for output
    let mut ptr = null_mut();
    let mut len = 0;
    // Query for file description
    let success = unsafe {
        VerQueryValueW(
            buffer.as_ptr() as *const std::ffi::c_void,
            "\StringFileInfo\040904B0\FileDescription",
            &mut ptr,
            &mut len,
        )
    }
    // The API call doesn't set the last error code so we cannot use `.ok()?` here
    .as_bool();
    if !success {
        return Err("Failed to query file description".into());
    }

    // `len` here is in elements (as opposed to bytes)
    let descr = unsafe { slice::from_raw_parts(ptr as *const u16, len as usize) };
    // Optionally use `from_utf16_lossy` if you don't need to handle invalid UTF-16
    let descr = String::from_utf16(descr)?;

    Ok(descr)
}

这更好,但肯定不是完美的。大多数改进都围绕着字符编码的细微之处,这是 Windows 上 Rust 最棘手的痛点。 Rust 中没有专用的字符串类型可以以 Windows 的本机字符编码 UTF-16.

存储字符串

值得注意的变化:

  • 该函数接受可转换为 Path reference. The underlying storage is of type OsStr 的参数,支持 UTF-8 的宽松版本,能够表示任何 UTF-16 代码单元序列,well-formed 或其他。这是至关重要的,因为 Windows 对文件系统对象没有字符编码保证。除了少数保留值外,几乎任何 16 位值序列都是允许的。您的程序需要为此做好准备。
  • API 调用的所有 narrow-character 集版本已替换为 wide-character 版本(有关背景信息,请参阅 Conventions for Function Prototypes)。在处理您无法控制的数据(例如任意二进制文件的版本信息资源)时,这是唯一安全的选择。这里需要特别注意的是 windows crate 提供了从 &OsStrPCWSTR 的隐式转换,因此将 Path 传递到 wide-character APIs并非完全不方便(但它们确实会导致转换和分配)。
  • 同样,windows crate 提供从 &strPCWSTR 的转换,受 "alloc" 功能的限制。这允许在对 VerQueryValueW 的调用中使用字符串文字,并根据需要转换所有内容(同样,具有转换和分配成本)。

要将其变成一个完整的示例,生成一个接受二进制图像的路径名作为其第一个参数的命令行应用程序,只需添加以下内容 Cargo.toml

[package]
name = "fileinfo"
version = "0.0.0"
edition = "2021"

[dependencies.windows]
version = "0.33.0"
features = [
    "alloc",
    "Win32_Foundation",
    "Win32_Storage_FileSystem",
]

并将以下内容添加到 src/main.rs 文件中:

use std::{env, error::Error, path::Path, ptr::null_mut, slice};
use windows::{
    core,
    Win32::Storage::FileSystem::{GetFileVersionInfoSizeW, GetFileVersionInfoW, VerQueryValueW},
};

fn main() -> Result<(), Box<dyn Error>> {
    let input = env::args_os()
        .nth(1)
        .ok_or("Expected 1 command line argument")?;
    let path = Path::new(&input);
    let descr = get_file_description(&path)?;

    println!("File description: \"{}\"", &descr);

    Ok(())
}