简体   繁体   中英

JNA passing structure containing pointer to structure(s) and pointer to primitive

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, ie 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:

  1. The JAN mappings are (intentionally) naive but are they really stupid? If so what are the logical types?

  2. 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?

  3. 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.

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM