Search code examples
javaccharjava-native-interfacebuffer

How can I use Java Native Interface to pass a byte array into a C function which takes a char* as an argument?


So I need to use JNI to call a C function from java. I've been able to successfully do this when passing in different data types (create the native variables, header file, shared library, blah blah), but can't get it to work with a byte array. Here's my C function:

#include <stdio.h>
void encrypt(int size, unsigned char *buffer);
void decrypt(int size, unsigned char *buffer);

void encrypt(int size, unsigned char *buffer){
    for(int i=0; i<size; i++){
        unsigned char c = buffer[i];
        printf("%c",c);
    }
}
void decrypt(int size, unsigned char *buffer){
    for(int i=0; i<size; i++){
        unsigned char c = buffer[i];
        printf("%c",c);
    }
}

And here's my java code (I understand that after making a header file from this, I have to replace the C function declarations by the JNI code in the header file)

class Tester{
    public native void encrypt(int size, char *buffer);
    public native void decrypt(int size, char *buffer);
    static{
    System.loadLibrary("buffer");
    {
    public static void main(String[] args){
        Tester test = new Tester();
        String hello = "hello";
        byte[] byteHello = hello.getBytes();
        test.encrypt(5,byteHello);
        test.decrypt(5,byteHello);
    }
}

I get that the char* type isn't supported in Java and that's why I'm getting an error trying to compile. Perhaps I should change the type to char[] in Java? Anyways, my goal is to be able to pass a byte array in Java into my C function, iterate through the byte array, and print out each element.


Solution

  • The Java char and C char types are incompatible, it would probably be better to pass the byte[] to C and then convert each element on demand.


    Something along these lines:

    Main.java:

    //...Code to load library...
    
    public static void main(String[] args) {
        passBytes("hello".getBytes());
    }
    
    public static native void passBytes(byte[] bytes);
    

    Main.c:

    #include "Main.h" // Main.h is generated
    
    JNIEXPORT void JNICALL Java_Main_passBytes
      (JNIEnv *env, jclass clazz, jbyteArray array) {
        unsigned char* buffer = (*env)->GetByteArrayElements(env, array, NULL);
        jsize size = (*env)->GetArrayLength(env, array);
    
        for(int i = 0; i < size; i++) {
            printf("%c", buffer[i]);
        }
    
        (*env)->ReleaseByteArrayElements(env, array, buffer, JNI_ABORT);
     }
    

    jbyteArray is nothing more then a stub type, defined in jni.h:

    struct _jobject;
    typedef struct _jobject *jobject;
    typedef jobject jarray;
    typedef jarray jbyteArray;
    

    It doesn't actually contain any data. It is more or less just a memory adress.

    To get the elements from it we pass it to GetByteArrayElements (since the type was byte[]) which can than ask the VM to retrieve the elements in a C style array. (It might or might not make a copy. See doc)

    The same is done for the array length.

    To tell the VM we're done with the array. We call ReleaseArrayElements.

    Also, jbyte is defined as signed char, so just using the result of GetByteArrayElements as an unsigend char* instead of jbyte* is safe in this case.