I have an Android app where the following C method is called when the app starts (in Activity.onCreate).
extern "C"
JNIEXPORT jstring JNICALL
Java_com_google_oboe_test_oboetest_MainActivity_stringFromJNI(
JNIEnv *env,
jobject instance) {
jclass sysclazz = env->FindClass("java/lang/System");
jmethodID getPropertyMethod = env->GetStaticMethodID(sysclazz, "getProperty", "(Ljava/lang/String;)Ljava/lang/String;");
jstring result = (jstring) env->CallStaticObjectMethod(sysclazz, getPropertyMethod, "os.name");
return result;
}
When this method is called the app crashes and I get the error:
JNI DETECTED ERROR IN APPLICATION: use of deleted local reference 0xd280e8d5
Step debugging shows that this line causes the crash:
jstring result = (jstring) env->CallStaticObjectMethod(sysclazz, getPropertyMethod, "os.name");
What causes this error? And how can I call System.getProperty("os.name") using JNI without getting this error?
The issue is that env->CallStaticObjectMethod is expecting a jstring as its 3rd argument and is instead being supplied with a string literal.
Creating a jstring first
jstring arg = env->NewStringUTF("os.name");
jstring result = (jstring) env->CallStaticObjectMethod(sysclazz, getPropertyMethod, arg);
fixed the problem.
In my case, I was using a local reference that was created in a function and was used in another function.
For example:
void funA(){
jclass localClass = env->FindClass("MyClass");
}
The above statement returns a local reference of jclass. Suppose we store this in a global variable and use it in another function funB after funA is completed, then this local reference will not be considered as valid and will return "use of deleted local reference error".
To resolve this we need to do this:
jclass localClass = env->FindClass("MyClass");
jclass globalClass = reinterpret_cast<jclass>(env->NewGlobalRef(localClass));
First, get the local reference and then get the global reference from local reference. globalClass can be used globally (in different functions).
Read Local and global references
Related
The lambda that I pass to builder is populated into className object, and called at regular intervals (every hour) of time to refresh the other members. It gets called the first time successfully. I'm not sure if the lambda retains env, instance to legally call the reverse JNI function?
JNIEXPORT jint JNICALL
Java_com_company_app_ClassName_JniInit(JNIEnv *env, jobject instance){
int data = 0;
auto builder = new Builder(data,
[env, instance]() -> std::string {
std::string stringObj = populateData(env, instance); // This function makes a reverse JNI call to get data from a java function in the class
return stringObj;
}
);
std::shared_ptr<className> = builder->build();
return 1;
}
I seem to be getting a SIGNAL 11 error, SIGSEGV. Is this kind of segmentation fault catchable in any way, so the app doesn't crash?
Fatal signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x228 in tid 21785 (ClassName), pid 21573 (.company.app)
It seems to be crashing at this line inside populateData-
jstring data = (jstring)(env)->CallObjectMethod(instance, javaFunctionName);
Is there a way to check if this function will fail before calling it? I checked if env (JNIEnv* argument in populateData) is NULL, but its not, and has a valid address along with instance (jinstance argument in populateData).
You'll have problems with jobject instance if this function does something asynchronously. The reason is that before this function is started, Java marks the object as having an extra reference. It removes that when it returns. So after it returns, the object can be cleaned up by the garbage collector if there's no other instances in the Java code.
This can be fixed by calling NewGlobalRef(JNIEnv *env, jobject obj) before starting the async function on the main thread, and calling DeleteGlobalRef at the end of the callback when jobject is no longer needed.
To answer this question, I have found a slightly different kind of hack.
Don't copy the JNIEnv, and object or create references to them. They get deleted as soon as your JNI function goes out of scope. I'm not sure why copying doesn't work (if someone could answer this, that would be great). Alternatively, I've used JavaVM* to maintain a local reference of the jvm, you can do this in your JNI_OnLoad.
Use the function below, and it would work fine.
JavaVM* jvm; // Initialise this in OnLoad
JNIEnv *getJNIEnv()
{
JNIEnv *env = nullptr;
jint ret = jvm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6);
if (ret == JNI_EDETACHED || !env)
{
env = nullptr;
}
return env;
}
If you need the class instance, you'll need to initialise it via JNIEnv, but I use a static method for my class, so I just fetched the class using env->FindClass and then you can do env->GetStaticMethodID along with env->CallStaticObjectMethod.
I had wrote a agent to use the JVMTI on android 9. Code like this, I create an AgentFunction object to monitor the VM. It work's fine.
AgentFunction *agent = 0;
JNIEXPORT jint JNICALL Agent_OnAttach(JavaVM *vm, char *options, void *reserved) {
agent = new AgentFunction();
agent->Init(vm);
agent->ParseOptions(options);
agent->AddCapability();
agent->ShowCapabilities();
agent->RegisterEvent();
return JNI_OK;
}
Then i want export some interface to java, than user can invoke the JVMTI function directly.
private native boolean applyChangeNative(List<ClassInfo> classes);
The JNI fumction in agent.so
extern "C"
JNIEXPORT jboolean JNICALL Java_com_cc_jvmtiagent_JVMTIFunction_applyChangeNative
(JNIEnv *jniEnv, jobject, jlong jvmti, jobject classInfo) {
...
jvmtiClassDefinition *def = new jvmtiClassDefinition[total_classes];
agent->RedefineClasses(total_classes, def);
}
But when invoke the native method applyChangeNative from JAVA, the agent->RedefineClasses crash caused by agent is null. After my test, i found i can't access the object create in JVMTI from JNI.
I had read the JDK souce code , I found it have an InvocationAdapter.cc, When Agent_OnAttach it create the JPLISAgent, then create java.lang.instrument.Instrumentation and save the JPLISAgent in it. Each function from Java will take the JPLISAgent point.
But i want to known , why access the JVMTI object is NULL directly from JNI?
Resolved:
If you want invoke the agent method via JNI, you should use System.Load(agentPath) instead of System.LoadLibrary(libName). It need use the same so file.
It work's on Android 9 and 10, But on Android 8.x, Can't access the agent, i do not know why.
I create Androidd NDK OpenGL ES application so I want to call Java method from NativeActivity, in main.cpp file I have JNIEnv* which I pass it to another classes where I want to call the Java method:
JNIEnv* env= manager.env;//I pass this pointer from main.cpp
jclass clazz = env->FindClass("com/game/JavaMethods");//Here I get the exception
jstring jstr1 =env->NewStringUTF(imgURL.c_str());
jmethodID mid = env->GetStaticMethodID(clazz, "Share", "(Ljava/lang/String;)V");
env->CallStaticVoidMethod(clazz, mid, jstr1);
I get SIGABRT crash if any env method called.
I am not expert in JNI so I am confused where is the wrong.
I'm trying to emend a JNI function that takes a string argument from the java layer.
Initially, the java code from the .java file was:
callJNIMethod();
I'm trying to change it so that I can give it a string, like this:
String arg = "abcd";
callJNIMethod(arg);
In my JNI code, this is what the function previously looked like:
JNIEXPORT jboolean JNICALL Java_com_file_app_Activity_callJNIMethod(
JNIEnv *env, jclass clazz){
//use the string
}
This is what I changed it to (in the wrapper .cpp file and the header .h file):
JNIEXPORT jboolean JNICALL Java_com_file_app_Activity_callJNIMethod(
JNIEnv *env, jclass clazz, jstring str);
I'm getting an error that the method callJNIMethod cannot be applied to the given types.
required: no arguments
found: java.lang.String
reason: actual and formal argument lists differ in length
Note: Some input files use or override a deprecated API.
Any idea what's wrong?
You went astray by editing the .h file. Changes to native methods should begin in your Java source.
The .h file should be generated by javah from the compiled .class file. You can set up your build system to re-run javah with each build. That way you'll see right away if the native method implementations no longer match the native declarations in your Java source.
I'm trying to do the following (not sure if I'm missing something or if this is not possible):
I have a Java class (in this particular case a Servlet) that calls a native method.
In this native method I'm spawning a new thread, and in that thread I would like to call a method on that Java object. Is that even possible?
What I tried in the native method that is called (original thread) is to remember the JavaVM instance, so that I can later can attach the other thread to it (seems to work), and make a NewGlobal ref for the jobject:
JavaVM *vm;
env->GetJavaVM(&vm);
env->NewGlobalRef(jobj)
What I don't know is how to retrieve the jobject in the other thread. If I just pass it the VM crashes, I assume because of an illegal thread access. If I create a new object for the class, I wouldn't have the exact object that I need.
Any ideas?
Thank you,
Mark
SOME ADDITIONAL CODE (method names etc. obscured):
The method that is called from the servlet:
JNIEXPORT jstring JNICALL ABC (JNIEnv *env, jobject jobj, some more arguments
{
JavaVM *vm;
env->GetJavaVM(&vm);
env->NewGlobalRef(jobj);
// spawning new thread (I'm using boost libraries)
boost::thread t = boost::thread(boost::bind(&XYZ::DEF, instance of XYZ, vm, &jobj);
...
}
void XYZ::DEF(JavaVM* vm, jobject* jobj)
{
JNIEnv* env;
vm->GetEnv( (void**)&env, JNI_VERSION_1_2);
vm->AttachCurrentThread((void**)&env, NULL);
... then eventually calling another method, but still the same thread, where I'm doing this:
jobject bb = env->NewDirectByteBuffer(...,...); // this crashed when I just used the env from the original thread, seems to work since I'm attaching the second thread to the VM.
// it crashes somewhere during the following code:
jclass cls = env->GetObjectClass(jobj);
jmethodID mid = env->GetMethodID(cls, "callback", "(I)V");
env->CallVoidMethod(jobj, mid, 13);
The crash produces something like this "A fatal error has been detected by the JRE... Problematic frame: V [libjvm.so+0x3e9220]...
You seem to be ignoring the result of NewGlobalRef. You have to use its result in the other thread instead of the original jobj.