microsoft / windows-rs

Rust for Windows

Home Page:https://kennykerr.ca/rust-getting-started/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

windows::Win32::Graphics::Printing::EnumPrintersW returns `Err` on success

FlippingBinary opened this issue · comments

Summary

Hello, all!

I'm developing an app that prints directly to a specific printer on Windows and discovered through much trial and error that EnumPrintersW returns Err on success. The Windows App Development documentation indicates the return value should be non-zero on success and 0 on failure. It appears to be mapped from a BOOL to a Result in the windows crate, although I'm not entirely sure I'm reading it correctly - there's a space in front of BOOL in the return type of the function declaration inside the link macro for some reason. There are several other cases of that space existing in front of BOOL in the same file too, but I'm not sure why.

#[inline]
pub unsafe fn EnumPrintersW<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::PCWSTR>,
{
windows_targets::link!("winspool.drv" "system" fn EnumPrintersW(flags : u32, name : windows_core::PCWSTR, level : u32, pprinterenum : *mut u8, cbbuf : u32, pcbneeded : *mut u32, pcreturned : *mut u32) -> super::super::Foundation:: BOOL);
EnumPrintersW(flags, name.param().abi(), level, core::mem::transmute(pprinterenum.as_deref().map_or(core::ptr::null(), |slice| slice.as_ptr())), pprinterenum.as_deref().map_or(0, |slice| slice.len().try_into().unwrap()), pcbneeded, pcreturned).ok()
}

I've worked around the odd usage of Result, but I'm wondering if it is a bug or if my confusion is a result of not being used to working with Windows APIs, lol.

Crate manifest

Any valid `Cargo.toml` should do it as long as it includes the features. Here's my `windows` dependency definition:


windows = { version = "0.54.0", features = [
  "Win32_Graphics_Printing",
  "Win32_Graphics_Gdi",
  "Win32_Security",
] }

Crate code

fn print_list_of_printers() -> Result<(), Error> {
    let mut needed: u32 = 0;
    let mut returned: u32 = 0;

    let result = unsafe {
        EnumPrintersW(
            PRINTER_ENUM_LOCAL | PRINTER_ENUM_CONNECTIONS,
            PCWSTR::null(),
            4,
            None,
            &mut needed,
            &mut returned,
        )
    };

    if result.is_ok() || needed == 0 {
        return Err(Error::NoPrintersFound);
    }

    // The following code executes, which tells me that `result` is an Err.

    let mut buffer: Vec<u8> = vec![0; needed as usize];
    unsafe {
        EnumPrintersW(
            PRINTER_ENUM_LOCAL | PRINTER_ENUM_CONNECTIONS,
            PCWSTR::null(),
            4,
            Some(&mut buffer),
            &mut needed,
            &mut returned,
        )
    }?;

    let printers: &[PRINTER_INFO_4W] =
        unsafe { std::slice::from_raw_parts(buffer.as_ptr() as *const _, returned as usize) };
    for printer in printers {
        let discovered_printer_name = unsafe { printer.pPrinterName.to_string() }?;
        println!("Printer: {}", discovered_printer_name);
    }

    Ok(())
}
commented

Code works just fine as it is, so I doubt that it's a bug.

Output from my computer (Windows 10):

Printer: OneNote for Windows 10
Printer: Microsoft XPS Document Writer
Printer: Microsoft Print to PDF
Printer: Fax

Are you calling this from a dll? The docs mentions that it should not be called from DllMain.

The fact that it works as written in the example I provided is indication of the bug I'm describing. If EnumPrintersW returned Ok (the expected value when a function succeeds) instead of Err, the result.is_ok() condition would be true, which would trigger the return statement in the conditional directly above the comment I wrote in that example. Instead result.is_ok() is false because EnumPrintersW returned an Err on success. I wrote a comment in the example to point out that the code below the comment can only execute because EnumPrintersW returned Err.

I'm calling this from a Tauri desktop application, not a dll.

The first time you call EnumPrintersW it naturally fails since the buffer (or lack thereof) is too small to receive the requested data. This failure is expected and gives you enough information to call it a second time and succeed.

@kennykerr Ahh, of course that makes perfect sense! My lack of sleep might be starting to show.

I updated the code to check for the specific HRESULT code that indicates the buffer was too small, that way any other errors will still be properly handled by my calling function. I suppose they would be on the second invocation anyway, but I don't like ignoring errors.

 fn print_list_of_printers() -> Result<(), Error> {
     let mut needed: u32 = 0;
     let mut returned: u32 = 0;
 
-    let result = unsafe {
+    unsafe {
         EnumPrintersW(
             PRINTER_ENUM_LOCAL | PRINTER_ENUM_CONNECTIONS,
             PCWSTR::null(),
             4,
             None,
             &mut needed,
             &mut returned,
         )
-     };
+     }
+    .or_else(|e| {
+        let code = e.code();
+        if code == HRESULT(0x8007007A_u32 as i32) {
+            // This result code means the buffer was too small, which is expected behavior.
+            Ok(())
+        } else {
+            Err(e)
+        }
+    })?;
 
-    if result.is_ok() || needed == 0 {
+    if needed == 0 {
         return Err(Error::NoPrintersFound);
     }
-
-    // The following code executes, which tells me that `result` is an Err.
 
     let mut buffer: Vec<u8> = vec![0; needed as usize];
     unsafe {
         EnumPrintersW(
             PRINTER_ENUM_LOCAL | PRINTER_ENUM_CONNECTIONS,
             PCWSTR::null(),
             4,
             Some(&mut buffer),
             &mut needed,
             &mut returned,
         )
     }?;
 
     let printers: &[PRINTER_INFO_4W] =
         unsafe { std::slice::from_raw_parts(buffer.as_ptr() as *const _, returned as usize) };
     for printer in printers {
         let discovered_printer_name = unsafe { printer.pPrinterName.to_string() }?;
         println!("Printer: {}", discovered_printer_name);
     }
 
     Ok(())
 }

Thank you!

By the way, I would have used STRSAFE_E_INSUFFICIENT_BUFFER to match against instead of creating my own HRESULT, but it's behind the "Win32_UI_WindowsAndMessaging" feature flag, which I wouldn't otherwise need. That error code seems to be used throughout the Windows API, based on a search of the desktop documentation. How did it wind up being defined in the UI module?

Technically, the API returns ERROR_INSUFFICIENT_BUFFER which is then wrapped in an HRESULT.

Well, you gave me an excuse to do more digging into the source code of the windows crate, which has been fun.

For the sake of anyone else who follows along with this conversation in the future, it looks like the glue code calls .ok() on the BOOL value that the dll returns. BOOL.ok() calls Error::from_win32() while creating an Err when BOOL.as_bool() is false. And Error::from_win32() calls GetLastError() and passes its return value to HRESULT::from_win32(u32) to put in the Err, which is good to know. GetLastError() returns a WIN32_ERROR, but that version of the type is just an alias for a u32, which is different than the WIN32_ERROR that windows::Win32::Foundation::ERROR_INSUFFICIENT_BUFFER is made of, so the u32 value has to be pulled out of ERROR_INSUFFICIENT_BUFFER to make a HRESULT for the comparison.

So, I've replaced one line to match against the self-documenting value ERROR_INSUFFICIENT_BUFFER instead of a hard-coded hex value:

-        let code = e.code();
-        if code == HRESULT(0x8007007A_u32 as i32) {
+        if e.code() == HRESULT::from_win32(ERROR_INSUFFICIENT_BUFFER.0) {

Thanks again for all your help!

You can also use the From and Into traits...

use windows::{core::*, Win32::Foundation::*};

fn main() {
    let e = Error::from(ERROR_INSUFFICIENT_BUFFER);

    if e.code() == ERROR_INSUFFICIENT_BUFFER.into() {
        println!("snap");
    }
}