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?
Related
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().
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 am trying to pass char* from C++ to java using JNI in android.
I have tried number of ways to pass that data
1) Using NewStringUTF:
const char* data = getData(); // this method returns a char array.
env->NewStringUTF(data);
Executing above code throws below error
JNI WARNING: input is not valid Modified UTF-8: illegal continuation byte 0x70.
2) Using NewString:
const char* data = getData(); // this method returns a char array.
// passing a byte array to java
jbyteArray trackIDArray = env->NewByteArray(strlen(data));
env->SetByteArrayRegion(trackIDArray, 0, strlen(data), (const jbyte*)trackID);
On java side, I am getting some garbage value. I don't understand how to get this char array to Java.
1) your data is simply not a valid UTF-8 string. Not every char array is automatically a valid UTF-8. You probably have it in some single-byte encoding (like ISO or Windows CP), or it's not a readable string at all.
2) should be ok, but show the code which fills trackID from data. The fact that you need to hard typecast it to jbyte* is suspicious. This code might be correct, but you can make a mistake on Java side too:
If data is not a readable string or is in single-byte encoding which is not "platform's default charset" java.lang.String(byte[]) constructor won't be able to make a readable string out of it! In that case, you must convert to UTF-8 on C side. You will also release yourself from the dependency on platform specific encoding (which may be wildly different).
I would suspect data instead of trackID.
env->SetByteArrayRegion(trackIDArray, 0, strlen(data), (const jbyte*)data);
Then you have the bytes and on the java side may look what encoding it is - by a hex dump or other inspection.
Later:
String s = new String(data, "Cp1252"); // Or so.
NewStringUTF expects you to pass a Modified UTF-8 string.
You are likely trying to pass UTF-8.
There are multiple ways to fix it:
Most obvious one is to encode the string to UTF-8 modified in C++ before passing it to Java.
Another way is to pass it to Java as a byte array and use String constructor to convert it from UTF-16.
The second way might be more efficient as in the end Java uses UTF-16 for string representation.
As an alternative approach, you could convert the string to UTF-16 in C++ and pass it to newString JNI function which expects UTF-16.
I put very big bytesources (>2kbyte) behind the JNI like this :
Content of a csv-table:
R"xxx(tbl_Cbla,Column 02,Column 03,Column 04
sdfsdsad,sdfasd,dsfaddf,fdsasdf,fafasa
18,1,10,8,0)xxx"`
std::string data1 =
#include "big_table1.csv"
;
std::string data2 =
#include "big_table2.csv"
;
extern "C" JNIEXPORT jbyteArray JNICALL
Java_com_example_bigData_rawResource(
JNIEnv *env,
jobject /* this */, jint index) {
std::string values;
switch (index) {
case 0: {values = data1;break;}
case 1: {values = data2;break;}
}
int byteCount = values.length();
jbyteArray ret = env->NewByteArray(byteCount);
const jbyte* pNativeMessage = reinterpret_cast<const jbyte*>(values.c_str());
env->SetByteArrayRegion (ret, 0, byteCount, pNativeMessage);
return ret;
}
In Java you can get it back like this, to import the native function is up to you:
ByteArrayInputStream bis = null;
try {
bis = new ByteArrayInputStream(rawResource(1);
} catch (Exception e) {
e.printStackTrace();
}
BufferedReader buffer = new BufferedReader(new InputStreamReader(bis, Charset.forName("UTF-8")));
To to handle the buffered reader is also up to you, small exg.:
Strig line = buffer.readLine();
while ((line = buffer.readLine()) != null) {
//play around with 'line'
}
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