Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add missing PRINTER_INFO_X structs #1429

Draft
wants to merge 22 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
87 changes: 85 additions & 2 deletions contrib/platform/src/com/sun/jna/platform/win32/WinGDI.java
Expand Up @@ -28,18 +28,101 @@
import com.sun.jna.Structure;
import com.sun.jna.Structure.FieldOrder;
import com.sun.jna.platform.win32.WinNT.HANDLE;
import com.sun.jna.platform.win32.WinDef.HBITMAP;
import com.sun.jna.platform.win32.WinDef.RECT;
import com.sun.jna.Union;

import static com.sun.jna.platform.win32.WinDef.*;

/**
* Ported from WinGDI.h.
* Microsoft Windows SDK 6.0A.
* @author dblock[at]dblock.org
* @author Andreas "PAX" Lück, onkelpax-git[at]yahoo.de
* @author SSHTOOLS Limited, support@sshtools.com
*/
public interface WinGDI {
int RDH_RECTANGLES = 1;

@FieldOrder({ "dmDeviceName", "dmSpecVersion", "dmDriverVersion", "dmSize", "dmDriverExtra", "dmFields", "dmUnion1", "dmColor",
"dmDuplex", "dmYResolution", "dmTTOption", "dmCollate", "dmFormName", "dmLogPixels", "dmBitsPerPel", "dmPelsWidth",
"dmPelsHeight", "dummyunionname2", "dmDisplayFrequency", "dmICMMethod", "dmICMIntent", "dmMediaType", "dmDitherType",
"dmReserved1", "dmReserved2", "dmPanningWidth", "dmPanningHeight" })

public static class DEVMODE extends Structure {
tresf marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reopening this comment, since the previous one at this code marking ended up talking about fixing the pointer and then reading a wide string properly.

Quoting @dbwiddis:

Unions are fine, but generally you should try to add code in the constructor that detects which union member needs to be read, and then reads it. That looks a little bit complex here... do you read fields and then based on which bits are set choose which union member applies?

@dbwiddis Is the current structure OK? My structure is a copy from a project called "damage" and it seems to mirror the Microsoft definition exactly, take note of the words DUMMY... in the definition.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the current structure OK?

I'd quibble over style; I'd prefer to see all the structure fields together at the top, and the nested class definitions below them. Right now it's hard to see which elements are in the union.

Yes, it's fine to match the Windows API names.

There is one missing piece, though, and I'm not clear how the structure works. In the native, all fields of the union are essentially "active" at any given time. In JNA, we only "read" the member of the union which is declared to be the correct type. For most unions this information is given in one of the other fields.

In this structure it looks like one of the fields is a bitmask indicating which of several union fields may be active. So you might have to do some bit manipulation there, or perhaps just read all three possibilities, doing something like this in the DEVMODE structure:

@Override
public void read() {
    super.read();
    dmUnion1.setType(DUMMYSTRUCTNAME.class);
    dmUnion1.read();
    dmUnion1.setType(POINT.class);
    dmUnion1.read();
    dmUnion1.setType(DUMMYSTRUCTNAME2.class); 
    dmUnion1.read();
} 

Copy link
Contributor Author

@tresf tresf Mar 30, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dbwiddis thanks. I've taken a deep dive into DEVMODE and I'm starting to understand its data. I'm not entirely sure what happens when it's of unexpected length and that's something I need to do more research on.

For starters, I've placed JavaDoc comments into the Structure explaining exactly when a field is used, so this PR has a lot more DEVMODE information in it now.

I've also moved the fields around, I hope they're to your liking. :)

  • dmDriverExtra: For example, the private driver data dmDriverExtra is meaningless to JNA, it can only ever be used by the driver manufacturer, since this format is intentionally proprietary. I'm sure someone will eventually write a function to read this, but it may be easier to just extend the DEVMODE struct when that happens. I'm fine leaving this alone, we just won't read the private data, as we have no place to put it. :)

  • dmSize: I think this one is going to be our friend, but I haven't wrapped my head around how to use it. What I've researched so far...

    • DUMMYUNIONNAME.DUMMYSTRUCTNAME is NEVER used for Displays. For this reason, I would like to rename it, but I don't know the ramifications of this.
    • DUMMYUNIONNAME.DUMMYSTRUCTNAME2 equally is NEVER used for Printers. For this reason, I would like to rename it as well, if that's a sane thing to do.

      *Correction, this is a shared struct, used for both printers and displays.
    • I have a suspicion that dmSize will give us some insight into which DUMMYSTRUCT will be provided, but I would love some help in this area if possible. I can't find a bitwise flags (yet) that would just expose to use if this DEVMODE is of type PRINTER or DISPLAY, but I think (I hope) that this answers the original concerns.
    • One final option is I could make a skeleton DEVMODE interface and extend/implement it for DEVMODE_PRINTER, DEVMODE_DISPLAY, however I think this is a bit of overkill and would be less intuitive to the end-user.

Copy link
Contributor

@dbwiddis dbwiddis Mar 30, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First ... not sure what you're doing with the XP_OR_HIGHER boolean. If you do need it, use the Windows API VersionHelpers.IsWindowsXPOrGreater().

To me it looks like dmSize would always be the same value which would match the JNA-calculated size() of this structure. I don't think you can use it for choosing the union -- by definition the largest potential member is always used. It appears dmDriverExtra can give a clue about a larger allocation you could use, but you have to first read the structure once to get that value, allocate something bigger, and then read it again to be able to use it. Not sure that's a use case you need to handle, though.

It looks to me like you could use the dmFields value. The example in the API mentions the bit dmOrientation which is in the DUMMYSTRUCTNAME struct. So if that bit is present, you'd have to read that member of the union. Same for dmPaperSize, dmPaperLength, etc. So you'd modify my code above to something like:

@Override
public void read() {
    super.read();
    if (dmFields & (DM_ORIENTATION | DM_PAPERSIZE | DM_PAPERLENGTH 
            | DM_PAPERWIDTH | DM_SCALE | DM_COPIES | DM_DEFAULTSOURC
            | DM_PRINTQUALITY) > 0) {
        dmUnion1.setType(DUMMYSTRUCTNAME.class);
        dmUnion1.read();
    }
    if (dmFields & (DM_POSITION) > 0) {
        dmUnion1.setType(POINT.class);
        dmUnion1.read();
    }
    if (dmFields & (DM_POSITION | DM_DISPLAYORIENTATION | DM_DISPLAYFIXEDOUTPUT) > 0) {
        dmUnion1.setType(DUMMYSTRUCTNAME2.class); 
        dmUnion1.read();
    }
} 

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reading more it does look like there are different versions of the structure, thus the different version numbers and size fields available. I'm guessing the newer version(s) added/would add fields at the end. So if the size is smaller than size() you would only partially read the structure.

So this falls into a typical Windows API situation of "call once to get the size and then call again with the properly sized buffer" pattern.

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

private static final int CHAR_WIDTH = Boolean.getBoolean("w32.ascii") ? 1 : 2;

public byte[] dmDeviceName = new byte[Winspool.CCHDEVICENAME * CHAR_WIDTH];
public WORD dmSpecVersion;
tresf marked this conversation as resolved.
Show resolved Hide resolved
public WORD dmDriverVersion;
public WORD dmSize;
public WORD dmDriverExtra;
public DWORD dmFields;
public DUMMYUNIONNAME dmUnion1;

public static class DUMMYUNIONNAME extends Union {
public DUMMYSTRUCTNAME dummystructname;

@FieldOrder({ "dmOrientation", "dmPaperSize", "dmPaperLength", "dmPaperWidth", "dmScale", "dmCopies", "dmDefaultSource",
"dmPrintQuality" })
public static class DUMMYSTRUCTNAME extends Structure {
tresf marked this conversation as resolved.
Show resolved Hide resolved
public short dmOrientation;
public short dmPaperSize;
public short dmPaperLength;
public short dmPaperWidth;
public short dmScale;
public short dmCopies;
public short dmDefaultSource;
public short dmPrintQuality;

public DUMMYSTRUCTNAME() {
super();
}
}

public POINT dmPosition;
public DUMMYSTRUCTNAME2 dummystructname2;

@FieldOrder({ "dmPosition", "dmDisplayOrientation", "dmDisplayFixedOutput" })
public static class DUMMYSTRUCTNAME2 extends Structure {
public POINT dmPosition;
public DWORD dmDisplayOrientation;
public DWORD dmDisplayFixedOutput;

public DUMMYSTRUCTNAME2() {
super();
}
}
}

public short dmColor;
public short dmDuplex;
public short dmYResolution;
public short dmTTOption;
public short dmCollate;
public byte[] dmFormName = new byte[Winspool.CCHFORMNAME * CHAR_WIDTH];
public WORD dmLogPixels;
public DWORD dmBitsPerPel;
public DWORD dmPelsWidth;
public DWORD dmPelsHeight;
public DUMMYUNIONNAME2 dummyunionname2;

public static class DUMMYUNIONNAME2 extends Union {
public DWORD dmDisplayFlags;
public DWORD dmNup;
}

public DWORD dmDisplayFrequency;
public DWORD dmICMMethod;
public DWORD dmICMIntent;
public DWORD dmMediaType;
public DWORD dmDitherType;
public DWORD dmReserved1;
public DWORD dmReserved2;
public DWORD dmPanningWidth;
public DWORD dmPanningHeight;
}

@FieldOrder({"dwSize", "iType", "nCount", "nRgnSize", "rcBound"})
class RGNDATAHEADER extends Structure {
public int dwSize = size();
Expand Down
93 changes: 93 additions & 0 deletions contrib/platform/src/com/sun/jna/platform/win32/Winspool.java
Expand Up @@ -29,6 +29,7 @@
import com.sun.jna.Structure;
import com.sun.jna.Structure.FieldOrder;
import com.sun.jna.Union;
import com.sun.jna.platform.win32.WinGDI.DEVMODE;
import com.sun.jna.platform.win32.WinBase.SYSTEMTIME;
import com.sun.jna.platform.win32.WinDef.DWORD;
import com.sun.jna.platform.win32.WinDef.DWORDByReference;
Expand All @@ -41,6 +42,8 @@
import com.sun.jna.win32.StdCallLibrary;
import com.sun.jna.win32.W32APIOptions;

import static com.sun.jna.platform.win32.WinDef.*;

/**
* Ported from Winspool.h. Windows SDK 6.0a
*
Expand All @@ -51,6 +54,7 @@ public interface Winspool extends StdCallLibrary {
Winspool INSTANCE = Native.load("Winspool.drv", Winspool.class, W32APIOptions.DEFAULT_OPTIONS);

public static final int CCHDEVICENAME = 32;
public static final int CCHFORMNAME = 32;

public static final int PRINTER_STATUS_PAUSED = 0x00000001;
public static final int PRINTER_STATUS_ERROR = 0x00000002;
Expand Down Expand Up @@ -508,6 +512,20 @@ public boolean hasAttribute(int value) {
}
}

/**
* The PRINTER_INFO_3 structure specifies printer security information.
*/
@FieldOrder({"pSecurityDescriptor"})
public static class PRINTER_INFO_3 extends Structure {
public WinNT.SECURITY_DESCRIPTOR_RELATIVE pSecurityDescriptor;
Copy link
Contributor Author

@tresf tresf Mar 29, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although WinNT provides a struct named SECURITY_DESCRIPTOR, the struct is malformed for use with PRINTER_INFO_3 despite having an identical name. Using WinNT.SECURITY_DESCRIPTOR will throw a runtime error java.lang.IllegalStateException: Array fields must be initialized.

Fortunately SECURITY_DESCRIPTOR_RELATIVE matches the struct design exactly that's returned by PRINTER_INFO_3, fixing this runtime exception. This is just an FYI and can be acknowledged by a project maintainer if there are no objections. :)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a pointer to SECURITY_DESCRIPTOR and I believe the _RELATIVE version is the correct "by reference" mapping.

Copy link
Contributor Author

@tresf tresf Mar 30, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reopening... so going back on the DWORD vs int conversation, I'm looking for examples and found that PRINTER_INFO_2 uses public INT_PTR pSecurityDescriptor; however this PR uses the WinNT.SECURITY_DESCRIPTOR_RELATIVE. Should I switch PRINTER_INFO_3 to match? It seems less useful, but I'd like to be consistent. If so, how would I go about getting the data, construct a Pointer from the int and manually cast it? If that's the case, is this better than just using the correct struct to begin with?

Copy link
Contributor Author

@tresf tresf Mar 30, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same issue occurs for PRINTER_INFO_2, which returns a INT_PTR in place of the pDevMode, whereas the newly written PRINTER_INFO_8, PRINTER_INFO_9 both use this PR's struct. pDevMode appears again in L_PRINTER_DEFAULTS, but this time as a Pointer object.

I can update PRINTER_INFO_2, but those using the API in its current form will break.

Alternately I can switch PRINTER_INFO_9 and PRINTER_INFO_8 to use Pointers and then allow DEVMODE to be constructed manually, but I think this will be less intuitive.

I tried to convert the INT_PTR to a Pointer and then to a struct, but I got an error java.lang.UnsupportedOperationException: This pointer is opaque: const@0x1a31bba904c.

// Doesn't work :(
public DEVMODE(Pointer p) {
    super(p);
    ensureAllocated();
    read();
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reopening... so going back on the DWORD vs int conversation, I'm looking for examples and found that PRINTER_INFO_2 uses public INT_PTR pSecurityDescriptor; however this PR uses the WinNT.SECURITY_DESCRIPTOR_RELATIVE. Should I switch PRINTER_INFO_3 to match?

My apologies, I answered the first one thinking about a completely different recent change. Ultimately you need to map a pointer of some sort, and then use that pointer to map to the structure it points to. Using the ByReference tag on the structure accomplishes this transparently and should probably be your first choice. So I'd suggest WinNT.SECURITY_DESCRIPTOR_RELATIVE.ByReference. Check the commit dates on the other structure, it's entirely possible it was committed first.

Don't change old ones to break compatibility. If you want consistency, feel free to match them, but INT_PTR is a pointer-sized integer so you'd need to use toPointer() to convert it and then use that pointer to pass to a constructor.

It seems less useful, but I'd like to be consistent. If so, how would I go about getting the data, construct a Pointer from the int and manually cast it? If that's the case, is this better than just using the correct struct to begin with?

I think the structure (by reference) is the easiest. So you get INT_PTR ip, you do Pointer p = ip.toPointer() and then pass p to the pointer constructor of the structure, essentially this (FooStructure extends Structure):

FooStructure(Pointer p) {
  super(p);
  // anything else you need to do
}


public PRINTER_INFO_3() {}

public PRINTER_INFO_3(int size) {
super(new Memory((long)size));
}
}

/**
* The PRINTER_INFO_4 structure specifies general printer information.
* <p>
Expand Down Expand Up @@ -546,6 +564,81 @@ public PRINTER_INFO_4(int size) {
}
}

/**
tresf marked this conversation as resolved.
Show resolved Hide resolved
* The PRINTER_INFO_5 structure specifies detailed printer information.
*/
@FieldOrder({"pPrinterName", "pPortName", "Attributes", "DeviceNotSelectedTimeout", "TransmissionRetryTimeout" })
public static class PRINTER_INFO_5 extends Structure {
public String pPrinterName;
public String pPortName;
public DWORD Attributes;
public DWORD DeviceNotSelectedTimeout;
public DWORD TransmissionRetryTimeout;

public PRINTER_INFO_5() {}

public PRINTER_INFO_5(int size) {
super(new Memory((long)size));
}
}

/**
* The PRINTER_INFO_6 structure specifies the status value of a printer.
*/
@FieldOrder({"dwStatus"})
public static class PRINTER_INFO_6 extends Structure {
public DWORD dwStatus;

public PRINTER_INFO_6() {}

public PRINTER_INFO_6(int size) {
super(new Memory((long)size));
}
}

/**
* The PRINTER_INFO_7 structure specifies directory services printer information.
*/
@FieldOrder({"pszObjectGUID", "dwAction"})
public static class PRINTER_INFO_7 extends Structure {
public String pszObjectGUID;
public DWORD dwAction;

public PRINTER_INFO_7() {}

public PRINTER_INFO_7(int size) {
super(new Memory((long)size));
}
}

/**
* The PRINTER_INFO_8 structure specifies the global default printer settings.
*/
@FieldOrder({"pDevMode"})
public static class PRINTER_INFO_8 extends Structure {
public DEVMODE.ByReference pDevMode;

public PRINTER_INFO_8() {}

public PRINTER_INFO_8(int size) {
super(new Memory((long)size));
}
}

/**
* The PRINTER_INFO_9 structure specifies the per-user default printer settings.
*/
@FieldOrder({"pDevMode"})
public static class PRINTER_INFO_9 extends Structure {
public DEVMODE.ByReference pDevMode;

public PRINTER_INFO_9() {}

public PRINTER_INFO_9(int size) {
super(new Memory((long)size));
}
}

/**
* The PRINTER_DEFAULTS structure specifies the default data type,
* environment, initialization data, and access rights for a printer.
Expand Down