I am developing an Android application for which I am using one native C library. This library is used for file decompression.
The definition of the native method is as follows:
public static native Long decompress(ByteBuffer src, ByteBuffer dst);
My application code to use this function is as follows:
try {
File inFile = new File(Environment.getExternalStorageDirectory().getPath() +"/1"); // source file
ByteBuffer buf = ByteBuffer.allocateDirect((int)inFile.length());
ByteBuffer buf_out = ByteBuffer.allocateDirect((int)inFile.length()*20);
InputStream is = new FileInputStream(inFile);
int b;
while ((b=is.read())!=-1) {
buf.put((byte)b);
}
Log.d("nims","source buffer zie"+buf.position());
File file = new File(Environment.getExternalStorageDirectory().getPath() +"/2");
// append or overwrite the file
boolean append = false;
FileChannel channel = new FileOutputStream(file, append).getChannel();
Long t = decompress(buf,buf_out); // t is some XYZ number
Log.d("nims","dest buffer zie"+buf_out.position()); // buf_out.position() returns 0
buf_out.flip();
// Writes a sequence of bytes to this channel from the given buffer.
channel.write(buf_out);
// close the channel
channel.close();
}
catch (IOException e) {
System.out.println("I/O Error: " + e.getMessage());
}
JNI code:
JNIEXPORT jlong JNICALL
Java_com_company_appName_MainActivity_decompress(JNIEnv* env, jclass cls, jobject src, jobject dst) {
uint8_t* src_buffer = (*env)->GetDirectBufferAddress(env,src);
const size_t src_size = (*env)->GetDirectBufferCapacity(env, src);
uint8_t* dst_buffer = (*env)->GetDirectBufferAddress(env,dst);
size_t dst_size = (*env)->GetDirectBufferCapacity(env, dst);
jlong test = _decode_buffer(dst_buffer, dst_size, src_buffer, src_size, NULL);
return test;
}
The destination buffer doesn't contain any data.
I have following questions:
How can I read the destination bytebuffer back from JNI code to application's code?
How can I write the destination bytebuffer data to a file?
I would appreciate any suggestion and thoughts on this topic.
EDIT 1:
size_t test = lzfse_decode_buffer(dst_buffer, dst_size, src_buffer, src_size, NULL);
ALOG("Size of test %d.",test); // output is 319488
ALOG("Size of test after casting %ld.",(jlong)test); // output is -125648933
//New code from stack overflow
jclass cls2 = (*env)->GetObjectClass(env, dst);
jmethodID limitId = (*env)->GetMethodID(env, cls2, "limit", "(I)Ljava/nio/Buffer;");
(*env)->CallObjectMethod(env, dst, limitId,(jlong) test);
jmethodID positionId = (*env)->GetMethodID(env, cls2, "position", "(I)Ljava/nio/Buffer;");
(*env)->CallObjectMethod(env, dst, positionId, 0);
EDIT 2:
size_t test = lzfse_decode_buffer(dst_buffer, dst_size, src_buffer, src_size, NULL);
ALOG("Size of test %d.",test); // output is 319488
ALOG("Size of test after casting %d.",(jint)test); // output is 319488
//New code from stack overflow
jclass cls2 = (*env)->GetObjectClass(env, dst);
jmethodID limitId = (*env)->GetMethodID(env, cls2, "limit", "(I)Ljava/nio/Buffer;");
(*env)->CallObjectMethod(env, dst, limitId,(jint) test);
jmethodID positionId = (*env)->GetMethodID(env, cls2, "position", "(I)Ljava/nio/Buffer;");
(*env)->CallObjectMethod(env, dst, positionId, 0);
When you write to DirectByteBuffer in C, this does not change the size (limit) and position
of the dst buffer. You must set these yourself. You can do it in native code:
jclass cls = (*env)->GetObjectClass(env, dst);
jmethodID limitId = (*env)->GetMethodID(env, cls, "limit", "(I)Ljava/nio/Buffer;");
(*env)->CallObjectMethod(env, dst, limitId, actual_size);
jmethodID positionId = (*env)->GetMethodID(env, cls, "position", "(I)Ljava/nio/Buffer;");
(*env)->CallObjectMethod(env, dst, positionId, 0);
This assumes that you can retrieve the actual number of bytes written to dst buffer from the _decode_buffer() function.
And you don't need flip().
Related
i am getting java.lang.Byte[] from c(jbyteArray in JNI) . How to convert this jbyteArray to string in java.
c function is
jbyteArray Java_eu_ratikal_helloc_MainActivity_getNameBytes(JNIEnv* env, jobject thiz) {
int n=0;
jstring na ="HelloAndroidString";
char* p = "HelloAndroidString";
while(*p++){
n++;
}
jbyteArray arr = (*env)->NewByteArray(env, n);
(*env)->SetByteArrayRegion(env,arr,0,n, (jbyte*)na);
char* b = (char*)arr;
return arr;
}
java code is
byte[] bytes = getNameBytes();
Getting compilation error like
Incompatible types
Required byte[]
Found java.lang.Byte[]
You need to return
public native byte[] getNamBytes();
but to convert it into a String you need to use new String e.g.
public String String getName() {
return new String(getNameBytes(), "UTF-8");
}
I'm working on a Android app that acts as TCP server/client i want to send data from the app to a server/client that is written in c/c++(made with the boost libraray). I have a normal Java function that calls a native c function for string converstion to bytes:
The function is defined as followed (the native function is Convert String:
// Send buffer, the method can be used by both client and server objects.
public void SendBuffer(String Buffer){
try {
// Convert char to string to byte
byte[] Temp = new byte[10];
String Teststring = "AAAAAAAABB";
Temp = ConvertString(Teststring);
//byte[] Temp = new String(Buffer).getBytes();
// Get socket output stream
OutputStream OutputBuffer = ClientSocket.getOutputStream();
//Write byte data to outputstream
OutputBuffer.write(Temp);
// Neatly flush and close the outputbuffer
OutputBuffer.flush();
OutputBuffer.close();
}
catch (IOException e) {
Log.e("TCPIPCommunicator: ", "Client: Failed to send", e);
e.printStackTrace();
}
}
The function ConvertString is a native function that converts the Java string to a C/C++ string and returns it as Java bytes, it is defined as followed:
JNIEXPORT jbyteArray JNICALL Java_com_example_communicationmoduleTCPIP_communicationmoduleTCPIP_ConvertString(
JNIEnv * env, jobject,
jstring Buffer)
{
// Array to fill with data
jbyteArray Array;
// Init java byte array
Array = env->NewByteArray(10);
const char* NewBuffer = env->GetStringUTFChars(Buffer, 0);
// Set byte array region with the size of the SendData CommStruct.
// Now we can send the data back.
env->SetByteArrayRegion(Array, 0, 10, (jbyte*)NewBuffer);
env->ReleaseStringUTFChars(Buffer, NewBuffer);
// Return java array
return Array;
}
}
When i run the program i get two 'AAAA' on the c side but not as a whole array ( so no 'AAAAAAAADD). I think the problem is that the server sends 2 'AAAA' and not the whole array at once. The client crashes with the following error:
'boost::exception_detail::clone_impl >'
what(): read: End of file
Does the java server sends the data wrong? can anyone give me a suggestion? all feedback is welcome!
Are you willing to send the data or to make the JNI stuff work?
In the former case, use Java to convert a string into UTF-8 (which will be ASCII for English.)
Conversion of text byte[] -> byte[] is not exactly what you need, but you'll get the idea:
//byte[] result;
//byte[] source;
String s = new String(source,"UTF-8");
result = s.getBytes("UTF-16LE");
For the 2nd case, I can share a portion of working code; it calls Java to convert from one encoding to another
// it returns NULL in the case of an exception
// the returned memory is calloc()'d; it's the caller's responsibility to free() it.
char* changeEncoding(const char*source, int len, int direction)
{
JNIEnv* env = threadUnsafeInfo.env;
jobject obj = threadUnsafeInfo.obj;
if (!source) {
JNU_ThrowByName(env, "java/lang/NullPointerException", 0);
return NULL;
}
jbyteArray srcArray = env->NewByteArray(len);
jclass cls = env->FindClass("com/xyz/MyClass");
jmethodID mid = env->GetMethodID(cls, "convert", "([BI)[B");
if (mid != NULL && srcArray != NULL) {
env->SetByteArrayRegion(srcArray, 0, len, (jbyte*)source);
env->ExceptionClear();
//jbyteArray resArray = (jbyteArray)env->CallStaticObjectMethod(cls, mid, srcArray, direction);
jbyteArray resArray = (jbyteArray)env->CallObjectMethod(obj, mid, srcArray, direction);
if(env->ExceptionOccurred()) {
DLOG("exception in convert ([BI)[B");
env->ExceptionDescribe();
//env->ExceptionClear(); // ??
return NULL;
}
int resultLen = env->GetArrayLength(resArray);
char* result = (char*)calloc(2 + resultLen,1); // why 2: a bit of healthy paranoia ain't gonna hurt anyone
if (result == 0) {
JNU_ThrowByName(env, "java/lang/OutOfMemoryError", 0);
return NULL;
}
env->GetByteArrayRegion(resArray, 0, resultLen, (jbyte *)result);
env->DeleteLocalRef(cls);
env->DeleteLocalRef(resArray);
env->DeleteLocalRef(srcArray);
return result;
} else {
JNU_ThrowByName(env, "java/lang/NullPointerException", 0);
myassert(("method id = 0",0));
}
return NULL;
}
In the code that I have at hand
I did not use jstrings, preferring the byte arrays.
I have a Java method that process a bitmap and returns a String.
When I call this method from JNI (VS 2010) it works, but if I call this method many times,
the memory of the process grown up until crash.
The instruction that use a lot of memory is:
jbyteArray jBuff = _env->NewByteArray(b->Length);
My code:
static jobject staticArray=0;
System::String^ MyClass::ExecuteJavaMethod(System::Drawing::Bitmap^ bmp)
{
JNIEnv *_env;
System::String^ out;
unsigned const char * buff;
int res = jvm->AttachCurrentThread((void **)&_env, NULL);
if (jvm->GetEnv((void**) &_env, JNI_VERSION_1_6) != JNI_OK)
{
return "GetEnv ERROR";
}
//save the bitmap in the stream
MemoryStream^ ms = gcnew MemoryStream();
bmp->Save(ms, ImageFormat::Bmp);
//get the bitmap buffer
array<unsigned char>^b = ms->GetBuffer() ;
//unmanaged conversion
buff = GetUnmanaged(b,b->Length);
//fill the buffer
jbyteArray jBuff = _env->NewByteArray(b->Length);
_env->SetByteArrayRegion(jBuff, 0, b->Length, (jbyte*) buff);
//call the java method
jstring str = (jstring) _env->CallStaticObjectMethod ( Main,
javaMethod,
jBuff);
// _env->ReleaseByteArrayElements(jBuff,(jbyte*)buff), 0); //NOT WORKING
//staticArray= _env->NewGlobalRef(jBuff); NOT
//_env->DeleteLocalRef(jBuff); WORKING
//return the string result of the java method
return gcnew String(env->GetStringUTFChars(str, 0));
}
the answer is: _env->DeleteLocalRef(jBuff);
You didn't call DetachCurrentThread() for each AttachCurrentThread(), which is requested in Java Native Interface Specification. That makes the local references (jBuff and str) unable to be freed automatically. Also, the const char* fetched through GetStringUTFChars() needs to be released.
The correct way is change
return gcnew String(env->GetStringUTFChars(str, 0));
into
const char* cstr = env->GetStringUTFChars(str, 0);
System::String^ retstr = gcnew String(cstr);
env->ReleaseStringUTFChars(str, cstr);
jvm->DetachCurrentThread();
return retstr;
I want to send Hangul (Korean symbols using UTF) text from C to JAVA.
But JAVA gets incorrect results
Example:
JNIEXPORT jstring JNICALL Java_get_1term
(JNIEnv *env, jobject, jint termInd)
{
const char *str = "음뮤원음직음원샘";
return (env)->NewStringUTF(str);
}
but in that case JAVA got incorrect string.
I couldn't find answer to my question in other posts, if there is same question please give me link to them
try with this (taken and adapted from here):
jstring WindowsToJstring(JNIEnv* pEnv, char* cstr) {
jstring retJstring = NULL;
int slen = strlen(cstr);
int length = MultiByteToWideChar( CP_ACP, 0, (LPCSTR)cstr, slen, NULL, 0 );
unsigned short* tempbuffer = (unsigned short *)malloc( length*2 + 1 );
MultiByteToWideChar( CP_ACP, 0, (LPCSTR)cstr, slen, (LPWSTR)tempbuffer, length );
retJstring = (pEnv)->NewString((jchar*)tempbuffer, length );
free( tempbuffer );
return retJstring;
}
EDIT As correctly noted by #Kerrek SB this is a Windows only solution
HANDLE hFile = CreateFileA("C:\\myfile.zip", GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
const int size = GetFileSize(hFile, NULL);
char* buffer = new char[size];
DWORD read;
ReadFile(hFile, buffer, size, &read, NULL);
jclass cls = ...;
jmethodID id = ...;
jbyteArray arr = env->NewByteArray(size);
env->GetByteArrayRegion(arr, 0, size, (jbyte*) buffer);
env->CallStaticVoidMethod(cls, id, arr);
problem is that byte array contains just null bytes in java side, does anyone have idea why?
EDIT: oh my bad it should be SetByteArrayRegion, sorry! all working now :)
I think your missing a line like :
(*env)-> SetByteArrayRegion(env, result, 0, size, fill);
check out :
http://java.sun.com/developer/onlineTraining/Programming/JDCBook/jnistring.html
for more details
also a similar question was answered here
How to return an array from JNI to Java?