A Correct Way to Convert Byte[] in Java to Unsigned Char* in C++, and Vice Versa

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

You can use this to convert unsigned char array into a jbyteArray

jbyteArray as_byte_array(unsigned char* buf, int len) {
jbyteArray array = env->NewByteArray (len);
env->SetByteArrayRegion (array, 0, len, reinterpret_cast<jbyte*>(buf));
return array;
}

to convert the other way around...

unsigned char* as_unsigned_char_array(jbyteArray array) {
int len = env->GetArrayLength (array);
unsigned char* buf = new unsigned char[len];
env->GetByteArrayRegion (array, 0, len, reinterpret_cast<jbyte*>(buf));
return buf;
}

Warning: while trying to convert java byte[] to C unsigned char*

The warning exists because the sign change might be important. In JNI the jbyte corresponds to Java byte which is a signed 8-bit integer; in C it is explicitly signed char.

However, it is OK to access any object with any character pointer, so you can cast to unsigned char explicitly:

unsigned char* flag = (unsigned char*)(*env)->GetByteArrayElements(env, array, NULL);

Alternatively, you can declare flag as signed char:

signed char* flag = (*env)->GetByteArrayElements(env, array, NULL);

This is fine for printf("%c\n", flag[i]); because %c requires that the argument be an integer; the integer is then converted to unsigned char so both signed and unsigned char will do.

However 3rd option would be to use neither - if you just want to write them to the terminal, use a void * pointer and fwrite:

JNIEXPORT void JNICALL 
Java_ImageConversion_covertBytes(JNIEnv *env, jobject obj, jbyteArray array)
{
void *flag = (*env)->GetByteArrayElements(env, array, NULL);
jsize size = (*env)->GetArrayLength(env, array);
fwrite(flag, 1, size, stdout);
}

and let fwrite worry about the looping.

How can I safely use a Java byte as an unsigned char?

You can safely use a byte to represent a value between 0 and 255 if you make sure to bitwise-AND its value with 255 (or 0xFF) before using it in computations. This promotes it to an int, and ensures the promoted value is between 0 and 255.

Otherwise, integer promotion would result in an int value between -128 and 127, using sign extension. -127 as a byte (hex 0x81) would become -127 as an int (hex 0xFFFFFF81).

So you can do this:

long a = (((data[0] & 255) << 24) | ((data[1] & 255) << 16) | ((data[2] & 255) << 8) | (data[3] & 255)) & 0xffffffff;

Note that the first & 255 is unnecessary here, since a later step masks off the extra bits anyway (& 0xffffffff). But it's probably simplest to just always include it.

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

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(file, "%*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);
}

How to convert byte array to string and vice versa?

Your byte array must have some encoding. The encoding cannot be ASCII if you've got negative values. Once you figure that out, you can convert a set of bytes to a String using:

byte[] bytes = {...}
String str = new String(bytes, StandardCharsets.UTF_8); // for UTF-8 encoding

There are a bunch of encodings you can use, look at the supported encodings in the Oracle javadocs.

C: Converting unsigned char array to signed int (vice versa)

You could store both int (or unsigned int) and unsigned char array as union. This method is called type punning and it is fully sanitized by standard since C99 (it was common practice earlier, though). Assuming that sizeof(int) == 4:

#include <stdio.h>

union device_buffer {
int i;
unsigned char c[4];
};

int main(int argv, char* argc[])
{
int original = 1054;

union device_buffer db;
db.i = original;

for (int i = 0; i < 4; i++) {
printf("c[i] = 0x%x\n", db.c[i]);
}
}

Note that values in array are stored due to byte order, i.e. endianess.

Write byte[] passed through jbytearray to a file

Have a look here: A correct way to convert byte[] in java to unsigned char* in C++, and vice versa?. I faced similar problem before and it was the solution.

Convert array of unsigned char (byte) into unsigned short in C++

You are using managed code. Endian-ness is an implementation detail that the framework is aware of:

array<Byte>^ arr = gcnew array<Byte> { 1, 2, 3, 4 };
int value = BitConverter::ToInt16(arr, 1);
System::Diagnostics::Debug::Assert(value == 0x302);

Whether the framework's assumptions are correct depends on where the data came from.



Related Topics



Leave a reply



Submit