Windows-rs and raw point to array of struct

I'm trying to get all installed printers on my Windows 10 machine using windows-rs crate and EnumPrintersW. So, MSDN says that in this function:

BOOL EnumPrinters(
  _In_  DWORD   Flags,
  _In_  LPTSTR  Name,
  _In_  DWORD   Level,
  _Out_ LPBYTE  pPrinterEnum,
  _In_  DWORD   cbBuf,
  _Out_ LPDWORD pcbNeeded,
  _Out_ LPDWORD pcReturned
);

or in rust crate:

pub unsafe fn EnumPrintersW<'a>(
    flags: u32, 
    name: impl IntoParam<'a, PWSTR>, 
    level: u32, 
    pprinterenum: *mut u8, 
    cbbuf: u32, 
    pcbneeded: *mut u32, 
    pcreturned: *mut u32
) -> BOOL

we have to put raw pointer onto array of struct's (in my case for Level 2) PRINTER_INFO_2W.

How could it be created? I had imported struct and trying like *Vec<PRINTER_INFO_2W> but no luck.
Thanks.

Stick the PRINTER_INFO_2W in a Vec, just as you have, but get the raw buffer using Vec::as_ptr(). Then you need to convert the pointer. One thing that you'll notice is that the "I'll just cast this pointer to whatever pointer I need" you'd do in C doesn't work -- you end up having to do it in steps (good thing!). This is from one of my projects:

CallbackParam: &mut ctx as *mut MDCContext as *mut c_void

unsafe :grimacing:

Also, only very tangentially related: Unicode in Microsoft Windows - Wikipedia

I was wondering why a bunch of Microsoft's Rust examples are using the A version of the API's, when their C/C++ typically try to use the "unicode" (W) versions. Seems like later Windows 10 versions support UTF8 in the A versions. I mention this only because if you're going out of your way to use the W version, I figure you might want to look into that.

3 Likes

Thanks for reply!
Still wondering wth is going - after bunch of tryings come code refined:

    unsafe {
        let mut buffer:Vec<PRINTER_INFO_2A> = Vec::new();
        let mut pcb_needed:u32  = 0;
        let mut pcreturned:u32 = 0;
        let level:u32 = 2;
        let name = None;
        let buffer_zero:u32 = 0;
        let flags:u32 = PRINTER_ENUM_LOCAL|PRINTER_ENUM_NETWORK;
        let c = EnumPrintersA(flags,name,level,buffer.as_mut_ptr() as *mut u8,buffer_zero, &mut pcb_needed,&mut pcreturned);
        println!("{} needed, {} returned",pcb_needed, pcreturned);
        let c = EnumPrintersA(flags,name,level,buffer.as_mut_ptr() as *mut u8,pcb_needed,  &mut pcb_needed,&mut pcreturned);
        println!("{} needed, {} returned",pcb_needed, pcreturned);
    }

Here, we made two calls of API so that first call returns buffer size needed for structures, but second call leads to:

129296 needed, 0 returned //output after first API call 
error: process didn't exit successfully: `target\debug\sample.exe` (exit code: 0xc0000005, STATUS_ACCESS_VIOLATION)
Segmentation fault

I had also trying to make a new buffer of size returned after first call - but none ideas how to cast that bytearray into array of PRINTER_INFO_2A.

Also - thanks for remembering about W and A functions - merely long years before with MASM32 experiments i had confirmed that W functions works with multilingual versions of Windows

Hmm... I haven't looked very close at EnumPrinters, but here's my guess at what's happening:

  1. The PRINTER_INFO_2 buffer contains a bunch of null-terminated strings.
  2. It's the responsibility of the application to allocate the buffer.

This is me just speculating, but these two facts point to that the reason you're getting such a large "needed" buffer is because you need to allocate enough for all the PRINTER_INFO_2 buffers and all the strings it will point to. EnumPrinters will store all the PRINTER_INFO_2 nodes in the beginning of the buffer, and stick all the strings at the end.

If this is the case, here's the issue: You need to make sure that buffer is large enough to hold all the PRINTER_INFO_2 nodes plus all the strings. You're getting a segfault because you're not allocating memory for the output buffer between the EnumPrinters calls.

Just to see if this is the case, you can try to pre-allocate space in buffer by using Vec::reserve(). Just allocate a sufficient amount of nodes to make sure that it'll fit, in your example, 129296 bytes of data. Conceptually:

let nodes_needed = pcb_needed / std::mem::size_of::<PRINTER_INFO_2>();
buffer.reserve(nodes_needed+1); // one extra for good luck (and also if the sizes don't align)

But note that this is only to solve the memory allocation issue, you still need to set the number of elements in the Vec to pcreturned somehow.

In my last reply I didn't bother checking how EnumPrinter works, and to be honest I just took a quick glance now, so I may be completely wrong.

Perhaps you should be using a Vec<u8> instead, and resize it as appropriate before the second call to EnumPrinters, and then use some unsafe pointer-trickery to point a PRINTER_INFO_2 pointer to the entries after the second call, and copy the data out to a Rust-native structure.

The only thing to make sure is that the internal Vec<u8> buffer is aligned properly for a PRINTER_INFO_2 pointer.

I have done very little ffi, so there are probably even better ways to do it.

Oh, and also: Keep an eye out for packing/padding bugs in windows-rs. I have encountered an issue where a struct member is at the incorrect offset in the windows crate (it stems from a bug in the win32metadata project). I think these things are very rare, but don't automatically assume that the structs are entirely bug-free in the windows crate.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.