Efficient JNI programming IV: Wrapping native data objects
July 30, 2007 4 Comments
Everytime I write an article about JNI, there’s some more stuff that comes to mind. This time about wrapping native data structures in a Java object. This is something very basic which almost every JNI program has to deal with, so I’ll have a look at a couple of approaches.
Casting pointers to long
The most straightforward, easy and probably most common approach to this problem is to simply cast the pointer to the data object to a jint
and store it into a field of the wrapper object. In order to stay compatible with 64 bit pointers on newer systems, you really want to cast to a jlong
instead. This is certainly the approach with the least overhead and least storage demands. But it has some (slight) disadvantages that you might consider. Let’s look at
Wrapping pointers in special objects
Some people would argue that exposing a pointer as long field (even if it’s private) is a little dangerous, because this means that the actual address could be accidentally changed from the Java side. This is why we have a bunch of special classes plus helper code in GNU Classpath. There, the pointer is stored in an instance of gnu.classpath.Pointer
, an abstract class with two concrete subclasses, gnu.classpath.Pointer32
and gnu.classpath.Pointer64
, each of which implements a 32 bit and 64 bit pointer respectively. This is accompanied by a couple of native helper functions to allow easy access to the actual pointer. This way it is possible to store a native pointer in an opaque way on the Java side. The tradeoff is a slightly higher memory demand (+1 Java object) and slightly more overhead to access the pointer. But it doesn’t hurt that much either (all are small O(1) operations).
Direct ByteBuffer
This is a cool trick that I learned from JOGL. From JDK4 onwards you can quickly forget the last paragraph, because you don’t need to add any new classes or helper functions, because it’s all right there in the JDK. It might not be obvious at first glance, but the direct ByteBuffer
serves the exact same purpose, and does even more. Maybe the name ByteBuffer
is a little misleading for this use case, but alas, it’s basically a native pointer and some bookkeeping information. There’s even the required helper methods in JNI, NewDirectByteBuffer()
, GetDirectBufferAddress()
and GetDirectBufferCapacity()
. Suppose you have a native datastructure of type MyNativeStruct
and want to wrap it as a direct ByteBuffer
, you’d do:
MyNativeStruct* data; // Initialized elsewhere. jobject bb = (*env)->NewDirectByteBuffer(env, (void*) data, sizeof(MyNativeStruct));
Later when you need to access the pointer, you can do this:
jobject bb; // Initialized elsewhere. MyNativeStruct* data = (MyNativeStruct*) (*env)->GetDirectBufferAddress(env, bb);
Easy, isn’t it? But it’s even better. Using this approach it is possible to actually access the native data structure from Java. You need to know the data layout of the native data and can then access it from Java using the ByteBuffer
methods.
Suppose you have such a data structure:
struct { int exampleInt; short exampleShort; } MyNativeStruct;
You could provide accessor methods on the java side like in the following example.
public int getExampleInt() { return bb.getInt(0); } public short getExampleShort() { return bb.getShort(4); }
The ByteBuffer
code would even sort out endianess problems for you. Neat, isn’t it?
Also check out part I, part II and part III of this series too.