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);
Related
I've seen plenty of questions about exactly the same error, but none of them seems to be trying to do this simple thing and still fail.
I have in my class header, as private members:
static JNIEnv* env;
static jclass copterServiceClass;
static jmethodID mavlinkMsgMethod;
Then in the source for that class:
JNIEnv* JU_Calls::env = 0;
jclass JU_Calls::copterServiceClass = 0;
jmethodID JU_Calls::mavlinkMsgMethod = 0;
bool JU_Calls::setupJNICalls() {
if (cached_jvm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
__android_log_print(ANDROID_LOG_ERROR, TAG, "Unable to get Java Env from cached JavaVM");
return -1;
}
jclass dataClass = env->FindClass("eu/deye/copterdroidair/copterdroidair/Services/CopterService");
copterServiceClass = (jclass) env->NewGlobalRef(dataClass);
mavlinkMsgMethod = env->GetMethodID(copterServiceClass, "MavlinkMsg", "(Ljava/lang/String;)V");
jobject javaObjectRef = env->NewObject(copterServiceClass, mavlinkMsgMethod);
jstring msg = env->NewStringUTF("aaaa");
env->CallVoidMethod(javaObjectRef, mavlinkMsgMethod, msg);
return true;
}
Note: cached_jvm is assigned at JNI_OnLoad.
It fails with the aforementioned error when executing NewObject.
Invalid indirect reference 0x416f68a0 in decodeIndirectRef
I tried passing NULL to the CallVoidMethod as the message, as seen on other's questions, but as it's logical, the problem is before, so useless.
Hope you can help me, as always, great SO masters ;)
EDIT: While I think Bangyno answer is the right one, what I've eventually done to solve the problem as quickest as possible, was to declare the Java Methods that would be called from C++ as static. That way I don't have to call the constructor, and everything makes a lot more sense. Because the Java class to which I was calling, was an Android Service, thus calling the constructor was erroneous for sure.
Apart from declaring the Java method static, the resulting C++ code results as follows:
jclass copterServiceClass = env->FindClass("eu/deye/copterdroidair/copterdroidair/Services/CopterService");
jmethodID mavlinkMsgMethod = env->GetStaticMethodID(copterServiceClass, "MavlinkMsg", "(ILjava/lang/String;)V");
jstring msg = env->NewStringUTF(str);
env->CallStaticVoidMethod(copterServiceClass, mavlinkMsgMethod, severity, msg);
env->DeleteLocalRef(msg);
Is very important not to forget the last line, because otherwise it will fill your JNI table and crash.
Here I want to discuss is the error message:
Invalid indirect reference 0x416f68a0 in decodeIndirectRef
It means you give the wrong argument, the second one "mavlinkMsgMethod".
In my experience, if you change "mavlinkMsgMethod" to a number such as "5", the 0x416f68a0 will change to 0x5.
The right way to use newObject is use to invoke the constructor. It should look like this, just a sample:
jclass dataClass = env->FindClass("eu/deye/copterdroidair/copterdroidair/Services/CopterService");
mavlinkMsgMethod = env->GetMethodID(copterServiceClass, "<init>", "(Ljava/lang/String;)V");
jstring str = env->NewStringUTF("testing");
jobject javaObjectRef = env->NewObject(copterServiceClass, mavlinkMsgMethod, str);
I have a JNI function in C which is getting passed in a Java FILE class which represents a directory listing. I would like to call the list() function and get the list of strings (files in the directory). What is the best way to do this?
Right now I have
static void* my_function(JNIEnv *env, jobject obj, jobject dir){
jarray listRet;
jclass cls = (*env)->GetObjectClass(env, dir);
jmethodID method = (*env)->GetMethodID(env, cls, "list", "()[Ljava/lang/String");
listRet = (*env)->CallObjectMethod(env, cls, method);
jsize stringCount = (*env)->GetArrayLength(env, listRet);
}
However, by adding logging statements, it seems that it never gets past the GetObjectClass call.
So, is this call correct? Further, is the GetMethodID call correct? The return type of list() is a (java) String[].
Is there anywhere else that I'm going wrong?
list is not a static method of File. That is, it belongs to an instance of File (dir in your case), not to the File class.
So instead of:
listRet = (*env)->CallObjectMethod(env, cls, method);
you should be using:
listRet = (*env)->CallObjectMethod(env, dir, method);
Also, you seem to be missing a semicolon in the signature for list. It should be "()[Ljava/lang/String;"
I want to know how to get a custom object from Java to c++?
I need to implement a method in c++ for get performance. I already have the method working in java but I want to port to c++.
On Java a I call the method like this:
private native boolean P(Mat Previous, String Name);
On CPP file I need to get the mat object. Getting string is easy! But how can I get the custom mat object similar to c++(cv::Mat)? I need to get java Mat into a cv::Mat.
Here the cpp file:
JNIEXPORT bool JNICALL Java_br_raphael_detector_SimpsonDetector_P
(JNIEnv* env,jobject thiz, jobject Previous, jstring Name){
jboolean sim = false;
const char* N = env->GetStringUTFChars(Name,0);
std::string Nome = N;
//Release
env->ReleaseStringUTFChars(Name,N);
//Then Return
return sim;
}
A java Mat object is a totally different thing from a native cv::Mat, you can't get one directly from the other.
That said, if you know what fields are inside Mat, and you know the corresponding fields in cv::Mat, you can write a conversion function that copies the contents of the fields one-by-one.
// First get the Mat class
jclass Mat = (*env)->GetObjectClass(env, Previous);
// To get a field
jfieldId field = (*env)->GetFieldID(env, Mat, "fieldName", field type);
// To get a method
jmethodId method = (*env)->GetMethodID(env, Mat, "methodName", method signature);
from there you can read the values of fields, or call methods
// getting a field
(*env)->GetObjectField(env, Previous, field);
// calling a method
(*env)->CallObjectMethod(env, Previous, method, parameters);
refer to http://docs.oracle.com/javase/1.5.0/docs/guide/jni/spec/functions.html for details
I'm one year late to the party, but the way you pass a Mat to C is with a jlong with the address of the Java's Mat. You can do something like this:
private static native boolean P(long matAddress, String name);
In C:
JNIEXPORT jboolean JNICALL Java_br_raphael_detector_SimpsonDetector_P
(JNIEnv* env,jobject thiz, jlong matAddress, jstring Name)
{
cv::Mat* image = (cv::Mat*)matAddress;
// Do stuff with image.
}
Then you would call the method in Java like this:
P(myMat.getNativeObjAddr(), name);
OpenCV exposes this method just for cases like this. (I would link to the documentation's page but the website is not loading here, sorry.)
I want to call the Java method from Jni code with the int and int[] arguments. for that i have Go-ogled and found the following sample .
Calling a java method from c++ in Android
And it worked fine with String parameter . But While trying with int i got issues .
Please help me .
JNI CODE:
jstring Java_com_calljavafromjni_MainActivity_getJniString( JNIEnv* env, jobject obj){
jstring jstr = (*env)->NewStringUTF(env, "RAJESH TEST from JNI ");
jint sss=1111;
jclass clazz = (*env)->FindClass(env, "com/calljavafromjni/MainActivity");
jmethodID messageMe = (*env)->GetMethodID(env, clazz, "messageMe", "(Ljava/lang/String;)Ljava/lang/Integer;");
jobject result = (*env)->CallObjectMethod(env, obj, messageMe, sss);
const char* str = (*env)->GetStringUTFChars(env,(jstring) result, NULL); // should be released but what a heck, it's a tutorial :)
printf("%s\n", str);
return (*env)->NewStringUTF(env, str);
}
Java code
public String messageMe(Integer text) {
System.out.println( "aaaaaaaaaaaaaaaa "+text);
return "test";
}
I don't see where int[] come into your problem, but with int it should be easy to solve.
You need to look at your GetMethodId() call, specifically the method signature argument (the last one). The JNI Specification provides a list of all its Type Signatures here. That should also help you when you eventually come to pass your int arrays too.
So we can see at the moment your signature is:
String messageMe(Integer text)
but you told JNI it was (Ljava/lang/String;)Ljava/lang/Integer; which translates to something like:
java.lang.Integer messageMe(String text)
The Type Signatures show us that the signature for an int is simply I so your argument for GetMethodId() should be something like this:
jmethodID messageMe = (*env)->GetMethodID(env, clazz, "messageMe", "(I)Ljava/lang/String;");
I hope that helps. As I said before, JNI isn't the easiest thing to get into but the answers really are all in the Specification, you just have to look quite hard.
EDIT: I corrected my signature.
Basically, you were almost there - you just got the arguments and return value the wrong way around in the signature. It should be (<arguments>)<return type>. You also made the easy mistake of specifying the class for Integer, instead of the primitive type.
I need to get name of my android application from the native side somethin like that:
android.content.context context=(android.content.context) this;//current activiy
Resources appR =context.getResources();
String packageName=context.getPackageName();
int id=appR.getIdentifier("app_name","string",packageName );
CharSequence txt = appR.getText(id);
my native code like that:
jstring Java_com_AnalyticToolC_AnalyticToolActivity_JNISendData(JNIEnv* env,jobject entryObject,jobject contxt)
{
char *realAppName;
realAppName=(char *)malloc(16 * 1024);
jclass android_content_Context =(*env)->GetObjectClass(env, contxt);
jmethodID midGetPackageName = (*env)->GetMethodID(env, android_content_Context, "getPackageName", "()Ljava/lang/String");
jstring packageName=(*env)->CallObjectMethod(env, contxt, midGetPackageName);
jmethodID midGetResources = (*env)->GetMethodID(env, android_content_Context, "getResources", "()L");
jobject jResource=(*env)->CallObjectMethod(env, context, midGetResources);
jclass resource_Class=(*env)->GetObjectClass(env, jResource);
jmethodID midGetIdentifier = (*env)->GetMethodID(env, resource_Class, "getIdentifier", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String)I");
jstring app_name=(*env)->NewStringUTF(env,"app_name");
jstring TypeName=(*env)->NewStringUTF(env,"string");
int id=(*env)->CallObjectMethod(env, jResource, midGetIdentifier,app_name,TypeName,packageName);
jmethodID midGetAppName = (*env)->GetMethodID(env, resource_Class,"getText","(I)Ljava/lang/String");
jstring appName=(*env)->CallObjectMethod(env, jResource, midGetAppName,id);
realAppName=(*env)->GetStringUTFChars(env, appName, NULL);
}
and i just pass the activity to my native methon from java code.
and i don't have a chance to write this code in java class then call it form my NDK application
I'm trying a lot to pass a context object as jobject to my native code but it always crash.
dose any one have any idea?
Reflected Java access in C is ugly, ugly, ugly, just like you've demonstrated. Pass the app name as an extra string parameter.
EDIT: OK, you want reflection, reflection you'll get.
Your native method belongs to class AnalyticToolActivity. As a nonstatic class method, it has a this pointer on every call. Unlike C++ and Java methods, this pointer is passed explicitly as the second parameter. The method has two mandatory parameters - a JNIEnv * and a jobject. The second one corresponds to the this pointer of the Java object.
So if your AnalyticToolActivity is a subclass of Activity - quite likely - the entryObject parameter is an instance of Activity, meaning it's an instance of Context. So get rid of the third parameter (contxt), and your JNI code can go like this:
jclass android_content_Context =(*env)->GetObjectClass(env, entryObject);
//or use FindClass
jmethodID midGetPackageName = (*env)->GetMethodID(env,
android_content_Context,
"getPackageName",
"()Ljava/lang/String;");
jstring packageName=(*env)->CallObjectMethod(env, entryObject, midGetPackageName);
and so forth.
Why do you need to do so from the NDK? Perhaps it would be more useful for us to help you debug the problem passing a context object, and then you could just pass the app name as a string.