Hi,I want to get dll or exe version on windows.
Using crate windows with feature "Win32_Storage_FileSystem",, write some following code.
But it not works.
use std::ffi::c_void;
use windows::{
core::PCSTR,
s,
Win32::Storage::FileSystem::{
GetFileVersionInfoA, GetFileVersionInfoSizeA, VerQueryValueA, VS_FIXEDFILEINFO,
},
};
fn get_version() -> std::result::Result<String, Box<dyn std::error::Error>> {
unsafe {
let dll_path = String::from("path to exe or dll");
let dll_path = PCSTR::from_raw(dll_path.as_ptr());
let dwlen = GetFileVersionInfoSizeA(dll_path, None);
// ??? here how to using dynamic array
let lpdata = [0u8; 2000];
let ok = GetFileVersionInfoA(dll_path, 0, dwlen, lpdata.as_ptr() as *mut c_void);
println!("ok: {:?} data: {:?}", ok, lpdata);
let info: VS_FIXEDFILEINFO = VS_FIXEDFILEINFO::default();
let ptr_info = &info as *const VS_FIXEDFILEINFO as *mut VS_FIXEDFILEINFO as *mut c_void;
let ptr_info = &ptr_info as *const *mut c_void as *mut *mut c_void;
// let pulen: u32 = std::mem::size_of_val(&info) as u32;
let pulen: u32 = 0;
println!("pulen = {}", pulen);
let ok = VerQueryValueA(
lpdata.as_ptr() as *const c_void,
s!(r"\"),
ptr_info,
pulen as *mut u32,
);
println!("ok: {:?} info: {:?}", ok, info);
}
Ok("ok".into())
}
man, those Hungarian naming is killing me. anyway, I digress.
the idiomatic way to receive untyped raw bytes from ffi is combinationn of Vec::with_capacity(), Vec::as_mut_ptr() and Vec::set_len(), the final step is unsafe, must be called after checking the ffi function is successful.
Edit: misread the microsoft doc. need another level of indirection, see comment below by @DanielKeep: the idiomatic way to provide an "out" pointer to an type (must be #[repl(C)]) is the combination of MaybeUninit::uninit(), MaybeUninit::as_mut_ptr() and MaybeUninit::assume(), again, the final step must be called after checking ffi function is successful.
unsafe {
let path = s!("C:/Windows/system32/cmd.exe");
let len_u32 = GetFileVersionInfoSizeA(path, None);
let len = len_u32 as usize;
let mut blob: Vec<u8> = Vec::with_capacity(len);
let ok = GetFileVersionInfoA(path, 0, len_u32, blob.as_mut_ptr().cast());
assert!(ok.as_bool());
blob.set_len(len);
let mut info_ptr = MaybeUninit::<*const VS_FIXEDFILEINFO>::uninit();
let mut len = 0;
let ok = VerQueryValueA(
blob.as_ptr().cast(),
s!(r"\"),
info_ptr.as_mut_ptr().cast(),
&mut len,
);
assert!(ok.as_bool());
assert!(std::mem::size_of::<VS_FIXEDFILEINFO>() == len as usize);
let info = info_ptr.assume_init().read_unaligned();
dbg!(info);
}
use std::ffi::c_void;
use windows::{
core::PCWSTR,
w,
Win32::Storage::FileSystem::{
// NOTE: I'm using the `*W` variants here. You should avoid `*A`
// variants where possible.
GetFileVersionInfoW, GetFileVersionInfoSizeW, VerQueryValueW,
VS_FIXEDFILEINFO,
},
};
fn get_version(dll_path: &str) -> Result<VS_FIXEDFILEINFO, Box<dyn std::error::Error>> {
unsafe {
// Encode `dll_path` as UTF-16, which is a subset of UCS2, which is what
// win32 wants.
let dll_path_wcs: Vec<_> = dll_path.encode_utf16().chain(std::iter::once(0)).collect();
let dll_path_pwcs = PCWSTR::from_raw(dll_path_wcs.as_ptr());
// Get file version info size.
let data_len = GetFileVersionInfoSizeW(dll_path_pwcs, None);
if data_len == 0 {
// Don't forget to check for errors!
return Err(windows::core::Error::from_win32())?;
}
// Win32 returns a `u32`, Rust wants a `usize`; do the conversion and
// make sure it's valid.
let data_len_usize: usize = data_len.try_into().unwrap();
/*
Allocate buffer.
NOTE THE USE OF `mut`! You must not ever, EVER mutate something you've
told the compiler won't be mutated.
Ever.
No exceptions.
*/
let mut data = vec![0u8; data_len_usize];
/*
Doing this makes it *slightly* harder to mess up: I can no longer
resize `data`, which helps prevent making pointers into it invalid.
*/
let data = &mut data[..];
// Get the info.
let ok = GetFileVersionInfoW(
dll_path_pwcs,
0,
data_len,
// NOTE: I used `as_mut_ptr` here because I want a mutable pointer.
data.as_mut_ptr() as *mut c_void,
);
ok.ok()?;
// Again, DO NOT EVER MAKE A MUTABLE POINTER TO NON-MUTABLE DATA!
let mut info_ptr: *mut VS_FIXEDFILEINFO = std::ptr::null_mut();
let mut info_len: u32 = 0;
let ok = VerQueryValueW(
data.as_ptr() as *const c_void,
w!(r"\"),
(&mut info_ptr) as *mut _ as *mut *mut c_void,
&mut info_len,
);
ok.ok()?;
// Read the info from the buffer.
assert!(!info_ptr.is_null());
assert_eq!(info_len as usize, std::mem::size_of::<VS_FIXEDFILEINFO>());
let info = info_ptr.read_unaligned();
Ok(info)
}
}
fn main() {
let ver_info = get_version("C:\\Windows\\system32\\cmd.exe").unwrap();
dbg!(ver_info);
}
Some notes:
You took mutable pointers to non mutable data in a few places. Don't. Ever.
You're using the *A versions of the calls. They work, but you need to be super careful that you never, ever give them anything other than basic ASCII text, or you'll also have to start accounting for codepages. Better to stick to the *W variants where possible.
Check conversions. Check for errors.
I think your understanding of the VerQueryValue* function wrong: you don't pass it a pointer to a pointer to where the result goes. You pass it a pointer to a pointer where the call will store a pointer to the result taken from within the buffer you give it.
Edit: I should have used the cast() method on the pointers rather than as. I wasn't aware that existed until I saw @nerditation's reply just now.
@nerditation To be honest, my brain slipped off the description of that parameter. It wasn't until I tested the code and noticed it didn't produce any results that I got suspicious.
Functions like this are exactly why I like strong type systems.