windowsruststructbuffer

How do I allocate space to call GetInterfaceInfo using the windows crate?


I'm trying to fetch information regarding the network interfaces available on the system via GetInterfaceInfo using Microsoft's windows crate. This requires me to do some unsafe operations, and I get it to work for one interface, but not two:

#[cfg(test)]
mod tests {
    use super::*;
    use windows::{
        core::*, Data::Xml::Dom::*, Win32::Foundation::*, Win32::NetworkManagement::IpHelper::*,
        Win32::System::Threading::*, Win32::UI::WindowsAndMessaging::*,
    };

    #[test]
    fn main() {
        unsafe {
            let mut dw_out_buf_len: u32 = 0;

            let mut dw_ret_val =
                GetInterfaceInfo(std::ptr::null_mut(), &mut dw_out_buf_len as *mut u32);

            if dw_ret_val != ERROR_INSUFFICIENT_BUFFER.0 {
                panic!();
            }

            println!("Size: {}", dw_out_buf_len);
            // allocate that amount of memory, which will be used as a buffer
            let mut ip_interface_info = Vec::with_capacity(dw_out_buf_len as usize);
            let mut ptr = ip_interface_info.as_mut_ptr() as *mut IP_INTERFACE_INFO;

            dw_ret_val = GetInterfaceInfo(ptr, &mut dw_out_buf_len as *mut u32);

            println!("Num adapters: {}", (*ptr).NumAdapters);
            for i in 0..(*ptr).NumAdapters as usize {
                println!(
                    "\tAdapter index: {}\n\tAdapter name: {}",
                    (*ptr).Adapter[i].Index,
                    String::from_utf16(&(*ptr).Adapter[i].Name).unwrap()
                );
            }
        }
    }
}

It crashes when I'm trying to access the second entry (even though there should be two available):

panicked at 'index out of bounds: the len is 1 but the index is 1'

The struct IP_INTERFACE_INFO containing all data has a field called Adapter which seems to be limited to only be array size of 1. Am I reading this correctly? How is it then supposed to hold multiple adapters?

#[repr(C)]
#[doc = "*Required features: `\"Win32_NetworkManagement_IpHelper\"`*"]
pub struct IP_INTERFACE_INFO {
    pub NumAdapters: i32,
    pub Adapter: [IP_ADAPTER_INDEX_MAP; 1],
}

Solution

  • It appears that IP_INTERFACE_INFO uses a C flexible array member, which often uses the [1] syntax. The C++ example in Managing Interfaces Using GetInterfaceInfo corroborates this usage:

    for (i = 0; i < (unsigned int) pInterfaceInfo->NumAdapters; i++) {
        printf("  Adapter Index[%d]: %ld\n", i,
               pInterfaceInfo->Adapter[i].Index);
        printf("  Adapter Name[%d]:  %ws\n\n", i,
               pInterfaceInfo->Adapter[i].Name);
    }
    

    The equivalent in Rust would be to take the single-element array, get the raw pointer to it, then iterate over that. There are lots of details to be aware of, such as allocation alignment and pointer provenance. Here's an annotated example:

    use std::{
        alloc::{GlobalAlloc, Layout, System},
        mem,
        ptr::{self, addr_of},
        slice,
    };
    use windows::{
        Win32::Foundation::*,
        Win32::NetworkManagement::IpHelper::{
            GetInterfaceInfo, IP_ADAPTER_INDEX_MAP, IP_INTERFACE_INFO,
        },
    };
    
    fn main() {
        unsafe {
            // Perform the first call to know how many bytes to allocate
            let mut raw_buf_len = 0;
            let ret_val = GetInterfaceInfo(ptr::null_mut(), &mut raw_buf_len);
            assert_eq!(
                ret_val, ERROR_INSUFFICIENT_BUFFER.0,
                "Expected to get the required buffer size, was {ret_val:?}",
            );
    
            // Allocate an appropriately sized *and aligned* buffer to store the result
            let buf_len = raw_buf_len.try_into().expect("Invalid buffer length");
            let layout = Layout::from_size_align(buf_len, mem::align_of::<IP_INTERFACE_INFO>())
                .expect("Could not calculate the appropriate memory layout");
            let base_ptr = System.alloc(layout);
            let ip_interface_info = base_ptr.cast();
    
            // Perform the second call to get the data
            let ret_val = GetInterfaceInfo(ip_interface_info, &mut raw_buf_len);
            assert_eq!(
                ret_val, NO_ERROR.0,
                "Could not get the data on the second call: {ret_val:?}",
            );
    
            // Construct a pointer to the adapter array that preserves the provenance of the original pointer
            let adapter_ptr = addr_of!((*ip_interface_info).Adapter);
            let adapter_ptr = adapter_ptr.cast::<IP_ADAPTER_INDEX_MAP>();
    
            // Combine the pointer and length into a Rust slice
            let n_adapters = (*ip_interface_info).NumAdapters;
            let n_adapters = n_adapters.try_into().expect("Invalid adapter count");
            let adapters = slice::from_raw_parts(adapter_ptr, n_adapters);
    
            println!("Num adapters: {}", adapters.len());
            for adapter in adapters {
                let IP_ADAPTER_INDEX_MAP {
                    Index: index,
                    Name: name,
                } = adapter;
    
                // The fixed-size buffer contains data after the UTF-16 NUL character
                let name_end = name.iter().position(|&c| c == 0).unwrap_or(name.len());
                let name = String::from_utf16_lossy(&name[..name_end]);
    
                println!("Adapter index: {index}\nAdapter name: {name}",);
            }
    
            // Free the allocation. This should be wrapped in a type that
            // implements `Drop` so we don't leak memory when unwinding a panic.
            System.dealloc(base_ptr, layout);
        }
    }