Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JNA arrays and Pointer

Tags:

java

c

jna

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!!)

like image 470
StackTrace Avatar asked Feb 23 '23 20:02

StackTrace


2 Answers

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 :

  1. take the first 4 Bytes following the flags field
  2. treat it as a pointer
  3. go into ram to the pointed adress and read 16 Bytes

Which 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

like image 73
Cerber Avatar answered Feb 25 '23 11:02

Cerber


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...
}
like image 35
technomage Avatar answered Feb 25 '23 12:02

technomage