Search code examples
javajava-native-interface

Direct ByteBuffer


I am testing out direct ByteBuffer(java.nio.ByteBuffer) with JNI. So the code below tries to:

  1. Put values into direct ByteBuffer in Java
  2. Change the value in C++
  3. Get the value in Java

I was wondering where exactly did I do wrong? The C++ code manage to get the data from Java but changes in C++ did not reflect back in Java.

This is what I did on java:

public static void main(String[] args){
    ByteBuffer bb = ByteBuffer.allocateDirect(3);
    byte[] b = {122,121,120};
    System.out.println("1: " + new String(b));
    bb.put(b);

    new JNI.process(bb);

    byte[] c = new byte[3];
    c[0] = bb.get();
    System.out.println("4: " + new String(c));
}

This is what I did on JNI function:

JNIEXPORT void JNICALL Java_MarsJNI_mapreduce
  (JNIEnv *env, jobject thisObj, jobject output){
    char *out = (char*)env->GetDirectBufferAddress(output);
    printf("2: %s\n", out);
    out = "ABC";
    printf("3: %s\n", out);
}

And the result I get is:

1: zyx
2: zyx
3: ABC
Exception in thread "main" java.nio.BufferUnderflowException
    at java.nio.Buffer.nextGetIndex(Buffer.java:474)
    at java.nio.DirectByteBuffer.get(DirectByteBuffer.java:208)
    at MarsJNI.main(MarsJNI.java:21)

Solution

  • First problem: see @TedBigham's answer. You can also use buf.rewind().

    Second problem: you only copy the first byte of the buffer into c, not the whole buffer. Do:

    byte[] c = new byte[3];
    bb.rewind();
    bb.put(c);
    System.out.println("4: " + new String(c));
    

    Third problem: your C++ code does:

    char *out = (char*)env->GetDirectBufferAddress(output);
    // ...
    out = "ABC";
    

    But what you do here is create { 'A', 'B', 'C', 0 } and assign out to it; you don't actually modify the content of the buffer. You should do:

    memcpy(out, "ABC", 3);
    

    Fourth problem: when you create a String out of a byte[], you should specify the encoding:

    new String(c, StandardCharsets.UTF_8);