How to set IntField from native jni to Java Android 10 - android

After update my project to support Android 10, a crash produced in the JNI level related to non-SDK interface restrictions in Android 10 :
JNI DETECTED ERROR IN APPLICATION: JNI SetIntField called with pending exception java.lang.NoSuchFieldError: no "I" field "value" in class "Ljava/lang/Integer;" or its superclasses
which come from this part of code :
jclass clazz = (*env)->GetObjectClass(env, outputObj);
jfieldID mi = (*env)->GetFieldID(env, clazz, "value", "I");
(*env)->SetIntField(env, outputObj, mi, pListLen);
To fix that i replace it by :
jclass clazz = (*env)->GetObjectClass(env, outputObj);
jmethodID intValueMethod = (*env)->GetMethodID(env, clazz, "intValue", "()I");
jint result = (*env)->CallIntMethod(env, outputObj,intValueMethod,pListLen);
After that my application doesn't crash and find the integer value correctly, but i want to set the result integer on Java code using the same method SetIntField.
Could you please give me a way or method to set the result on the Java part.

As changing the intger field is not a good practice on JNI part, so i changed the way instead changing it by reflection method
jclass clazz = (*env)->GetObjectClass(env, outputBufLen);
jmethodID value_of = (*env)->GetStaticMethodID(env,clazz, "valueOf", "(I)Ljava/lang/Integer;");
jobject result = (*env)->CallStaticObjectMethod(env,clazz, value_of, pOutBufLen);
I set the result under an int Array and i return it :
int result = (*env)->CallStaticObjectMethod(env,clazz, value_of, pOutBufLen);
jintArray resultArray = (*env)->NewIntArray(env, 2);
jint fill[2];
fill[0] = result;
fill[1] = (jint) pOutBufLen;
(*env)->SetIntArrayRegion(env, result, 0, 2, fill);
return (resultArray);

Related

How to pass an object or delegate using JNI/Kotlin callback?

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"))

How to access Pair<> of Java in JNI?

I have ArrayList<Pair<Pair<Float, Float>, Pair<Float, Float>>> on Java side and want to use this data in JNI.
What methods and classes to use to convert to std::pair<std::pair<float, float>, std::pair<float, float>>
I tried following
jclass pairClass = env->FindClass("android/util/Pair");
jfieldID pairGetKey = env->GetFieldID(pairClass, "first", "java/util/Objects");
pairGetKey is always null
The type of the field has to be given as a signature; that is, you need to use the int -> I, T[] -> [T, reference.Type -> Lreference/Type; encoding. Also, the type of the field is java.lang.Object, not java.util.Objects.
jfieldID first = env->GetFieldID(pairClass, "first", "Ljava/lang/Object;");
jfieldID second = env->GetFieldID(pairClass, "second", "Ljava/lang/Object;");
The rest is tedious, but not hard:
jfloat extract_float(JNIEnv *env, jobject f) {
// Note the syntax of signatures: float floatValue() has signature "()F"
return env->CallFloatMethod(f,
env->GetMethodID(env->FindClass("java/lang/Float"), "floatValue", "()F"));
}
std::pair<jobject, jobject> extract_pair(JNIEnv *env, jobject p) {
jclass pairClass = env->FindClass("android/util/Pair");
jfieldID first = env->GetFieldID(pairClass, "first", "Ljava/lang/Object;");
jfieldID second = env->GetFieldID(pairClass, "second", "Ljava/lang/Object;");
return std::pair(env->GetObjectField(p, first), env->GetObjectField(p, second));
}
JNIEnv *env;
jobject pair;
auto [f1, f2] = extract_pair(env, pair);
auto [f11, f12] = extract_pair(env, f1);
auto [f21, f22] = extract_pair(env, f2);
std::pair p(
std::pair(extract_float(env, f11), extract_float(env, f12)),
std::pair(extract_float(env, f21), extract_float(env, f22)));
Though, I think I must ask, do you actually need to do this? Can you preprocess the nested pairs to something nicer on the Java side? Doing it on this side is ugly.

java lang class exception -Unable to cast the java vector<user defined java class> return from c++ jni object callback

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

Instantiate Java class from JNI

I need to create an instance of a Java class in my native code. To do it, I am using the following C code:
jobject Java_com_mypackage__myClass_myMethod(JNIEnv* env, jobject thiz, jint index){
int fd = pDevs[index].ufds.fd; // fd = open(....); it's a input/eventX file.
jclass class = (*env)->FindClass(env,"com/mypackage/ClassName");
jmethodID mid = (*env)->GetMethodID(env,class,"<init>","(Ljava/lang/String;)V");
return (*env)->NewObject(env,class,mid,(*env)->NewStringUTF(env, pDevs[index].device_path));
}
But when I invoke myMethod, I keep getting fatal signal 11 (SIGSEGV). Is the code wrong?
You should use logging/debbuger to find place where segmentation fault happenned. The easiest way is to use android logging system as described
here
jclass class = (*env)->FindClass(env,"com/mypackage/ClassName");
if(class == null)
{
__android_log_print(ANDROID_LOG_VERBOSE, "TAG", "class is null");
}
For example if ClassName is an inner class of some activity you should use com/mypackage/ActivityName#ClassName instead of com/mypackage/ClassName. But I can only guess before you provide your logs.

Android NDK crash in CallObjectMethod calling getSystemService

This is a follow-up to another question I asked: Android -- get MEID from JNI
I am trying to get the ID of a phone in Android. I have some JNI code and a simple test app to call the JNI code. Here is working Java code from my simple test app:
TelephonyManager tm = (TelephonyManager)this.getSystemService(Context.TELEPHONY_SERVICE);
String id = tm.getDeviceId();
The string id is set to the phone ID value that I want. But I need to get it from JNI, and just using the above code and passing the ID value in is not an acceptable solution. (This is to make some JNI code somewhat tamper-proof and I shouldn't trust the Java layer to send a correct ID value.)
Here is the JNI code that I have written, with error handling removed so it is easier to follow. It works until the indicated line, and then the whole app crashes.
// "env" and "obj" are passed to a JNI function and are used unmodified in this code
// JNIEnv *env, jobject obj
jclass cls_context = NULL;
jclass cls_tm = NULL;
jobject tm = NULL;
jmethodID mid;
jfieldID fid;
jstring jstr;
jsize len_jstr;
cls_context = (*env)->FindClass(env, "android/content/Context");
fid = (*env)->GetStaticFieldID(env, cls_context, "TELEPHONY_SERVICE",
"Ljava/lang/String;");
jstr = (*env)->GetStaticObjectField(env, cls_context, fid);
mid = (*env)->GetMethodID(env, cls_context, "getSystemService",
"(Ljava/lang/String;)Ljava/lang/Object;");
tm = (*env)->CallObjectMethod(env, obj, mid, jstr); // THIS LINE CRASHES
cls_tm = (*env)->FindClass(env, "android/telephony/TelephonyManager");
mid = (*env)->GetMethodID(env, cls_tm, "getDeviceId",
"()Ljava/lang/String;");
jstr = (*env)->CallObjectMethod(env, tm, mid);
len_jstr = (*env)->GetStringUTFLength(env, jstr);
(*env)->GetStringUTFRegion(env, jstr, 0, len_jstr, buf_devid);
I think the problem is that obj isn't the right thing to pass, but if so I have no idea what is the right thing. Isn't obj that gets passed to the JNI function the same thing as this in the Java code?
EDIT: Okay, we have figured out that if we add an extra argument of type jobject to the JNI function, and explicitly pass a copy of this in that argument, and then pass that to CallObjectMethod() (the one that crashes in the above code), everything works. We get our TelephonyManager instance and we can query the Phone ID value.
Using a logging macro, I logged the obj pointer and the passed-in this pointer. They are similar numbers (addresses close to each other) but not identical. So I think obj is some sort of object reference from inside the Java VM... it is not actually the same as this.
In a JNI function, the first two arguments are JNIEnv *env and jobject obj. What is that second one for? What can I do with it? Is there any way I can use it to call getSystemService or will I have to pass an extra argument and pass in this?
The problem seems to be related to Java inheritance: in Java, you can call this.getSystemService() and it works, even though this is not actually an instance of Context. When you make the JNI call, the call simply fails.
So our solution was to have our Android app add a .getApplicationContext() method function actually as part of its own class. This in turn calls the actual getSystemService() and returns the result.
The code didn't change: we are still calling
mid = (*env)->GetMethodID(env, cls_context, "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;");
but now it works, when there is a .getSystemService() method in the class that is calling our JNI function. So, the env argument in a JNI call does represent this (it's not identical... I printed the value of this as a pointer, and printed env, and they are not the same but they are definitely related).
If you want to call a superclass method on object, you should use CallNonvirtualObjectMethod()
How to call an overriden method in JNI
Try this... It works... "context" came from java :)
jclass ctx = env->FindClass("android/content/Context");
jfieldID fid = env->GetStaticFieldID(ctx,"TELEPHONY_SERVICE","Ljava/lang/String;");
jstring str = (jstring) env->GetStaticObjectField(ctx, fid);
jmethodID mid = env->GetMethodID(ctx, "getSystemService","(Ljava/lang/String;)Ljava/lang/Object;");
jobject tm = env->CallObjectMethod(context, mid, str);
jclass ctx_tm = env->FindClass("android/telephony/TelephonyManager");
jmethodID mid_tm = env->GetMethodID(ctx_tm,"getDeviceId","()Ljava/lang/String;");
jstring str_tm = (jstring) env->CallObjectMethod(tm, mid_tm);
strReturn = env->GetStringUTFChars(str_tm, 0);

Categories

Resources