Search code examples
javaarraysstructurejna

JNA toArray() resets structure fields?


Consider the following JNA structure:

public class VkDeviceQueueCreateInfo extends VulkanStructure {
    public VkStructureType sType = VkStructureType.VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
    public Pointer pNext;
    public int flags;
    public int queueFamilyIndex;
    public int queueCount;
    public Pointer pQueuePriorities;
}

The sType field is a constant value used by the native layer to identify the type of structure. This works fine for a single instance of this class created using new.

However if we allocate an array of this structure using toArray() the sType is reset to the default value after the constructor has been invoked for each array element. i.e. it clears the field!

Alternatives that didn't work:

  • Setting the field explicitly in the constructor has no effect (it still gets reset).
  • Ditto making the field final.
  • Doesn't seem to matter whether we try the default constructor or one with a JNA Pointer argument.

The only thing that seems to work is to turn off auto-read for the structure:

public class VkDeviceQueueCreateInfo extends VulkanStructure {
    ...
    public VkDeviceQueueCreateInfo() {
        setAutoRead(false);
    }
}

(Note that this structure is only used to write to the native layer, not read anything back).

This works but what is it actually doing? Why is JNA resetting the structure to the native values when there aren't any yet? Is there a way to switch off the auto-read for this field globally?

This is not a big deal for a single structure, but in this project there are several hundred that were code generated from the native layer, most of which (but not all) with the sType pre-populated as the above example. Clearly pre-populating the field was not was the way to go, but what is the alternative? Will every structure need to be re-generated with the above fiddle?

EDIT: Another related question that comes to mind after brooding on this - what about array types in a structure? Are they reset to null by the auto-read thingy? The code-generated structures initialise any arrays to size the structure, e.g. public float[] colour = new float[4];


Solution

  • You are on the right track pointing out that the auto-read() is part of the problem here. When you invoke toArray() you are (usually) changing the memory backing for the array to a new native memory allocation (the auto-allocation zeroes out the memory). So all those 0's are loaded into your array.

    The internal Structure toArray() keeps the values for the first element for your convenience, but does nothing for the remainder, which are instantiated using newInstance() inside the loop. Here are the two lines causing your problem:

    array[i] = newInstance(getClass(), memory.share(i*size, size));
    array[i].conditionalAutoRead();
    

    My recommendation would be for you to override toArray() in your own VulkanStructure structure that the others inherit from. You could copy over the existing code and modify it as you see fit (e.g., remove the autoRead).

    Or you could overload a toArray() that gets passed a collection of Structures and copies over the backing memory from the old collection before reading it to the new one. Alternately, if the original memory backing is large enough when toArray() is called, the memory isn't cleared. So you could allocate your own large enough memory, use useMemory() on the first element to change its backing, and copy over the backing memory bytes; and they would be auto-read into the new array version.