Search code examples
javacjvmjava-native-interface

Is there a way to set DirectByteBuffer memory with JNI?


For some reason I have to use Linux-specific API which is not currently accessible from JVM directly and need to design a method which accepts ByteBuffer (This is definitely not because of some performance reason). Here is how it looks like:

//I need to call device-specific ioctl here
//and fill the ByteBuffer
public static native long putIntoByteBuffer(int fd, ByteBuffer buf);

The generated header file is

JNIEXPORT jlong JNICALL Java_net_abcomm_PlatformSpecific_putIntoByteBuffer
  (JNIEnv *, jclass, jint, jobject);

How to get the char* by the given ByteBuffer with JNI? I could use DirectBuffer, but then I will be limited to DirectBuffers only and besides the following warning is generated:

warning: DirectBuffer is internal proprietary API and may be removed in a 
future release
import sun.nio.ch.DirectBuffer;

Also there is GetDirectBufferAddress returning void* but it is limited to DirectBuffers only.


Solution

  • Supposing that you limit yourself to public classes and their public API, you have two relatively efficient alternatives for approaching the problem, but they have in common that they rely on putting the data into a Java byte[]. That's not so hard:

    /* error checks omitted for brevity; these are *not* optional */
    
    char *cBytes = /* ... */;
    size_t numCBytes = /* ... */;
    jbyteArray javaBytes = /* ... */;
    
    jsize numBytes = (*env)->GetArrayLength(env, javaBytes);
    jbyte *bytes = (*env)->GetPrimitiveArrayCritical(env, javaBytes, NULL);
    /* It is pretty safe to assume that jbyte is a character type, such as signed
       char, in any C implementation that supports the JNI at all */
    /* Assumes numCBytes <= numBytes; adjust as necessary if that may not be true: */
    memcpy(bytes, cBytes, numCBytes);
    (*env)->ReleasePrimitiveArrayCritical(env, javaBytes, bytes, 0);
    

    Note that if the JNI function performs some kind of I/O to get the bytes, it may be able to read them directly into bytes (depending on the native requirements, not on JNI), but in that case you should use the non-Critical versions of the Get/Release routines.

    With that said, your two main alternatives for getting the data all the way into a ByteBuffer are

    • if the buffer hasArray(), then obtain the byte[] for the above procedure via the buffer's array() method. Done and done.

    • if the buffer does not hasArray(), or if you don't want to check, then obtain the byte[] by instantiating it freshly, and after loading it via the above procedure, copy the contents into the buffer via the buffer's bulk put(byte[]) or put(byte[], int, int) method. Obviously, this involves an extra copy relative to the other alternative, and it uses an extra temporary object.

    I cannot recommend assuming a specific concrete ByteBuffer subclass or relying on non-public methods. That might be worth considering if performance were a high priority, but you seem to say it's not.