开发者

JNA arrays and Pointer

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 functi开发者_JAVA技巧on 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 :

  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


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


(New answer to meet StackTrace expectations ; see comment to my previous answer)

Well I'm not sure if there is a nice and clean way to do it ... hence I'll point out a nice game called the Memory and Pointer game

Keep in mind you might need it these ways (C) :

Frame nativeLibFunc(...);        //1
Frame * nativeLibFunc(...);      //2
void nativeLibFunc(Frame f);     //3
void nativeLibFunc(Frame * fptr);//4

The worst case in my opinion will be the third since you can't do it without the normal mapping explained before. Next worst case is the first since you won't get a location in RAM but the direct result at the top of the stack, hence reading it requires you to trick JNA to be able to fetch the Pointer and read what comes after the memory zone you want to skip (Java):

class MyMicroFrame extends Structure {
    public short port;
    public short flags;
    //public byte[] name = new byte[16]; ==> commented out, ignored
    //public int rateDivisor;            ==> commented out, read manually, see below
}
//...
MyMicroFrame mmf = NativeLib.INSTANCE.nativeLibFunc(...);
int rateDivisor = mmf.getPointer().getInt(20);

I think you got the idea. if your structure is bigger, just split it in small parts avoiding the zones you want to skip an re-join them by manually navigating in the memory pointed by the first part.

Things to keep in mind while working this way :

  1. Pointer size 32/64 bits -- 4 or 8 Bytes see com.sun.jna.Native.POINTER_SIZE
  2. Structure alignement which I've not checked on this answer
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜