I am new to JNI and trying to muddle my way through. Please can someone point me in the direction to do the following in JNI. I am a bit out of my depth here, Can JNI handle Byte Arrays. Also were is the best places to find JNI examples.
This is the JAVA code I want to convert
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
outputStream.write(key1);
outputStream.write(key2);
byte[] key3 = outputStream.toByteArray();
Thank you in advance
Rob
Here is a literal translation of the Java code you posted:
jclass cls_BAOS = env->FindClass("java/io/ByteArrayOutputStream");
jmethodID ctr_BAOS = env->GetMethodID(cls_BAOS, "<init>", "()V");
jobject baos = env->NewObject(cls_BAOS, ctr_BAOS);
jmethodID mid_BAOS_writeBytes = env->GetMethodID(cls_BAOS, "writeBytes", "([B)V");
env->CallVoidMethod(baos, mid_BAOS_writeBytes, key1);
env->CallVoidMethod(baos, mid_BAOS_writeBytes, key2);
jmethodID mid_BAOS_toByteArray = env->GetMethodID(cls_BAOS, "toByteArray", "()[B");
jbyteArray key3 = (jbytearray)env->CallObjectMethod(baos, mid_BAOS_toByteArray);
And here is an implementation that uses lower-level operations:
jsize key1len = env->GetArrayLength(key1);
jsize key2len = env->GetArrayLength(key2);
jbyteArray key3 = env->NewByteArray(key1+key2);
{
jbyte *key1ptr = env->GetByteArrayElements(key1, nullptr);
env->SetByteArrayRegion(key3, 0, key1len, key1ptr);
env->ReleaseByteArrayElements(key1, key1ptr, JNI_ABORT);
}
{
jbyte *key2ptr = env->GetByteArrayElements(key2, nullptr);
env->SetByteArrayRegion(key3, key1len, key2len, key2ptr);
env->ReleaseByteArrayElements(key2, key2ptr, JNI_ABORT);
}
You could also memcpy both byte arrays to a single C++ array first and then use a single call to SetByteArrayRegion or anything else really, but what's the point? The Java code was simple and readable and all of this JNI code is just adding pointless obfuscation.
Thank you Botje, this is the final code that works.
jclass cls_BAOS = env->FindClass("java/io/ByteArrayOutputStream");
jmethodID ctr_BAOS = env->GetMethodID(cls_BAOS, "<init>", "()V");
jobject baos = env->NewObject(cls_BAOS,ctr_BAOS);
jmethodID mid_BAOS_writeBytes = env->GetMethodID(cls_BAOS, "writeBytes", "([B)V");
env->CallVoidMethod(baos, mid_BAOS_writeBytes, a);
env->CallVoidMethod(baos, mid_BAOS_writeBytes, b);
jmethodID mid_BAOS_toByteArray = env->GetMethodID(cls_BAOS, "toByteArray", "()[B");
jbyteArray key3 = (jbyteArray)env->CallObjectMethod(baos, mid_BAOS_toByteArray);
Related
I'm new to JNI development. What I'm trying to achieve is that I need to capture a snapshot using JNI. I'm able to achieve the same using Kotlin/Java.
I'm using the following code to capture the snapshot and copy it into a bitmap.
jclass bitmapConfig = env->FindClass("android/graphics/Bitmap$Config");
jfieldID rgba8888FieldID = env->GetStaticFieldID(bitmapConfig, "ARGB_8888", "Landroid/graphics/Bitmap$Config;");
jobject rgba8888Obj = env->GetStaticObjectField(bitmapConfig, rgba8888FieldID);
jclass bitmapClass = env->FindClass("android/graphics/Bitmap");
jmethodID createBitmapMethodID = env->GetStaticMethodID(bitmapClass,"createBitmap", "(IILandroid/graphics/Bitmap$Config;)Landroid/graphics/Bitmap;");
jobject bitmapObj = env->CallStaticObjectMethod(bitmapClass, createBitmapMethodID, SNAPSHOT_WIDTH, SNAPSHOT_HEIGHT, rgba8888Obj);
jintArray pixels = env->NewIntArray(SNAPSHOT_WIDTH * SNAPSHOT_HEIGHT);
jmethodID setPixelsMid = env->GetMethodID(bitmapClass, "setPixels", "([IIIIIII)V");
env->CallVoidMethod(bitmapObj, setPixelsMid, pixels, 0, SNAPSHOT_WIDTH, 0, 0, SNAPSHOT_WIDTH, SNAPSHOT_HEIGHT);
Next, I need to update my Kotlin class that this bitmap has been created and pass this bitmap object as a callback. I'm able to pass a string using the Kotlin/JNI callback implementation.
My code for Kotlin/JNI callback ( String )
jclass cls = env->GetObjectClass(clazz);
jmethodID methodid = env->GetMethodID(cls, "RegisterCallback", "(Ljava/lang/String;)V");
jstring jstr = env->NewStringUTF("Hello from C");
env->CallVoidMethod(clazz, methodid, jstr);
Function in the Kotlin side,
fun RegisterCallback(success: String) {
onBitmapReceived?.invoke(success)
}
Now, I would like to know is it possible to pass a Bitmap like this? Or, even better pass a delegate or something.
Thanks in advance.
you can try this :
val image = Robot().createScreenCapture(Rectangle(Toolkit.getDefaultToolkit().screenSize))
enter code herImageIO.write(image, "bmp", File("screenshot.bmp"))
i'm trying to access getPackageManager.getApplicationInfo in jni.
const char* getNativeLibPath(JNIEnv* env, jobject thiz, const char* libraryName, const char* packageName) {
jclass contextClass = env->GetObjectClass(thiz);
jmethodID getPackageManager = env->GetMethodID(contextClass, "getPackageManager", "()Landroid/content/pm/PackageManager;");
jobject instantiatePackageManager = env->CallObjectMethod(thiz, getPackageManager);
jclass packageManagerClass = env->GetObjectClass(instantiatePackageManager);
jmethodID getApplicationInfo = env->GetMethodID(packageManagerClass, "getApplicationInfo", "(Ljava/lang/String;I)Landroid/content/pm/ApplicationInfo;");
jobject instantiateApplicationInfo = env->CallObjectMethod(thiz, getApplicationInfo, packageName, 0);
jclass applicationInfoClass = env->GetObjectClass(instantiateApplicationInfo);
jfieldID nativeLibraryDir = env->GetFieldID(applicationInfoClass, "nativeLibraryDir", "Ljava/lang/String;");
auto string = (jstring) env->GetObjectField(instantiateApplicationInfo, nativeLibraryDir);
const char* returnValue = env->GetStringUTFChars(string, nullptr);
std::string appendedResult = std::string(returnValue) + std::string("/") + std::string(libraryName);
return appendedResult.c_str();
}
This is my code for it. However for some reason i'm getting this error: JNI ERROR (app bug): accessed stale WeakGlobal 0x74eecd21ff (index 1324143135 in a table of size 38) JNI DETECTED ERROR IN APPLICATION: use of deleted weak global reference 0x74eecd21ff
Any help is appreciated!
Your code has at least three problems:
You call getApplicationInfo with a const char * which expects a Java string:
jobject instantiateApplicationInfo = env->CallObjectMethod(instantiatePackageManager, getApplicationInfo, env->NewStringUTF(packageName), 0);
You need to call env->ReleaseStringUTF(returnValue) to release the string on the Java side
You cannot return a const char * like that. Either return the std::string directly, or allocate memory with new char[] and let the caller free it.
I want to add C library to my project. lzfse for decode img via apple algorithm.
I have added c files to project
added CmakeLines file:
externalNativeBuild {
cmake {
path "src/main/lzfse/CMakeLists.txt"
version "3.10.2"
}
}
I have written JNI for decode:
JNIEXPORT jint JNICALL
Java_com_android_Decompressor_decode(
JNIEnv* env, jclass cls, jobject src, jobject dst
) {
uint8_t* src_buffer = (*env)->GetDirectBufferAddress(env,src);
const size_t src_size = (const size_t) (*env)->GetDirectBufferCapacity(env, src);
uint8_t* dst_buffer = (*env)->GetDirectBufferAddress(env,dst);
size_t dst_size = (size_t) (*env)->GetDirectBufferCapacity(env, dst);
jlong test = lzfse_decode_buffer(dst_buffer, dst_size, src_buffer, src_size, NULL);
return (jint) test;
}
then I call from kt decode fun:
val buf = ByteBuffer.wrap(byteArray)
val buf_out = ByteBuffer.allocateDirect(byteArray.size *20)
val size= decode(dstArray = buf_out, srcArray = buf)
But
My app just crashes
2020-02-25 20:12:25.717 28603-28603/com.android A/libc: Fatal signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x0 in tid 28603 (), pid 28603 ()
Where did I lose?
Wrapping a ByteArray does not result in a direct buffer (at least on Android).
You will need to call GetByteArrayElements to get a (possibly copied) pointer.
I have fixed the crash! After some time I have decided to publish the working code.
So, the call native c++ from project:
private val dstArray: ByteBuffer = ByteBuffer.allocateDirect(DESTINATION_BUFFER_CAPACITY)
private val srcArray: ByteBuffer = ByteBuffer.allocateDirect(SRC_BUFFER_CAPACITY)
decode(srcArray.put(byteArray), dstArray)
Here instead of:
val buf = ByteBuffer.wrap(byteArray)
val size= decode(dstArray = buf_out, srcArray = buf)
I allocate a direct buffer via ByteBuffer.allocateDirect and put the source array in buffer srcArray.put(byteArray).
Because as #Botje said in his post :
Wrapping a ByteArray does not result in a direct buffer (at least on
Android).
my JNI:
JNIEXPORT JNICALL
Java_com_android_Decompressor_decode(
JNIEnv *env, jclass cls, jobject src, jobject dst
) {
uint8_t *src_buffer = (*env)->GetDirectBufferAddress(env, src);
const size_t src_size = (const size_t) (*env)->GetDirectBufferCapacity(env, src);
uint8_t *dst_buffer = (*env)->GetDirectBufferAddress(env, dst);
size_t dst_size = (size_t) (*env)->GetDirectBufferCapacity(env, dst);
lzfse_decode_buffer(dst_buffer, dst_size, src_buffer, src_size, NULL);
}
So I almost haven't changed JNI.
But in my first code sample, I have found that all works until the line:
//...
return (jint) test;
The line caused the crash.
In my case, I didn't need return value so I rewrite JNI function without return any values, but I think it possible to find where I have failed with the return value.
Thanks, guys who tried to give some help, I hope my post also can be helpful for someone.
I am not able to cast back the user self defined class return from C++ jni callback
code snippet as follow:
//Kotlin class
data class class_record(var id:String?,var class_name:String?,var class_type)
// com.example.MainActivity
public native Object Cls(String id);
Vector vec_ classrecord=new Vector();
vec_classrecord=(Vector)Cls("1234");
// c++ jni code
extern "C"
JNIEXPORT jobject JNICALL
Java_com_example_MainActivity_Cls(JNIEnv *env, jobject instance, jstring id,
) {
jclass java_vector_class;
jmethodID java_vector_method;
jobject java_vector_object ;
auto vec_record=// call c++ method that return vector for class record pointer
jstring jni_str;
jclass javaClassRef;
// jni for java.util.Vector
java_vector_class = env->FindClass("java/util/Vector");
java_vector_method_constructor = env->GetMethodID(java_vector_class, "<init>", "()V");
java_vector_object = env->NewObject(java_vector_class, java_vector_method_constructor, "");
for (auto record_it = vec_record.begin(); record_it < vec_record.end(); ++record_it) {
// jni for class_record
jclass java_class = env->FindClass("com/example/class_record");
javaClassRef = (jclass) env->NewGlobalRef(java_class);
jmethodID cls_constructor = env->GetMethodID(javaClassRef, "<init>", "()V");
jobject cls_object = env->NewObject(javaClassRef, cls_constructor, "");
// set id
javaMethodRef = env->GetMethodID(javaClassRef, "setId", "(Ljava/lang/String;)V");
std::string strval = record_it.id;
jni_str = env->NewStringUTF(strval.c_str());
env->CallVoidMethod(cls_object, javaMethodRef, jni_str);
// set class_name
javaMethodRef = env->GetMethodID(javaClassRef, "setClass_name","(Ljava/lang/String;)V");
std::string strval = record_it.class_name;
jni_str = env->NewStringUTF(strval.c_str());
env->CallVoidMethod(cls_object, javaMethodRef, jni_str);
//set class_type
javaMethodRef = env->GetMethodID(javaClassRef, "setClass_type","(Ljava/lang/String;)V");
std::string strval = record_it.class_type;
jni_str = env->NewStringUTF(strval.c_str());
env->CallVoidMethod(cls_object, javaMethodRef, jni_str);
jmethodID java_vector_add = env->GetMethodID(java_vector_class, "addElement","(Ljava/lang/Object;)V");
**env->CallVoidMethod(java_vector_object, java_vector_add, javaClassRef);**
}
return java_vector_object;
}
env->CallVoidMethod(java_vector_object, java_vector_add, cls_object);
Under Kotlin environment , it it much better to express jni c++ callback in ArrayList instead of vector which grow almost double the size with snippet as follow in situation where the returned arraylist is fixed size immuatable.
java_util_class = env->FindClass("java/util/ArrayList");
jmethodID java_add= env->GetMethodID(java_util_class, "add","(Ljava/lang/Object;)Z");
env->CallBooleanMethod(java_object, java_add, cls_object);
sample tutorial
I am new to NDK and learning.
I managed to call the native method from java code but don't know how can I compare two jbyte arrays
here is what I have:
jbyte bytes1[] = {48, -126, 1,4};
jbyte bytes2[] = {48, -126, 1,4};
jclass cls = (*env)->GetObjectClass(env, bytes1);
jmethodID mid = (*env)->GetMethodID(env, cls, "equals", "([B)Z");
jboolean isEqual = (*env)->CallBooleanMethod(env, bytes1, mid, bytes2);
I know above code is wrong but is there a correct way of comparing the byte arrays
Use memcmp if you need to compare two jbyte arrays.
#include <string.h>
int memcmp(const void *s1, const void *s2, size_t n);
In your example,
jboolean isEqual = (memcmp(bytes1, bytes2, 4) == 0);