[Win32] Usage of Winspool's FindFirstPrinterChangeNotification and FindNextPrinterChangeNotification with options

31 views
Skip to first unread message

Ian O'Neill

unread,
Jan 6, 2021, 7:08:45 AM1/6/21
to Java Native Access

Hi,

I'm trying to use Winspool.FindFirstPrinterChangeNotification() and Winspool.FindNextPrinterChangeNotification(), and am attempting to pass in the options and retrieve data back from the calls.

I've created classes similar to the ones defined in a previous conversation in this group - https://groups.google.com/g/jna-users/c/V8gF2SaksG0 as below:

public class Winspool2 {

  @Structure.FieldOrder({ "Version", "Flags", "Count", "pTypes" })
  public static class PRINTER_NOTIFY_OPTIONS extends Structure {

    public static class ByReference extends PRINTER_NOTIFY_OPTIONS implements Structure.ByReference {
    }

    public int Version;

    public int Flags;

    public int Count;

    public PRINTER_NOTIFY_OPTIONS_TYPE.ByReference pTypes;

  }

  @Structure.FieldOrder({ "Type", "Reserved0", "Reserved1", "Reserved2", "Count", "pFields" })
  public static class PRINTER_NOTIFY_OPTIONS_TYPE extends Structure {

    public static class ByReference extends PRINTER_NOTIFY_OPTIONS_TYPE implements Structure.ByReference {
    }

    public short Type;

    public short Reserved0;

    public int Reserved1;

    public int Reserved2;

    public int Count;

    public Pointer pFields;

  }

  @Structure.FieldOrder({ "Version", "Flags", "Count", "aData" })
  public static class PRINTER_NOTIFY_INFO extends Structure {

    public static class ByReference extends PRINTER_NOTIFY_INFO implements Structure.ByReference {
    }

    public static class ByValue extends PRINTER_NOTIFY_INFO implements Structure.ByValue {
    }

    public int Version;

    public int Flags;

    public int Count;

    public PRINTER_NOTIFY_INFO_DATA[] aData = new PRINTER_NOTIFY_INFO_DATA[1];

  }

  @Structure.FieldOrder({ "cbBuf", "pBuf" })
  public static class Data_struct extends Structure {

    public static class ByReference extends Data_struct implements Structure.ByReference {
    }

    public static class ByValue extends Data_struct implements Structure.ByValue {
    }

    public int cbBuf;

    public WinDef.PVOID pBuf;

  }

  public static class NotifyData_union extends Union {

    public static class ByReference extends NotifyData_union implements Structure.ByReference {
    }

    public static class ByValue extends NotifyData_union implements Structure.ByValue {
    }

    public int[] adwData = new int[2];

    public Data_struct Data;

  }

  @Structure.FieldOrder({ "Type", "Field", "Reserved", "Id", "NotifyData" })
  public static class PRINTER_NOTIFY_INFO_DATA extends Structure {

    public static final short JOB_NOTIFY_FIELD_PRINTER_NAME = 0x00;

    public static final short JOB_NOTIFY_FIELD_STATUS = 0x0A;

    public static final short JOB_NOTIFY_FIELD_DOCUMENT = 0x0D;

    public static class ByReference extends PRINTER_NOTIFY_INFO_DATA implements Structure.ByReference {
    }

    public static class ByValue extends PRINTER_NOTIFY_INFO_DATA implements Structure.ByValue {
    }

    public short Type;

    public short Field;

    public int Reserved;

    public int Id;

    public NotifyData_union NotifyData;

    @Override
    public void read() {
      super.read();

      if (Type == JOB_NOTIFY_FIELD_STATUS) {
        NotifyData.setType(int[].class);
      } else {
        NotifyData.setType(Data_struct.class);
      }
      NotifyData.read();
    }
  }

}


My application code looks like this:

public class JnaTest {

  private static final int PRINTER_NOTIFY_OPTIONS_REFRESH = 0x01;

  private static final int JOB_NOTIFY_TYPE = 0x01;

  private static final int TWO_DIMENSIONAL_PRINTERS = 0;

  private static final int NUMBER_OF_BYTES_IN_WORD = 2;

  public static void main(String[] args) throws Exception {
    WinNT.HANDLEByReference printServerHandle = new WinNT.HANDLEByReference();
    boolean success = Winspool.INSTANCE.OpenPrinter(null, printServerHandle, null); // Get job info for all printers
    if (!success) {
      int errorCode = Kernel32.INSTANCE.GetLastError();
      throw new RuntimeException("Failed to access the print server - " + errorCode);
    }

    try {
      Winspool2.PRINTER_NOTIFY_OPTIONS options = new Winspool2.PRINTER_NOTIFY_OPTIONS();
      options.Version = 2;
      options.Flags = PRINTER_NOTIFY_OPTIONS_REFRESH;
      options.Count = 1;
      Winspool2.PRINTER_NOTIFY_OPTIONS_TYPE.ByReference optionsType = new Winspool2.PRINTER_NOTIFY_OPTIONS_TYPE.ByReference();
      optionsType.Type = JOB_NOTIFY_TYPE;
      optionsType.Count = 3;
      optionsType.pFields = new Memory(3 * NUMBER_OF_BYTES_IN_WORD);
      optionsType.pFields.write(0,
                                new short[] { Winspool2.PRINTER_NOTIFY_INFO_DATA.JOB_NOTIFY_FIELD_PRINTER_NAME,
                                              Winspool2.PRINTER_NOTIFY_INFO_DATA.JOB_NOTIFY_FIELD_STATUS,
                                              Winspool2.PRINTER_NOTIFY_INFO_DATA.JOB_NOTIFY_FIELD_DOCUMENT
                                },
                                0,
                                3);
      optionsType.toArray(1);
      options.pTypes = optionsType;
      options.write();
      WinDef.LPVOID optionsPointer = new WinDef.LPVOID(options.getPointer());
      WinNT.HANDLE changeNotificationsHandle = Winspool.INSTANCE.FindFirstPrinterChangeNotification(printServerHandle.getValue(),
                                                                                                    Winspool.PRINTER_CHANGE_ADD_JOB
                                                                                                    | Winspool.PRINTER_CHANGE_SET_JOB
                                                                                                    | Winspool.PRINTER_CHANGE_DELETE_JOB,
                                                                                                    TWO_DIMENSIONAL_PRINTERS,
                                                                                                    optionsPointer);
      if (!isValidHandle(changeNotificationsHandle)) {
        int errorCode = Kernel32.INSTANCE.GetLastError();
        throw new RuntimeException("Failed to get a change handle - " + errorCode);
      }

      try {
        while (true) {
          Kernel32.INSTANCE.WaitForSingleObject(changeNotificationsHandle, WinBase.INFINITE);

          WinDef.DWORDByReference change = new WinDef.DWORDByReference();
          Winspool2.PRINTER_NOTIFY_INFO.ByReference info = new Winspool2.PRINTER_NOTIFY_INFO.ByReference();
          success = Winspool.INSTANCE.FindNextPrinterChangeNotification(changeNotificationsHandle,
                                                                        change,
                                                                        optionsPointer,
                                                                        new WinDef.LPVOID(info.getPointer()));
          if (!success) {
            int errorCode = Kernel32.INSTANCE.GetLastError();
            throw new RuntimeException("Failed to get printer change notification - " + errorCode);
          }

          info.read();
          System.out.println("Change - " + String.format("0x%08X", change.getValue().longValue()));
          System.out.println(info.Version); // This should be printing 2, but I get a large number instead
        }

      } finally {
        runIfValidHandle(changeNotificationsHandle, Winspool.INSTANCE::FindClosePrinterChangeNotification);
      }
    } finally {
      runIfValidHandle(printServerHandle.getValue(), Winspool.INSTANCE::ClosePrinter);
    }
  }

  static boolean isValidHandle(WinNT.HANDLE handle) {
    return handle != null && !handle.equals(Kernel32.INVALID_HANDLE_VALUE);
  }

  static void runIfValidHandle(WinNT.HANDLE handle, Consumer<WinNT.HANDLE> consumer) {
    if (isValidHandle(handle)) {
      consumer.accept(handle);
    }
  }
}


If you send a print job to any printer (e.g. the built in Microsoft Print to PDF), the contents of info.Version is a very large number, rather than 2 as defined in the Windows documentation.

My usage of pointers and ByReference must be wrong, but I don't know how (I'm new to JNA).

Any advice would be greatly appreciated.

Thanks,

Ian

Tres Finocchiaro

unread,
Jan 6, 2021, 12:29:17 PM1/6/21
to jna-...@googlegroups.com
Ian,

From what I'm reading:

  • WinSpool says to set PRINTER_NOTIFY_OPTIONS.Version = 2.
    (you do)
  • WinSpool says to set PRINTER_NOTIFY_INFO.Version = 2.
    (not applicable to your code)

  • When you read PRINTER_NOTIFY_INFO.Version, it's not 2.
    (hence your concern about reading back the data).

I don't know much about ByReference conversion (sorry), but I'm curious as to your use-case as I'm working with identical parts of JNA.

What I'm most curious about is why you prepare the options struct at all?  I've had good luck using twall's example here: https://github.com/java-native-access/jna/blob/master/contrib/w32printing/src/com/sun/jna/platform/win32/Win32SpoolMonitor.java

It's much easier because it uses the valid "null" option for the options, and retrieves job status.  Using a similar technique WinSpoolUtil.getPrinterInfo1/getPrinterInfo2/getPrinterInfo4, WinSpoolUtil.getJobInfo1.  In my case, I don't specify an options size but rather loop over all the data and process manually.

I'm sorry this doesn't answer your question directly, but I wanted to share.


--
You received this message because you are subscribed to the Google Groups "Java Native Access" group.
To unsubscribe from this group and stop receiving emails from it, send an email to jna-users+...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/jna-users/8ab592a9-030d-42ab-b649-4bb9aa5d46b2n%40googlegroups.com.

Ian O'Neill

unread,
Jan 6, 2021, 12:59:24 PM1/6/21
to Java Native Access
Hi Tres,

I'm looking to do print monitoring for all printers on the machine, rather than just a specific printer. I was therefore thinking it would be more efficient to have a single process that gets notified about print jobs from all printers - which you can achieve by opening a print handle with null as the printer name.

If I do this though, I need to pass an options object into FindFirstPrinterChangeNotification() and FindNextPrinterChangeNotification() so that the info object gets populated with data such as the printer name, the job name and it's current status.

The Win32SpoolMonitor class you link to was the starting point for my code - and using it verbatim works great for one printer. If a print server has 50 printers attached to it though, I didn't really want to have to create 50 Java threads to deal with them all and the management overhead that will bring.

FYI I see you're using WinspoolUtil.getJobInfo1() in your code - you'll almost certainly run into (like I did) the issue resolved by https://github.com/java-native-access/jna/pull/1291- by coincidence this was fixed just days ago.

Best,

Ian

Matthias Bläsing

unread,
Jan 8, 2021, 5:03:56 PM1/8/21
to jna-...@googlegroups.com
Hi Ian,

Am Mittwoch, den 06.01.2021, 04:08 -0800 schrieb Ian O'Neill:
> [Sample Code for API]

If you send a print job to any printer (e.g. the built in Microsoft
Print to PDF), the contents of info.Version is a very large number,
rather than 2 as defined in the Windows documentation.

My usage of pointers and ByReference must be wrong, but I don't know
how (I'm new to JNA).

Any advice would be greatly appreciated.

I had a look at your code and decided to push my suggestions here:

https://github.com/matthiasblaesing/JNA-Demos/tree/master/PrinterAccess

I checked in your original code and updated it with my suggestions. I
tried to add enough comments to make it understandable. The core
changes are here:

https://github.com/matthiasblaesing/JNA-Demos/commit/a5bb6c50e9c03db7a6e3222d9ba3be2dce21457b#diff-3352f0e148483917a126ea81e9a0e4bab1098a7f0f72e5057ce1195194c0b59f

An additional update can be found here - this one only simplifies the
calls a slight bit:

https://github.com/matthiasblaesing/JNA-Demos/commit/105966c0425e10679c8e4448f5df9e14f216efb0#diff-3352f0e148483917a126ea81e9a0e4bab1098a7f0f72e5057ce1195194c0b59f

If you have questions, feel free to do.

Greetings

Matthias

Ian O'Neill

unread,
Jan 9, 2021, 2:57:03 PM1/9/21
to Java Native Access
Hi Matthias,

Thank you very much - this works perfectly!

Would you be happy for me to make a pull request against the main JNA repo integrating the changes from Winspool2 into the main Winspool class to benefit the community? I would of course mention you in the commit/pull request.

I'll add in a tidied up and renamed copy of the JnaTest class into the contrib directory too, so that others can see a more complex print monitoring example.

Once again - thank you for putting in your time to help.

Best,

Ian

Matthias Bläsing

unread,
Jan 9, 2021, 3:01:36 PM1/9/21
to jna-...@googlegroups.com
Hi Ian,

Am Samstag, den 09.01.2021, 11:57 -0800 schrieb Ian O'Neill:
Would you be happy for me to make a pull request against the main JNA repo integrating the changes from Winspool2 into the main Winspool class to benefit the community? I would of course mention you in the commit/pull request.

that would be great!

Matthias
Reply all
Reply to author
Forward
0 new messages