I need to pass a JNA structure to the native layer that contains a pointer-to-structure field (may contain zero-or-more structures).
Here is the 'parent' structure:
public class VkRenderPassCreateInfo extends Structure {
public int attachmentCount;
public VkAttachmentDescription.ByReference pAttachments;
}
(Other fields, @FieldOrder and ByReference/Value classes omitted for brevity)
And here is the 'child' structure:
public class VkAttachmentDescription extends Structure {
public int flags;
// ... lots and lots of other simple fields
}
As per the JNA documentation (here) the pointer-to-array field should be a Structure.ByReference
field.
From other posts the standard approach to populating this field is:
init the field to a structure-by-reference
allocate an array of structures from the field using
Structure::toArray
populate the array
So:
// Init structure fields
renderPass.pAttachments = new VkRenderPassCreateInfo.ByReference();
renderPass.attachmentCount = size;
// Allocate memory
VkAttachmentDescription[] attachments = (VkAttachmentDescription[]) renderPass.pAttachments.toArray(size);
// Populate array
for(int n = 0; n < size; ++n) {
attachments[n].flags = ...
// and so on for other fields
}
1 - Is this the correct approach for initialising and allocating a pointer-to-structure field within a structure? Seems a lot of mucking around?
2 - The above works fine for fiddling sized structures but some of the ones I'm dealing with have a huge number of fields, sub-structures, etc. I had assumed I could just build an array of JNA structures on the Java side and set those directly into the parent structure, but the toArray
approach means I have to then copy everything to the generated array? Is there a better/simpler approach that means I don't have to create and copy data that I essentially already have on the Java side?
3 - JNA provides a StringArray
helper class that handles a similar case for an array of strings within a structure:
// Array of strings maintained on the Java side
List<String> strings = ...
// Nice and easy means of populating the JNA structure
structure.pStrings = new StringArray(strings.toArray(String[]::new));
...
// Send the data
library.fireandForget(structure);
This is sort-of what I'm trying to achieve with the structure code above but obviously is only for the string case - are there other similar helpers that I've missed?
Please note that the above is passing the structures to the native layer, I am not trying to retrieve anything.
EDIT 1: Just to qualify the point of this question - although the above works it results in a buge amount of boiler-plate code for all but the most trivial cases. I'm struggling to work out the simplest/best way of building a complex graph of structures to be passed to a native method. There seems a shortage of examples or tutorials, or maybe I'm just not asking the right question (?) Any pointers to examples, tutorials or example code of passing structures containing pointers to other structures would be very appreciated.
EDIT 2: So I've tried numerous approaches all of which result in Illegal memory access
errors when I invoke the native library.
The data that I want to send is constructed by the application - it could be a builder pattern, selections by the user, etc. In any case the result is a list of VkAttachmentDescription
that I then need to send as a pointer-to-structure field in the 'parent' VkRenderPassCreateInfo
.
The reason for using the JNA VkAttachmentStructure
on the Java side is that some these structures contain a large number of fields. i.e. calling Structure::toArray
and then populating the resultant array field-by-field is just not tenable: the amount of code would be huge, error-prone and brittle to change (e.g. forget to copy a new field). I could create another class to abstract the JNA class but that would just move the problem.
Here is what the code is doing:
// Application builds the attachments
final List<VkAttachmentDescription> attachments = ...
...
// At some point we then send the render pass including the attachments
// Populate the render pass descriptor
final VkRenderPassCreateInfo info = new VkRenderPassCreateInfo();
info.pAttachments = ??? <--- what?
// ... other fields
// Send the descriptor
library.sendRenderPass(info);
Attempt 1: Naively set the pointer-to-structure to an array:
final VkRenderPassCreateInfo info = new VkRenderPassCreateInfo();
final var array = attachments.toArray(VkAttachmentDescription.ByReference[]::new);
info.pAttachments = array[0];
library.sendRenderPass(info);
Result is a memory access error, I didn't expect this to work!
Attempt 2: Use Structure::toArray(int) and set the field to the first element
final VkAttachmentDescription.ByReference[] array = (VkAttachmentDescription.ByReference[]) new VkAttachmentDescription.ByReference().toArray(attachments.size());
for(int n = 0; n < size; ++n) {
array[n] = attachments.get(n);
}
info.pAttachments = array[0];
library.sendRenderPass(info);
Same result.
Attempt 3: Use Structure::toArray(array)
There is an alternative toArray
method in Structure
that takes an array but it doesn't seem to do anything different to calling the integer version?
Attempt 4: Copy field-by-field
final VkAttachmentDescription.ByReference[] array = (VkAttachmentDescription.ByReference[]) new VkAttachmentDescription.ByReference().toArray(attachments.size());
for(int n = 0; n < size; ++n) {
array[n].field = attachments.get(n).field;
// ...lots of other fields
}
info.pAttachments = array[0];
library.sendRenderPass(info);
This works but is nasty.
I'm obviously completely missing something about JNA. My main sticking point is that Structure::toArray
creates an array of empty structures that have to be populated field-by-field, but I already have the array of structures with everything populated - how can I set the pointer-to-structure field to that array (i.e. an equivalent of the StringArray
helper)? It seems such a simple thing to do in my head but I simply cannot find any examples of how to do what I want (other than trivial ones that copy field-by-field).
The other thing that is bothering me is that fact that the parent structure field has to be ByReference
which means every other structure in the code has to be by-reference? Again it just feels like I'm doing this all wrong.