I am trying to call the EnumPrinters function from Rust using the windows crate.
Function signature:
windows::Win32::Graphics::Printing
pub unsafe fn EnumPrintersA<P0>(flags: u32, name: P0, level: u32, pprinterenum: Option<&mut [u8]>, pcbneeded: *mut u32, pcreturned: *mut u32) -> windows_core::Result<()>
where
P0: windows_core::Param<windows_core::PCSTR>,
Call:
let flags: u32 = 2; // PRINTER_ENUM_LOCAL
let name = // ?
let level: u32 = 4; // PRINTER_INFO_4;
let pprinterenum = // ?
let mut pcbneeded = // ?
let mut pcreturned = // ?
let result = unsafe { EnumPrintersA(
flags,
name,
level,
pprinterenum,
pcb_needed,
pcreturned,
) };
I have the problem that I do not know
how the variables have to be declared
how the variables have to be passed (for both function calls, especially if null or an empty memory has to be passed)
The function must be called twice with the following parameters:
To determine the size of the required buffer: EnumPrintersA(2, null, 4, null, &pcbneeded, null)
To get the actual data in pprinterenum: EnumPrintersA(2, null, 4, &pprinterenum, &pcbneeded, &pcbreturned)
Of course, a memory the size of pcbneeded must also be allocated for pprinterenum. What is the best way to do this?
Finally, I would like to have a Vec<PRINTER_INFO_4A> that contains the data.
This function is fairly annoying to use, mostly due to the polymorphic data being returned. But here's how to go about it with some tips:
use windows::Win32::Foundation::ERROR_INSUFFICIENT_BUFFER;
use windows::Win32::Graphics::Printing::{EnumPrintersA, PRINTER_ENUM_LOCAL, PRINTER_INFO_4A};
let mut needed = 0;
let mut returned = 0;
let result = unsafe {
EnumPrintersA(
PRINTER_ENUM_LOCAL,
None,
4,
None,
&mut needed,
&mut returned,
)
};
match result {
Ok(_) => {}
Err(err) if err.code() == ERROR_INSUFFICIENT_BUFFER.into() => {} // "data area too small" is expected
Err(err) => {
// TODO: return error
}
}
For the first call, all we're interested in is the needed
number. When passing simple out-params like this, it is usually enough to declare it with a default value and passing it as &mut _
. The compiler will coerce the mutable reference to a pointer as well as infer the correct type for needed
. If you were to declare it with the wrong type, it would error somewhere due to mismatched types. However, it is clear from the EnumPrintersA
signature that it is a u32
.
The only tricky one here is the "name" parameter since the function wants a generic Param<PCSTR>
type. Fortunately None
is sufficient if we want to pass NULL for that.
The call will return a "too small" error which we anticipate since we provided an empty buffer.
const INFO_SIZE: usize = std::mem::size_of::<PRINTER_INFO_4A>();
let mut printers = Vec::<PRINTER_INFO_4A>::with_capacity((needed as usize) / INFO_SIZE);
unsafe {
std::ptr::write_bytes(printers.as_mut_ptr(), 0, printers.capacity());
}
let result = unsafe {
EnumPrintersA(
PRINTER_ENUM_LOCAL,
None,
4,
Some(std::slice::from_raw_parts_mut(
printers.as_mut_ptr().cast(),
needed as usize,
)),
&mut needed,
&mut returned,
)
};
result.unwrap(); // TODO: handle `result`
assert!(returned as usize <= printers.capacity());
unsafe { printers.set_len(returned as usize) };
It is a bit annoying because the needed
expresses the number of bytes that are to be written, but we ultimately want a Vec<PRINTER_INFO_4A>
so it is best to declare it as that upfront with enough capacity by dividing by the ultimate struct size. The std::ptr::write_bytes
call to initialize the Vec
's allocation is a formality but technically required since we pass it later as a &mut [u8]
.
Essentially we pass the memory that we've set aside in the Vec
as the buffer to be written to (using std::slice::from_raw_parts_mut
to actually pass it). If all goes well (remember to check the result
) then we can simply tell the Vec
that the data exists via .set_len
and you can use printers
as you like.