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.
Related
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 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
I am using JNI in an Android Studio project I am working on. Currently, I have a C++ library that looks similar to this.
#include <jni.h>
...
extern "C" {
JNIEXPORT jobject JNICALL Java_com_cerbyarms_cerbyarms_esra_camera_CameraActivity_FindFeatures(JNIEnv* env, jobject, jlong maskMat)
{
...
jclass rectClass = env->FindClass("org/opencv/core/Rect");
jmethodID rectID = env->GetMethodID(rectClass, "<init>", "(IIII)V");
return env->NewObject(rectClass, rectID, x, y, width, height);
}
}
This works. However, it is inefficient. Every time this is run, rectClass has to refind the class and other variables that remain constant in the program have to be recalculated and redefined every time function FindFeatures is called.
I came across this answer on Stack Overflow (It is not related to this question apart from the fact that it shows an example of what I am trying to do), that shows a different layout for a native file when using JNI.
It looked like this
static jclass java_util_ArrayList;
static jmethodID java_util_ArrayList_;
jmethodID java_util_ArrayList_size;
jmethodID java_util_ArrayList_get;
jmethodID java_util_ArrayList_add;
static thread_local JNIEnv* env;
void init() {
java_util_ArrayList = static_cast<jclass>(env->NewGlobalRef(env->FindClass("java/util/ArrayList")));
java_util_ArrayList_ = env->GetMethodID(java_util_ArrayList, "<init>", "(I)V");
java_util_ArrayList_size = env->GetMethodID (java_util_ArrayList, "size", "()I");
java_util_ArrayList_get = env->GetMethodID(java_util_ArrayList, "get", "(I)Ljava/lang/Object;");
java_util_ArrayList_add = env->GetMethodID(java_util_ArrayList, "add", "(Ljava/lang/Object;)Z");
}
std::vector<std::string> java2cpp(jobject arrayList) {
jint len = env->CallIntMethod(arrayList, java_util_ArrayList_size);
std::vector<std::string> result;
result.reserve(len);
for (jint i = 0; i < len; i++) {
jstring element = static_cast<jstring>(env->CallObjectMethod(arrayList, java_util_ArrayList_get, i));
const char* pchars = env->GetStringUTFChars(element, nullptr);
result.emplace_back(pchars);
env->ReleaseStringUTFChars(element, pchars);
env->DeleteLocalRef(element);
}
}
This shows a native file that has expensive and constant variables that appear to only be declared and calculated once.
How can I achieve a similar thing using only the Android Studio IDE? I don't mind having to set up external tools in the Android Studio IDE settings, but I don't want to have keep switching between Android Studio and something like CMD every time I compile my code.
Ideally, this could all be handled correctly when Make Project is hit. Is this possible in Android Studio 3?
You are 100% right, some JNI values beg to be cached and reused. Class references and method IDs are good examples. Please remember that FindClass() returns a local reference, so you need NewGlobalRef() for each class you keep in cache.
Android Studio does not help us with this setup, and I am not aware of reliable tools that can do such refactoring for us. You can learn good practices from open source code, e.g. from WebRTC JNI wrapper or from Spotify JNI helpers.
Android Studio can only keep track of the native methods, not of the cached objects, conversions, etc.
I'm trying to call several instances of a java class from c language. The code runs well for a single instance but fails when I try to call several instances of java classes.
There is a jar file and a supporting dll for the java classes, and the java classes are assumed to be a complete blackbox
JNIEnv* create_vm(JavaVM **jvm) {
JNIEnv *env;
JavaVMInitArgs vm_args;
JavaVMOption options[4];
options[0].optionString = "-Djava.compiler=none";
//Path to the java source code
options[1].optionString = "-Djava.class.path=G:\\dtk_testing\\dtk_test\\bin;C:\\Program Files (x86)\\DTK Software\\DTK ANPR SDK\\Bin\\Java\\DTKANPR.jar";
options[2].optionString = "-Djava.library.path=C:\\Windows\\System32";
//options[3].optionString = "-verbose";
vm_args.version = JNI_VERSION_1_6;
vm_args.nOptions = 4;
vm_args.options = options;
vm_args.ignoreUnrecognized = JNI_TRUE;
jint ret =JNI_CreateJavaVM(jvm, (void**)&env, &vm_args);
invoking a class using the following method in C:
The value j is a parameter that I pass on to the java code, which in turn calls an image with that particular serial number
void invoke_class(JNIEnv *env, char *str)
{
jclass DTK_ANPR_Test;
jmethodID dtk_anpr;
jint k=2;
jvalue *val=(jvalue *)malloc(sizeof(jvalue));
val->i=(jint *)malloc(sizeof(jint));
(val->i)=k;
DTK_ANPR_Test = (*env)->FindClass(env, "DTKANPRTest");
dtk_anpr = (*env)->GetStaticMethodID(env, DTK_ANPR_Test, "anpr", " (I)V");
(*env)->CallStaticVoidMethodA(env, DTK_ANPR_Test, dtk_anpr, val);
}
Later I call this method, 'invoke_class' wherever I need to run the java class.
I was thinking that running the code in parallel (using openmp or mpi) would do, but I'm still not sure as to how I can proceed further
P.S. I don't have any good idea about openmp or mpi
I think what you mean, is not "instances" since you are invoking a static method, but multiple threads invoking the same static method?
If that is the case, your first invocation will work because the thread used, called JNI_CreateJavaVM which becomes your java main thread. When JVM is created it attaches the current native thread to the JVM.
The JNIEnv that you get, is only valid for that 1 thread which originally created the JVM. If you want to call the same method from additional native threads, you need to attach those threads as well, using the JNI call AttachCurrentThread.
Note, you will have a different JNIEnv instance for each thread.
Hope that helps.
PS: if you are passing a simple jint argument to a java method, I would suggest you use CallStaticVoidMethod(env, DTK_ANPR_Test, dtk_anpr, 2) instead of needlessly allocating memory to pass an integer, which never seems to be released, so this will be a memory leak.
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.