使用Windows crate调用GetInterfaceInfo时的多网卡内存分配问题
问题描述
使用Microsoft的windows crate调用GetInterfaceInfo获取系统网卡信息时,单网卡场景可正常运行,但多网卡时崩溃,报错:
panicked at 'index out of bounds: the len is 1 but the index is 1'
查看IP_INTERFACE_INFO结构体定义,其Adapter字段为长度1的数组:
#[repr(C)] pub struct IP_INTERFACE_INFO { pub NumAdapters: i32, pub Adapter: [IP_ADAPTER_INDEX_MAP; 1], }
疑惑该结构体如何存储多网卡数据,测试代码如下:
#[cfg(test)] mod tests { use super::*; use windows::{ core::*, Win32::Foundation::*, Win32::NetworkManagement::IpHelper::*, }; #[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!(); } 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() ); } } } }
请问该如何正确分配内存以支持多网卡信息获取?
解决方案
这个问题的核心是Windows API中可变长度结构体的特殊处理逻辑:IP_INTERFACE_INFO的Adapter字段定义为长度1的数组,是C语言里用来表示"紧跟在结构体后的可变数量元素"的写法——实际内存中,NumAdapters的值指定了结构体后紧跟的IP_ADAPTER_INDEX_MAP实例数量,并非真的只有1个元素。
你的代码崩溃是因为直接通过(*ptr).Adapter[i]访问时,Rust会严格遵循编译期确定的数组长度(仅1个元素)进行边界检查,多网卡场景下索引超出范围触发panic。正确的做法是通过指针偏移来访问后续的IP_ADAPTER_INDEX_MAP元素。
修改后的测试代码如下:
#[cfg(test)] mod tests { use super::*; use windows::{ core::*, Win32::Foundation::*, Win32::NetworkManagement::IpHelper::*, }; #[test] fn get_interface_info_test() { unsafe { let mut dw_out_buf_len: u32 = 0; // 第一次调用获取所需内存大小 let mut dw_ret_val = GetInterfaceInfo(std::ptr::null_mut(), &mut dw_out_buf_len); if dw_ret_val != ERROR_INSUFFICIENT_BUFFER { panic!("Failed to get required buffer size: {dw_ret_val}"); } // 分配并初始化足够的内存(WinAPI要求内存初始化为0,避免未定义行为) let mut buf = vec![0u8; dw_out_buf_len as usize]; let info_ptr = buf.as_mut_ptr() as *mut IP_INTERFACE_INFO; // 第二次调用获取实际网卡数据 dw_ret_val = GetInterfaceInfo(info_ptr, &mut dw_out_buf_len); if dw_ret_val != NO_ERROR { panic!("Failed to get interface info: {dw_ret_val}"); } let num_adapters = (*info_ptr).NumAdapters as usize; println!("Num adapters: {}", num_adapters); // 获取第一个Adapter的指针,通过指针偏移访问后续元素 let first_adapter = &(*info_ptr).Adapter[0] as *const IP_ADAPTER_INDEX_MAP; for i in 0..num_adapters { let adapter = *first_adapter.add(i); // 处理UTF-16名称,截断到第一个空字符避免多余空字节 let name = String::from_utf16(&adapter.Name) .unwrap_or_else(|_| String::from("Invalid UTF-16 name")) .trim_matches(char::from(0)) .to_string(); println!( "\tAdapter index: {}\n\tAdapter name: {}", adapter.Index, name ); } } } }
关键修改说明:
- 内存分配与初始化:用
vec![0u8; dw_out_buf_len as usize]替代Vec::with_capacity,确保内存被实际分配并初始化为0,符合WinAPI的内存要求 - 指针偏移访问:取第一个
IP_ADAPTER_INDEX_MAP的指针,用add(i)进行偏移,绕过Rust的数组边界检查(这是处理WinAPI可变长度结构体的标准unsafe操作) - 返回值检查:补充第二次调用
GetInterfaceInfo的返回值检查,确保数据获取成功 - 名称处理优化:对
Adapter.Name的UTF-16转字符串逻辑增加空字符截断,避免无效字符或多余空字节
内容的提问来源于stack exchange,提问作者Mah
相关产品推荐
相关产品推荐

