Reputation: 1561
I am using JNA and finding it very straight-forward for retrieving data from a native library, but struggling to understand how to to do it the other way round, i.e. passing structured data to a native method.
I'll use a small example from part of the library I'm trying to invoke.
The native library typedefs are as follows:
typedef struct CreateInfo {
int count; // Number of queue infos
const QueueInfo* queues; // Zero-or-more queue info structures
} CreateInfo;
typedef struct QueueInfo {
int count; // Number of queue priorities
const float* priorities; // 'array' of queue priorities
} QueueInfo;
So we have a CreateInfo
that refers to a number of QueueInfo
each of which contains a list of floating-point values.
A naive JNA implementation of these structure could be as follows (field order, constructors, etc omitted for brevity):
public class CreateInfo extends Structure {
public int count;
public QueueInfo.ByReference queues;
}
public QueueInfo extends Structure {
int count;
public Pointer priorities;
}
So:
The JAN mappings are (intentionally) naive but are they really stupid? If so what are the logical types?
If I already have an array of QueueInfo
can I simply set the pointer to the first element of that array? Or do I have to allocate an array using Structure::toArray
? The structures have no constructor other than the default, should they have?
I have the queue priorities float array but how do I set the pointer? Should it actually be a pointer or something else? A float[]?
I can find lots of questions on SO and the interwebs in general for receiving structures from a native library, but relatively few for passing structured data. And the examples I've found all use different approaches for the same problem that seem very complex for what should be pretty simple (?) so I'm at a lost for the 'correct' approach.
I suspect I'm not asking the right questions which probably means I'm missing something fundamental about JNA.
Hopefully some kind soul can point out what is wrong with the naive JNA code above and how it could be populated with data on the Java side.
Upvotes: 2
Views: 645
Reputation: 9091
1 - JNA mappings
Mappings are designed to directly relate Java-side types to the corresponding native side types. When the memory required for these mappings is well known, JNA works very well. Unfortunately, when the amount of native memory to be mapped is variable, that requires a bit of work to allocate and map the required native memory. There are a few ways to go about doing this, with varying levels of abstraction/control.
2 - already have QueueInfo[] (part 1)
With the way you've defined QueueInfo
in your question, it's not helpful. You've only defined a Java-side class but the Pointer
class implies a native memory pointer. You should modify your class to extend Structure
and use public
on your count
field. Note that instantiating this structure will only allocate native memory for the int
and the Pointer
. The memory for the array itself will need to be allocated separately.
3 - allocate float array
As I mentioned in the comments, one way of doing this is to allocate native memory for the float array:
Memory buffer = new Memory(count * Native.getNativeSize(Float.TYPE));
Then assuming you have float[] buf
defined you can copy this into the native memory using
buffer.write(0L, buf, 0, count);
You can then just use buffer
as the priorities
field of your QueueInfo
instance.
2 - already have QueueInfo[] (part 2)
Now to the question, you can't just set the pointer to the first element unless you know you have a contiguous C-side array. Your choices are using Structure::toArray
to allocate the memory (and then populating each element) or separately creating an array of (contiguous) pointers and copying over the Pointer
value from your separately allocated structures. For the toArray
variant, you don't need a pointer constructor if you directly set the values in the generated array, but the pointer constructor can make copying (from one native memory block to the other) easier.
Summary
Option 1: instantiate separate QueueInfo
structures using the Pointer.write()
method for the float array. It might be helpful to create a constructor which takes the float[]
as an argument and sets the count
and allocates and sets priorities
variable as described above. Then, create an array of Pointer
s for the CreateInfo
structure and copy over each element's reference pointer.
Option 2: create an array of structures using Structure::toArray
to allocate the native memory; then iterate over this structure and directly create the QueueInfo
structures at the appropriate index.
Upvotes: 1