I'm working on a project at the moment that requires me to receive a call in Java from a C library. Basically I call a C function that takes a function pointer, the C function then uses the function pointer as a callback. I'm using JNA to pass a Java object (I'll call it the Callback object from now on) as the callback function. The callback object has a single method that receives a C structure (called Frame) that contains a 16 element byte array as well as other variables. I've wrapped this structure in a Java class in the standard JNA way, like this:
class Frame extends Structure {
public short port;
public short flags;
public Pointer name // this is a pointer to the byte array
public int rateDivisor;
}
The callback mechanism works fine! The callback object receives a Frame object from C, but when I try to get the byte array from the Pointer using name.getByteArray(0, 16)
the application crashes with an Access Violation Exception. However, if I replace the Pointer with a byte array:
class Frame extends Structure {
public short port;
public short flags;
public byte[] name = new byte[16];
public int rateDivisor;
}
Then the code works fine! There's a good reason why I don't want to use this code however. Every time I call the function the returned Frame is actually the same object (it's just being reused) but the byte array is a new object. This callback function is being called many times a second which causes the garbage collector to goes crazy gobbling up thousands of temporary arrays. This project is performance critical so I really don't want any temporary objects in this part of the application.
My guess is that by using a byte array in the Frame class I'm causing JNA to create a new copy of the C byte array. What I want to do is have a pointer to the C byte array therefore removing the need to copy the data, hence the experimentation with JNA Pointer.
My question is why can't I get the byte array from the Pointer? Any help would be much appreciated :)
(Note: The thing that makes this even more difficult is that I don't have access to the C source code!!)
The reason is probably as simple as that : name is not a pointer in the C structure counterpart.
Let me show you two examples :
C:
typedef struct{
char * name;
}MyCStruct;
maps to Java :
class MyJStruct extends Structure{
Pointer name;
}
But in this other scenario (in which I think you got yourself into):
C:
typedef struct{
char name[16];
}MyCStruct;
maps to Java :
class MyJStruct extends Structure{
byte[] name = new byte[16];
}
In the first case the C structure holds a pointer and it's sizeof is the size of a pointer (4 or 8Bytes depending on 32/64bits), in the second case it holds the whole array which mean it's size is 16 Bytes.
This explains the difference between the two Java mappings : JNA needs to know how to read the structure fields and it explains too why you get an error while calling name.getByteArray(0, 16)
as this exectute the following :
flags
fieldWhich crashed since the memory zone pointed is probably out of program reach.
You can check it yourself in C : if sizeof(struct Frame)
is 24 then the struct holds the whole array if it's 12 (or 16) then it holds a 32 bits pointer (or a 64 bits pointer)
Official documentation about this matter is pretty clear but hard to find, so here you go :
http://twall.github.com/jna/3.3.0/javadoc/overview-summary.html
you'll find about it under "Nested arrays" and I really encourage you to read the section just below "Variable-sized structures"
hope this helps
regards
Change the input parameter of your callback to Pointer. Maintain your own private Pointer->Frame map (with or without weak references), and only create a new Frame based on the input pointer if there isn't one already.
e.g.
Map frames = new HashMap<Pointer,Frame>();
void callback(Pointer p) {
Frame f = frames.get(p);
if (f == null) {
f = new Frame(p);
frames.put(p, f);
}
// do whatever...
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With