I might as well give my own attempt at a solution. The example in GetAdaptersAddress()
uses HeapAlloc()
/HeapFree()
to allocate memory, so clearly the alignment of that API must be sufficient. If we look at the documentation for HeapAlloc()
, it says:
The alignment of memory returned by HeapAlloc is MEMORY_ALLOCATION_ALIGNMENT in WinNT.h:
#if defined(_WIN64) || defined(_M_ALPHA)
#define MEMORY_ALLOCATION_ALIGNMENT 16
#else
#define MEMORY_ALLOCATION_ALIGNMENT 8
#endif
And this value is exposed by windows-rs. So we can use it as our alignment when creating a Layout
for alloc::alloc()
, and otherwise follow the method of the example:
/*
[dependencies.windows]
version = "0.48.0"
features = [
"Win32_Foundation",
"Win32_NetworkManagement_IpHelper",
"Win32_NetworkManagement_Ndis",
"Win32_Networking_WinSock",
"Win32_System_SystemServices",
]
*/
use std::alloc::{self, Layout};
use windows::core::Result as WinResult;
use windows::Win32::{
Foundation::{ERROR_BUFFER_OVERFLOW, ERROR_NOT_ENOUGH_MEMORY, ERROR_SUCCESS, WIN32_ERROR},
NetworkManagement::IpHelper::{
GetAdaptersAddresses, GET_ADAPTERS_ADDRESSES_FLAGS, IP_ADAPTER_ADDRESSES_LH,
},
System::SystemServices::MEMORY_ALLOCATION_ALIGNMENT,
};
pub fn get_adapters_addresses(
family: u32,
flags: GET_ADAPTERS_ADDRESSES_FLAGS,
) -> WinResult<(*mut IP_ADAPTER_ADDRESSES_LH, usize)> {
unsafe {
let mut size_u32 = 15000;
let align: usize = MEMORY_ALLOCATION_ALIGNMENT.try_into().unwrap();
loop {
let size: usize = size_u32.try_into().unwrap();
let Ok(layout) = Layout::from_size_align(size, align) else {
return Err(ERROR_NOT_ENOUGH_MEMORY.into());
};
let ptr = alloc::alloc(layout);
if ptr.is_null() {
return Err(ERROR_NOT_ENOUGH_MEMORY.into());
}
let result = GetAdaptersAddresses(family, flags, None, Some(ptr.cast()), &mut size_u32);
match WIN32_ERROR(result) {
ERROR_SUCCESS => return Ok((ptr.cast(), size)),
ERROR_BUFFER_OVERFLOW => {
alloc::dealloc(ptr, layout);
continue;
}
error => {
alloc::dealloc(ptr, layout);
return Err(error.into());
}
}
}
}
}
pub unsafe fn free_adapters_addresses(ptr: *mut IP_ADAPTER_ADDRESSES_LH, size: usize) {
let align: usize = MEMORY_ALLOCATION_ALIGNMENT.try_into().unwrap();
let layout = Layout::from_size_align(size, align).unwrap();
alloc::dealloc(ptr.cast(), layout);
}
fn main() {
let (head, size) = get_adapters_addresses(0, Default::default()).unwrap();
unsafe {
let mut ptr = head;
while !ptr.is_null() {
println!("{}", (*ptr).FriendlyName.display());
ptr = (*ptr).Next;
}
free_adapters_addresses(head, size);
}
}
Alternatively, to avoid lugging around the size
, we can call HeapAlloc()
/HeapFree()
directly, as std
already does internally.
Alternative method
/*
[dependencies.windows]
version = "0.48.0"
features = [
"Win32_Foundation",
"Win32_NetworkManagement_IpHelper",
"Win32_NetworkManagement_Ndis",
"Win32_Networking_WinSock",
"Win32_System_Memory",
]
*/
use windows::core::Result as WinResult;
use windows::Win32::{
Foundation::{ERROR_BUFFER_OVERFLOW, ERROR_NOT_ENOUGH_MEMORY, ERROR_SUCCESS, WIN32_ERROR},
NetworkManagement::IpHelper::{
GetAdaptersAddresses, GET_ADAPTERS_ADDRESSES_FLAGS, IP_ADAPTER_ADDRESSES_LH,
},
System::Memory::{GetProcessHeap, HeapAlloc, HeapFree},
};
pub fn get_adapters_addresses(
family: u32,
flags: GET_ADAPTERS_ADDRESSES_FLAGS,
) -> WinResult<*mut IP_ADAPTER_ADDRESSES_LH> {
unsafe {
let heap = GetProcessHeap()?;
let mut size_u32 = 15000;
loop {
let size: usize = size_u32.try_into().unwrap();
let ptr = HeapAlloc(heap, Default::default(), size);
if ptr.is_null() {
return Err(ERROR_NOT_ENOUGH_MEMORY.into());
}
let result = GetAdaptersAddresses(family, flags, None, Some(ptr.cast()), &mut size_u32);
match WIN32_ERROR(result) {
ERROR_SUCCESS => return Ok(ptr.cast()),
ERROR_BUFFER_OVERFLOW => {
HeapFree(heap, Default::default(), Some(ptr)).ok()?;
continue;
}
error => {
// ignore secondary error
HeapFree(heap, Default::default(), Some(ptr));
return Err(error.into());
}
}
}
}
}
pub unsafe fn free_adapters_addresses(ptr: *mut IP_ADAPTER_ADDRESSES_LH) -> WinResult<()> {
let heap = GetProcessHeap()?;
HeapFree(heap, Default::default(), Some(ptr.cast())).ok()
}
fn main() {
let head = get_adapters_addresses(0, Default::default()).unwrap();
unsafe {
let mut ptr = head;
while !ptr.is_null() {
println!("{}", (*ptr).FriendlyName.display());
ptr = (*ptr).Next;
}
free_adapters_addresses(head).unwrap();
}
}
(Also, depending on the desired semantics, we could make get_adapters_address()
successfully return a null pointer on ERROR_NO_DATA
, to indicate an empty linked list. If free_adapters_address()
is implemented with alloc::dealloc()
, it would need to be modified to do nothing when it receives a null pointer.)