Help with GetModuleFileNameExA lpbasename parameter

Hi,

I struggle with the GetModuleFileNameExA() lpbasename parameter.

It expects a &mut [u8] where it will store a string. I tried some different things like filename.as_bytes_mut() or filename.as_mut_ptr(), but none of them worked. I could need some help.

use itertools::Itertools;
use sysinfo::System;
use windows::Win32::Foundation::HMODULE;
use windows::Win32::System::ProcessStatus::GetModuleFileNameExA;
use windows::Win32::System::Threading::{OpenProcess, PROCESS_QUERY_INFORMATION, PROCESS_VM_READ};

fn main() {
    let process_name = "noita.exe";
    let system = System::new();
    let noita_pid = match system.processes_by_exact_name(process_name).at_most_one() {
        Ok(Some(process)) => process.pid().as_u32(),
        Ok(None) => {
            println!("No process '{}' was found. 🙁", process_name);
            std::process::exit(1);
        }
        Err(_) => {
            println!("More than one process '{}' was found. đŸ˜ĩ‍đŸ’Ģ", process_name);
            std::process::exit(1);
        }
    };

    let process_handle = unsafe {
        OpenProcess(
            PROCESS_QUERY_INFORMATION | PROCESS_VM_READ,
            false,
            noita_pid,
        )
        .unwrap()
    };

    let hmod = HMODULE(0x400000);
    let mut filename = String::new();
    let result = unsafe { GetModuleFileNameExA(process_handle, hmod, filename.as_bytes_mut()) };
    println!("Result: {}, Value: {:?}", result, filename);
    if result == 0 {
        use windows::Win32::Foundation::GetLastError;
        let error = unsafe { GetLastError() };
        println!("{:?}", error);
    }
}

First of all, I would recommend using GetModuleFileNameExW instead. It returns a string encoded in UTF-16, while GetModuleFileNameExA appears to return a string encoded in ANSI, which is code for "whatever string encoding the system happens to be using at the time." While it usually is defined as Windows-1252 on US/Western European systems, you probably don't want to deal with the ambiguity. So just use the W function.

You do need to make filename an actual array of u16. Something like [0_u16; 255] will probably be enough for your purposes. You can then pass that array to GetModuleFileNameExW, and if you then want to turn it into a String, you can use String::from_utf16_lossy.
Here's an example:

let mut filename_buf = [0_u16; 255];
let filename_len = unsafe { GetModuleFileNameExW(process_handle, hmod, &mut filename) };
if filename_len == 0 {
  // Error handling goes here
}
let filename = String::from_utf16_lossy(&filename_buf[..filename_len as usize]);
1 Like

To use as_bytes_mut() you will need to use unsafe {}. Here is an example.

    let mut s = String::from("some text");
    let t = unsafe { s.as_bytes_mut() };

    fn test(_x: &mut [u8]) {
        return;
    }
    test(t);

Don't do that in general unless you also make sure that the contents are valid UTF8 before leaving unsafe.

And also don't do that in the context of translating Windows paths to UTF8, even with checking. It will fail unnecessarily for some paths and create garbage results for other paths, due to interpreting whatever encoding is used as UTF8 when it is not.[1]

Use the wide encoding instead, like @ravenclaw900 said.


  1. Unless you know or query the encoding and do an actual transliteration of the encoding, but there's no reason to go to such lengths. ↩ī¸Ž

1 Like

A version that handles long paths, the error, and returns a PathBuf...

use windows::Win32::Foundation::HMODULE;
use windows::Win32::System::ProcessStatus::GetModuleFileNameExW;
use windows::Win32::System::Threading::GetCurrentProcess;

use grob::{winapi_path_buf, RvIsSize};

fn main() -> Result<(), Box<dyn std::error::Error>> {

    let path = winapi_path_buf(|argument| {
        RvIsSize::new(unsafe { GetModuleFileNameExW(
            GetCurrentProcess(), HMODULE(0), argument.as_mut_slice()) })
    })?;

    println!("GetModuleFileNameExW returned {}", path.display());
    Ok(())
}

When using the windows crate, it helps to read the actual Windows API docs:

There's lots of helpful information, but even just seeing the actual signature helps make it a bit clearer what's happening:

DWORD GetModuleFileNameExW(
  [in]           HANDLE  hProcess,
  [in, optional] HMODULE hModule,
  [out]          LPWSTR  lpFilename,
  [in]           DWORD   nSize
);

The LPWSTR type is "Long Pointer to Wide (null terminated) STRing", eg *mut u16. ("Long" is an artifact of 16 bit Windows, if you're curious, and the const version adds a C, eg LPCWSTR)

The windows crate likes to bundle up buffer pointer, length parameter parameters into slices (depending on if the metadata correctly annotated them), so here the lpFilename parameter is also including the nSize for the size you're providing for windows to write into.

Unfortunately, it doesn't look like there's a way to get the actual full path length, unlike most Windows APIs (the general pattern is you call it with either a null or initial guess sized buffer, and it returns the actual size, which you can then allocate if it didn't fit and call it again to get the whole thing)