Muthu GM Muthu GM - 3 months ago 46
C Question

How to convert the contents of a Java byte array to C string in JNI?




Update:

Error:
jbyte* elements = (*env)->GetByteArrayElements(env, array, NULL);
returns only 8 bytes. Provide any alternative to way to retrieve byte form jbytearray.




I'm new in JNI so I'm not familiar in JNI and also English.

Now I try the simple JNI program on File reading in Java and write it into file using C.

File reading java code:

public class FileIO {
static {
System.loadLibrary("io");
}

private native void writeToFile(byte[] msg);

public static void main(String[] args) throws IOException {
FileInputStream fileInputStream=null;

File file = new File("version1-1.png");

byte[] bFile = new byte[(int) file.length()];

try {
//convert file into array of bytes
fileInputStream = new FileInputStream(file);
fileInputStream.read(bFile);
fileInputStream.close();
System.out.println("Done");

} catch(Exception e){
e.printStackTrace();
}

new FileIO().writeToFile(bFile);
}
}


File write C code:

#include <jni.h>
#include <stdio.h>
#include <string.h>
#include "FileIO.h"

JNIEXPORT void JNICALL Java_FileIO_writeToFile (JNIEnv *env, jobject job, jbyteArray array ){

char * buf ;

// here what i do ??? :(

FILE *file = fopen("test123.png", "w");

int results = fputs(buf, file);
if (results == EOF) {
//Failed to write do error code here.
}
fclose(file);
}


I have tried many solutions (below link) but no luck in writing it to the file. Please provide the correct solution and best JNI tutorial site.

Already tried solution: (But not success)

A correct way to convert byte[] in java to unsigned char* in C++, and vice versa?

Converting jbyteArray to a character array, and then printing to console

How to convert jbyteArray to native char* in jni?

int len = (*env)->GetArrayLength (env , array );
printf(" Length of the bytearray %d\n", len );
unsigned char * string ;
string = (char *)malloc((len + 1) * sizeof(char)) ;

jbyte* b = (*env)->GetByteArrayElements(env, array, &isCopy);


After
GetByteArrayElements
jbyte length should be 8 but the
GetArrayLength
returns bytearray length is 50,335.

What i try :

JNIEXPORT void JNICALL Java_HelloJNI_sayHello (JNIEnv *env, jobject job, jbyteArray array ){

jsize num_bytes = (*env)->GetArrayLength(env, array);
char *buffer = malloc(num_bytes + 1);

printf("Number of jByte element : %d\n", (int) num_bytes);

if (!buffer)
printf("Buff Fail\n");

jbyte* elements = (*env)->GetByteArrayElements(env, array, NULL);
if (!elements)
printf("Element Fail\n");

printf ("Number of Byte elements : %d\n", (int) strlen (elements));

memcpy(buffer, elements, num_bytes);
buffer[num_bytes] = 0;

printf("Number of buffer elements : %d\n", (int) strlen(elements));

(*env)->ReleaseByteArrayElements(env, array, elements, JNI_ABORT);

FILE *fp;
fp = fopen( "file.txt" , "w" );
fwrite(buffer , 1 , sizeof(buffer) , fp );
fclose(fp);
return;
}


AND the Output:

Done
Number of jByte element : 50335
Number of Byte elements : 8
Number of buffer elements : 8

Answer

As @Olaf observed, pointers and arrays are altogether different kinds of objects. There is no meaningful way to convert one to another. The first step, therefore, is to better characterize what you actually mean.

Since your ultimate objective appears to be to write the bytes of the Java array to a file via fputs(), it seems that what you want to do is to create a C string whose contents are the same as the Java byte array's. This is exactly what Olaf's proposed dupe, Converting jbyteArray to a character array, and then printing to console, requests, and what the accepted answer purports to provide. You claim, however, that you already tried that solution and it didn't work. You don't describe the nature of the failure, but I can believe that it does fail, because the solution in the accepted answer is a bit buggy. In what follows, I'll refer to that answer as "the historic answer".

The historic answer is quite correct on several points. In particular, a C string must be null-terminated, and the contents of a Java byte array are not terminated, unless accidentally. Also, the jbytearray pointer is not a pointer directly to the data, so you need to use appropriate JNI functions to get the data themselves. You'll need to create a new buffer large enough for the byte array's contents plus the terminator, copy the bytes into it, and add the terminator.

For example:

// determine the needed length and allocate a buffer for it
jsize num_bytes = GetArrayLength(env, array);
char *buffer = malloc(num_bytes + 1);

if (!buffer) {
    // handle allocation failure ...
}

// obtain the array elements
jbyte* elements = GetByteArrayElements(env, array, NULL);

if (!elements) {
    // handle JNI error ...
}

// copy the array elements into the buffer, and append a terminator
memcpy(buffer, elements, num_bytes);
buffer[num_bytes] = 0;

// Do not forget to release the element array provided by JNI:
ReleaseByteArrayElements(env, array, elements, JNI_ABORT);

After that, you have in the space pointed to by buffer a null-terminated copy of the contents of the byte array, which you can, for example, pass to fputs():

int result = fputs(buffer, file);

Also, once you successfully allocate the buffer, you must be certain to free it before returning from the function, including via any error-handling return path:

free(buffer);

The main problem I see with the historic answer is that it suggests using strlen() to compute the length of the array data so as to be able to allocate a sufficiently large temporary buffer. But that could work only if the byte array were already null terminated, in which case it wouldn't be necessary in the first place.

Update

The above answers the question as posed -- how to convert the data to a C string. Note, however, that the question itself is premised on the supposition that converting the data to a C string and outputting them via fputs() is an appropriate mechanism in the first place. As @Michael observed, that is not the case if the data contain null bytes, as may be difficult to rule out if they originally come from a binary file.

If the overall objective is simply to write the bytes to a file, then first converting them to a C string is pointless. There are alternative mechanisms for outputting the data without first performing such a conversion. If the data can be relied upon not to contain internal null bytes, then you can use fprintf() to write them:

fprintf("%*s", (int) num_bytes, (char *) elements);

On the other hand, if the data may contain nulls then you should use an appropriate low-level output function. That might look like this:

#include <stdio.h>
#include <jni.h>
#include "FileIO.h"

JNIEXPORT void JNICALL Java_FileIO_writeToFile(JNIEnv *env, jobject job,
        jbyteArray array) {
    FILE *fp = fopen( "file.txt" , "w" );

    if (!fp) {
        // handle failure to open the file ...
    }

    // determine the needed length and allocate a buffer for it
    jsize num_bytes = GetArrayLength(env, array);

    // obtain the array elements
    jbyte* elements = GetByteArrayElements(env, array, NULL);

    if (!elements) {
        // handle JNI error ...
    }

    // output the data
    if (fwrite(elements, 1, num_bytes, fp) != num_bytes) {
        // handle I/O error ...
    }

    // Do not forget to release the element array provided by JNI:
    ReleaseByteArrayElements(env, array, elements, JNI_ABORT);

    fclose(fp);
}
Comments