As the author of the previous answer, I realize a lot of the confusion was approaching it one way before realizing a better solution that we discussed primarily in comments to your answer. I will try to answer this additional clarification with an actual demonstration of my suggestion on that answer which I think is the best approach. Simply, if you have a non-contiguous structure and need a contiguous structure, you must either bring the contiguous memory to the structure, or copy the structure to the contiguous memory. I'll outline both approaches below.
Is there another, simpler and more 'JNA' way of doing it? It seems really dumb (and probably incorrect) to have to allocate a byte-by-byte copy of the data that we essentially already have!
I did mention in my answer on the other question that you could use useMemory()
in this situation. It is a protected
method but if you are already extending a Structure
you have access to that method from the subclass (your structure), in much the same way (and for precisely the same purpose) as you would extend the Pointer
constructor of a subclass.
You could therefore take an existing structure in your collection and change its native backing memory to be the contiguous memory. Here is a working example:
public class Test {
@FieldOrder({ "a", "b" })
public static class Foo extends Structure {
public int a;
public int b;
// You can either override or create a separate helper method
@Override
public void useMemory(Pointer m) {
super.useMemory(m);
}
}
public static void main(String[] args) {
List<Foo> list = new ArrayList<>();
for (int i = 1; i < 6; i += 2) {
Foo x = new Foo();
x.a = i;
x.b = i + 1;
list.add(x);
}
Foo[] array = (Foo[]) list.get(0).toArray(list.size());
// Index 0 copied on toArray()
System.out.println(array[0].toString());
// but we still need to change backing memory for it to the copy
list.get(0).useMemory(array[0].getPointer());
// iterate to change backing and write the rest
for (int i = 1; i < array.length; i++) {
list.get(i).useMemory(array[i].getPointer());
list.get(i).write();
// Since sending the structure array as an argument will auto-write,
// it's necessary to sync it here.
array[1].read();
}
// At this point you could send the contiguous structure array to native.
// Both list.get(n) and array[n] point to the same memory, for example:
System.out.println(list.get(1).toString());
System.out.println(array[1].toString());
}
Output (note the contiguous allocation). The second two outputs are the same, from either the list or the array.
Test$Foo(allocated@0x7fb687f0d550 (8 bytes) (shared from auto-allocated@0x7fb687f0d550 (24 bytes))) {
int a@0x0=0x0001
int b@0x4=0x0002
}
Test$Foo(allocated@0x7fb687f0d558 (8 bytes) (shared from allocated@0x7fb687f0d558 (8 bytes) (shared from allocated@0x7fb687f0d558 (8 bytes) (shared from allocated@0x7fb687f0d550 (8 bytes) (shared from auto-allocated@0x7fb687f0d550 (24 bytes)))))) {
int a@0x0=0x0003
int b@0x4=0x0004
}
Test$Foo(allocated@0x7fb687f0d558 (8 bytes) (shared from allocated@0x7fb687f0d558 (8 bytes) (shared from allocated@0x7fb687f0d550 (8 bytes) (shared from auto-allocated@0x7fb687f0d550 (24 bytes))))) {
int a@0x0=0x0003
int b@0x4=0x0004
}
If you don't want to put useMemory
in every one of your structure definitions you can still put it in an intermediate class that extends Structure
and then extend that intermediate class instead of Structure
.
If you don't want to override useMemory()
in your structure definitions (or a superclass of them), you can still do it "simply" in code with a little bit of inefficiency by copying over the memory.
In order to "get" that memory to write it elsewhere, you have to either read it from the Java-side memory (via reflection, which is what JNA does to convert the structure to the native memory block), or read it from Native-side memory (which requires writing it there, even if all you want to do is read it). Under-the-hood, JNA is writing the native bytes field-by-field, all hidden under a simple write()
call in the API.
Your "Rubbish Solution #2" seems close to what's desired in this case. Here are the constraints that we have to deal with, with whatever solution:
- In the existing list or array of
Structure
, the native memory is not contiguous (unless you pre-allocate contiguous memory yourself, and use that memory in a controlled manner, or override useMemory()
as demonstrated above), and the size is variable.
- The native function taking an array argument expects a block of contiguous memory.
Here are the "JNA ways" of dealing with structures and memory:
- Structures have native-allocated memory at a pointer value accessible via
Structure.getPointer()
with a size of (at least) Structure.size()
.
- Structure native memory can be read in bulk using
Structure.getByteArray()
.
- Structures can be constructed from a pointer to native memory using the
new Structure(Pointer p)
constructor.
- The
Structure.toArray()
method creates an array of structures backed by a large, contiguous block of native memory.
I think your solution #2 is a rather efficient way of doing it, but your question indicates you'd like more type safety, or at least self-documenting code, in which case I'd point out a more "JNA way" of modifying #2 with two steps:
- Replace the new
Memory(array.length * size)
native allocation with the Structure.toArray()
allocation from your solution #1.
- You still have a
length * size
block of contiguous native memory and a pointer to it (array[0].getPointer()
).
- You additionally have pointers to the offsets, so you could replace
mem.write(n * size, ... )
with array[n].getPointer().write(0, ... )
.
- There is no getting around the memory copying, but having two well-commented lines which call
getByteArray()
and immediately write()
that byte array seem clear enough to me.
- You could even one-line it...
write(0, getByteArray(0, size), 0, size)
, although one might argue if that's more or less clear.
So, adapting your method #2, I'd suggest:
// Make your collection an array as you do, but you could just keep it in the list
// using `size()` and `list.get(n)` rather than `length` and `array[n]`.
MyStructure[] array = list.toArray(MyStructure[]::new);
// Allocate a contiguous block of memory of the needed size
// This actually writes the native memory for index 0,
// so you can start the below iteration from 1
MyStructure[] structureArray = (MyStructure[]) array[0].toArray(array.length);
// Iterate the contiguous memory and copy over bytes from the array/list
int size = array[0].size();
for(int n = 1; n < array.length; ++n) {
if(array[n] != null) {
// sync local structure to native (using reflection on fields)
array[n].write();
// read bytes from the non-contiguous native memory
byte[] bytes = array[n].getPointer().getByteArray(0, size);
// write bytes into the contiguous native memory
structureArray[n].getPointer().write(0, bytes, 0, bytes.length);
// sync native to local (using reflection on fields)
structureArray[n].read();
}
}
From a "clean code" standpoint I think this rather effectively accomplishes your goal. The one "ugly" part of the above method is that JNA doesn't provide an easy way to copy fields between Structures without writing them to native memory in the process. Unfortunately that's the "JNA way" of "serializing" and "deserializing" objects, and it's not designed with any "magic" for your use case. Strings include built-in methods to convert to bytes, making such "magic" methods easier.
It is also possible to avoid writing the structure to native memory just to read it back again if you do the field-by-field copy as you implied in your Method #1. However, you could use JNA's field accessors to make it a lot easier to access the reflection under the hood. The field methods are protected
so you'd have to extend Structure
to do this -- which if you're doing that, the useMemory()
approach is probably better! But you could then pull this iteration out of write()
:
for (StructField sf : fields().values()) {
// do stuff with sf
}
My initial thought would be to iterate over the non-contiguous Structure
fields using the above loop, storing a Field.copy()
in a HashMap
with sf.name
as the key. Then, perform that same iteration on the other (contiguous) Structure
object's fields, reading from the HashMap
and setting their values.