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
Finding Android Sdk on MAC and Adding to Path
Android Studio Run/Debug Configuration Error: Module Not Specified
Android Animate Drop Down/Up View Proper
How to Create Android Project with Gradle from Command Line
Change Fill Color on Vector Asset in Android Studio
When Should I Recycle a Bitmap Using Lrucache
Error: This Android Sdk Requires Android Developer Toolkit Version 22.6.1 or Above
Fast Scroll Display Problem with Listadapter and Sectionindexer
Android - Expandable Textview with Animation
Fragment View in Viewpager Is Not Restored When Resuming
How to Pin a Certificate with Square Okhttp
Best Way to Show a Loading/Progress Indicator