Windows crate: Best way to create buffers to pass to Windows API?

I am quite new to Rust, and as one of my first projects I am rewriting a Windows project I created in C in Rust. Since the project uses different parts of the Windows API extensively, I decided to use the windows crate from microsoft (GitHub - microsoft/windows-rs: Rust for Windows)

There are a lot of Windows API functions that require you to pass a pointer to a buffer and the size of the buffer as arguments to the function. The function then write data to the buffer. The data could be a C string, a struct or something else.

What is the best (both regarding safety and writing idiomatic rust) to do this? Below are my first naive attempts, I am sure it can be done in a better way.

First example, GetTokenInformation(): bindings::windows::win32::security::GetTokenInformation - Rust / GetTokenInformation function (securitybaseapi.h) - Win32 apps | Microsoft Docs

You usually call the function twice. The first time you call it with the buffer size set to 0, on which the function returns the needed buffer size. You then create the buffer with the correct size, and call the function again, with a pointer to your new buffer and the buffer size.

In Rust I have used vec! macro to create a Vec of u8 and use that as a buffer in these cases, but I have an inkling that there are better ways.

How I am doing it today:

    unsafe {
        // token is a HANDLE to a token gotten using OpenProcessToken() or OpenThreadToken()
        let mut token_privileges: *mut TOKEN_PRIVILEGES = std::ptr::null_mut();
        let mut token_privileges_length: u32 = 0;

        // first we call the function with a buffer length of 0 to get the needed buffer size
        // When length is 0, the buffer pointer can be NULL
        GetTokenInformation(
            token,
            TOKEN_INFORMATION_CLASS::TokenPrivileges,
            token_privileges as *mut std::ff::c_void,
            0,
            &mut token_privileges_length as *mut u32,
        );

        // The following two lines is what I am unsure about. It seems to work, but is it by luck?
        // Any better way to do it?
        let mut token_privileges_vec = vec![0u8; token_privileges_length as usize];
        token_privileges = token_privileges_vec.as_mut_ptr() as *mut TOKEN_PRIVILEGES;

        // The buffer has been created with the correct size, call GetTokenInformation() again
        if GetTokenInformation(
            token,
            TOKEN_INFORMATION_CLASS::TokenPrivileges,
            token_privileges as *mut std::ffi::c_void,
            token_privileges_length,
            &mut token_privileges_length as *mut u32,
        ) == true
        {
            // Do stuff with the token privilege information
        }
    }

Here the data is a C struct, but I also wonder how to best do the same thing when the data is a C style null terminated string that I need to use in my rust code.

When dealing with C style null terminated strings I have done the following:

            unsafe {
               // privilege_ptr points to the first element of 
               // a C array of LUID_AND_ATTRIBUTES structs that I am iterating over
               // PSTR is a struct from the windows crate representing a Windows API
               // PSTR. PSTR.0 is a *mut u8 pointing to the buffer containing the null
               // terminated string.

                let mut privilege_name_length: u32 = 0;
                let null_str = PSTR::default();
                // First, find the needed buffer size by passing 0 as length
                LookupPrivilegeNameA(
                    null_str,
                    &mut (*privilege_ptr.offset(i as isize)).luid as *mut LUID,
                    null_str,
                    &mut privilege_name_length,
                );
                // if privilege_name_length != 0, LookupPrivilegeNameA() succeeded
                if privilege_name_length != 0 {

                    // Again I am usin a Vec<u8> and .as_mut_ptr()
                    // Is there better ways to do this? (I suspect it is)
                    let mut privilege_name_vec = vec![0u8; privilege_name_length + 1 as usize];
                    let privilege_name_ptr = privilege_name_vec.as_mut_ptr();
                    LookupPrivilegeNameA(
                        null_str,
                        &mut (*privilege_ptr.offset(i as isize)).luid as *mut LUID,
                        PSTR {
                            0: privilege_name_ptr,
                        },
                        &mut privilege_name_length,
                    );
                    // make the string usable in rust
                    let privilege_name = CStr::from_ptr(privilege_name_ptr as *const i8)
                        .to_str()
                        .unwrap();
                    println!("Privilege name is: {}", privilege_name);
                }
            }

I usually prefer using Vec::with_capacity.

let mut token_privileges_vec = Vec::with_capacity(token_privileges_length as usize);
token_privileges = token_privileges_vec.as_mut_ptr() as *mut TOKEN_PRIVILEGES;

But what you are doing is fine.

1 Like