Search code examples
javajna

How to fill an array of structures in JNA?


I am trying to use the following windows API in JNA :

UINT WINAPI GetRawInputDeviceList(
  _Out_opt_  PRAWINPUTDEVICELIST pRawInputDeviceList,
  _Inout_    PUINT puiNumDevices,
  _In_       UINT cbSize
);

UINT cbSize is the size of a RAWINPUTDEVICELIST structure, in bytes. How to know it in JNA? I had accidentally found that 16 is a correct value.

The structure is the following:

typedef struct tagRAWINPUTDEVICELIST {
  HANDLE hDevice;
  DWORD  dwType;
} RAWINPUTDEVICELIST, *PRAWINPUTDEVICELIST;

pRawInputDeviceList is An array of RAWINPUTDEVICELIST structures so in JNA I'm declaring the following signature :

UINT GetRawInputDeviceList(PointerByReference pRawInputDeviceList, IntByReference puiNumDevices, UINT cbSize);

And here is my structurre in JNA :

public static class RawInputDeviceList extends Structure {

    public HANDLE hDevice;
    public DWORD dwType;

    public RawInputDeviceList() {
            // required for toArray()
    }

    public RawInputDeviceList(Pointer pointer) {
        super(pointer);
        read();
    }

    @Override
    protected List<String> getFieldOrder() {
        return Arrays.asList(new String[] { "hDevice", "dwType" });
    }

}

When i'm getting the PointerByReference value to get the pointer it is null, what is wrong with that? The number of devices is correct bu i can't get the RawInputDeviceList array correctly.

Here is the full test code:

public class TestRawInput {

    public static void main(String[] args) {
        PointerByReference lRawInputDevicesReference = new PointerByReference();
        IntByReference lNumDevices = new IntByReference();

        UINT lRes = WindowsRawInput.INSTANCE.GetRawInputDeviceList(lRawInputDevicesReference, lNumDevices, new UINT(16));

        Pointer lRawInputDevicePointer = lRawInputDevicesReference.getValue();
        WindowsRawInput.RawInputDeviceList lRawInputDevice = new WindowsRawInput.RawInputDeviceList(lRawInputDevicePointer);
        WindowsRawInput.RawInputDeviceList[] lDevices = (WindowsRawInput.RawInputDeviceList[]) lRawInputDevice.toArray(lNumDevices.getValue());

        System.out.println("devices deteced=" + lNumDevices.getValue());
        for (int i=0;i<lDevices.length;i++) {
            System.out.println("device type: " + lDevices[i].dwType);
        }
    }

    public interface WindowsRawInput extends StdCallLibrary {

        WindowsRawInput INSTANCE = (WindowsRawInput) Native.loadLibrary("user32", WindowsRawInput.class, W32APIOptions.DEFAULT_OPTIONS);

        UINT GetRawInputDeviceList(PointerByReference pRawInputDeviceList,
            IntByReference puiNumDevices, UINT cbSize);

        public static class RawInputDeviceList extends Structure {

            public HANDLE hDevice;
            public DWORD dwType;

            public RawInputDeviceList() {
                // required for toArray()
            }

            public RawInputDeviceList(Pointer pointer) {
                super(pointer);
                read();
            }

            @Override
            protected List<String> getFieldOrder() {
                return Arrays.asList(new String[] { "hDevice", "dwType" });
            }

        }
    }

}

Solution

    1. Create an array of continguously-allocated structures using Structure.toArray().
    2. Pass the first element of that array to your native function. In this case, struct* is equivalent to struct[] (native types), so use RawInputDeviceList as the argument type instead of PointerByReference. I'm not sure why they call the individual structure a "list", but that's windows for you.
    3. Pass the size of your array in puiNumDevices, which presumably the native function will update with the count actually filled in.
    4. The native function will fill in the memory, and JNA will sync the native memory to Java Structure on function return.
    5. cbSize may be obtained by calling Structure.size().