This is closed. New problem will be addressed in a new question.
See edit for latest problem. I am trying to pass a Vector3 value from my cpp library to my java activity. I am able to do it vice versa, but cannot seem to find a way to go cpp to java. Anyone mine helping me out with this? I am receving this error: undefined reference to 'jni_createjavavm'
JavaVM *jvm; /* denotes a Java VM */
JNIEnv *env; /* pointer to native method interface */
JavaVMInitArgs vm_args; /* JDK/JRE 6 VM initialization arguments */
JavaVMOption* options = new JavaVMOption[1];
options[0].optionString = "-Djava.class.path=/usr/lib/java";
vm_args.version = JNI_VERSION_1_6;
vm_args.nOptions = 1;
vm_args.options = options;
vm_args.ignoreUnrecognized = false;
/* load and initialize a Java VM, return a JNI interface
* pointer in env */
JNI_CreateJavaVM(&jvm, &env, &vm_args);
delete options;
/* invoke the Main.test method using the JNI */
jclass cls = env->FindClass("MenuActivity");
jmethodID mid = env->GetStaticMethodID(cls, "Test", "(I)V");
env->CallStaticVoidMethod(cls, mid);
/* We are done. */
jvm->DestroyJavaVM();
Nov 11 2018 #2031 UTC+9 | EDIT: New Problem.. Crashes with java_class == NULL.
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
//Some Other Code Not Regarding JVM
JNIEnv *env;
vm->AttachCurrentThread(&env, NULL);
jclass cls = env->FindClass("MenuActivity");
jmethodID mid = env->GetStaticMethodID(cls, "Test", "(I)V");
env->CallStaticVoidMethod(cls, mid);
return JNI_VERSION_1_6;
}
On Android, there is no JNI_CreateJavaVM(). The apps run in JVM which is essential to access system APIs and services.
The callbacks from native code to the Java part of the app use the JNIEnv * that must belong to the current thread.
If this runs on a Java thread, the JNIEnv is received as the first parameter by the native method. You can call back to Java from a native thread, too. But then, you must attach the thread to JVM. AttachCurrentThread() accepts JavaVM * which can be stored as a global in your native code. You can obtain it in JNI_OnLoad() or derive it from JNIEnv with GetJavaVM().
Each native thread that is attached, must be detached on termination. The best practice is to use pthread_key_create() to define a destructor function that will be called before the thread exits.
You can read more explanations in the Android JNI tips article.
Related
I'm using Android NDK and need access to assets. A requirement for asset access seems to be obtaining an AssetManager reference.
Looking at the NDK samples (https://github.com/android/ndk-samples), the pattern seems to be:
A JNIEnv* is passed into the func when called directly from the JavaVM, along with some jobject
Use these to get AAssetManager* and then use this to open assets
That seems simple enough, except in my case, the functions are being called from Unity so I don't have access to either a JNIEnv* or jobject. Getting the JNIEnv* seems easy enough as I can make use of JNI_OnLoad to get access to a JavaVM* and then use that to get a JNIEnv* via vm->GetEnv. My questions about this are:
1) My understanding is that, an Android app can only have one instance of a Java VM. Am I safe to take the JavaVM* passed into JNI_OnLoad and save it for use in other function calls?
2) What about the JNIEnv*? Can I grab that once during JNI_OnLoad and save it, or should I grab a fresh one every time I need to use assets within a function? Is JNIEnv* something I need to explicitly free? (i.e. what's the lifetime/ownership situation with JNIEnv*?)
3) AAssetManager_fromJava also requires a jobject with the documentation (https://developer.android.com/ndk/reference/group/asset#group___asset_1gadfd6537af41577735bcaee52120127f4) saying: "Note that the caller is responsible for obtaining and holding a VM reference to the jobject to prevent its being garbage collected while the native object is in use.". I've seem some examples that simply pass in an empty (native) string like AAssetManager_fromJava(env, ""); - is that ok? I'd only be using the AssetManager for the lifetime of that call, and I could get a fresh one each time. (Again, is AAssetManager* a resource I need to manage, or am I just getting a reference to something owned elsewhere? The documentation seems to imply the latter.)
4) So given all the above, I'd probably do something like:
JavaVM* g_vm;
JNIEnv* g_env;
jint JNI_OnLoad(JavaVM* vm, void* reserved) {
g_vm = vm;
g_vm->GetEnv((void **)&g_env, JNI_VERSION_1_6); // TODO: error checking
return JNI_VERSION_1_6;
}
void do_asset_stuff() {
AAssetManager* mgr = AAssetManager_fromJava(g_env, "");
// do stuff...
}
Is that reasonable? No memory/resource leak issues? Any issues with multi-threading?
Thanks!
EDIT: Seems like there are some threading considerations with JNIEnv*. See: Unable to get JNIEnv* value in arbitrary context
Point-by point answer to your questions:
Yes, there can be only one VM in Android. You are allowed to store this pointer or use JNI_GetCreatedJavaVMs.
JNIEnv pointers are tightly coupled to the thread they were created on. In your situation you will first have to attach the thread to the VM using AttachCurrentThread. This will fill in a JNIEnv * for you. Don't forget to DetachCurrentThread when you're done.
Also note the caveat about FindClass: you need to look up classes from the main thread or via the classloader of a class you looked up in the main thread.
The implementation of AAssetmanager_fromJava is pretty clear: passing it anything other than an AssetManager object is undefined behavior. This answer shows one approach to grabbing the asset manager, another might be to call your own JNI function with a reference to the AssetManager object. Make sure to keep a global reference so it does not get GCed.
Given the above, it would probably look more like this:
JavaVM* g_vm;
jobject cached_assetmanager;
jint JNI_OnLoad(JavaVM* vm, void* reserved) {
g_vm = vm;
return JNI_VERSION_1_6;
}
void do_asset_stuff() {
JNIEnv *env;
JavaVMAttachArgs args = { JNI_VERSION_1_6, "my cool thread", NULL };
g_vm->AttachCurrentThread((void **)&env, &args);
AAssetManager* mgr = AAssetManager_fromJava(g_env, cached_assetmanager);
// do stuff...
}
// Assuming you call `com.shhhsecret.app.storeassetmanager(mgr)` somewhere.
void Java_com_shhhsecret_app_storeassetmanager(JNIEnv *env, jclass cls, jobject am) {
cached_assetmanager = env->NewGlobalRef(am);
}
I was able to read a json file from Unity c++ plugin.
I had to extend UnityPlayerActivity to get assetManager as jobject.
The tricky part also was to find the correct path to the asset in the plugin:
I placed it into StreamingAssets/data and was able to read using this path 'data/myfile'
see my comment with the code:
unity answers
Seems Botje's answer is precise (pity, I did not have it earlier)
Thought I'd post what I ended up doing in case it's of help to others...
#include <jni.h>
#include <android/asset_manager.h>
#include <android/asset_manager_jni.h>
JavaVM* g_JavaVM;
jobject g_JavaAssetManager;
bool g_Initialized = false;
jint JNI_OnLoad(JavaVM* vm, void* reserved) {
g_JavaVM = vm;
return JNI_VERSION_1_6;
}
// call this once from the main thread in C# land:
extern "C" void UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API NativeInit() {
if (g_Initialized) { return; }
g_Initialized = true;
JNIEnv* env = nullptr;
jint get_env_result = g_JavaVM->GetEnv((void **)&env, JNI_VERSION_1_6);
if (get_env_result == JNI_EDETACHED) {
jint attach_thread_result = g_JavaVM->AttachCurrentThreadAsDaemon(&env, nullptr);
if (attach_thread_result != 0) {
return;
}
get_env_result = JNI_OK;
}
if (env == nullptr || get_env_result != JNI_OK) {
return;
}
jclass unity_player = env->FindClass("com/unity3d/player/UnityPlayer");
jfieldID static_activity_id = env->GetStaticFieldID(unity_player, "currentActivity","Landroid/app/Activity;");
jobject unity_activity = env->GetStaticObjectField(unity_player, static_activity_id);
jmethodID get_assets_id = env->GetMethodID(env->GetObjectClass(unity_activity), "getAssets", "()Landroid/content/res/AssetManager;");
jobject java_asset_manager = env->CallObjectMethod(unity_activity, get_assets_id);
g_JavaAssetManager = env->NewGlobalRef(java_asset_manager);
}
Now g_JavaAssetManager can be used in any thread to call AAssetManager_fromJava.
I have the following CPP code. What I want to do is when an error occurs in my native side, I will notify Java about the error. I used How can I catch SIGSEGV (segmentation fault) and get a stack trace under JNI on Android? as reference.
static JavaVM* g_JVM = NULL;
static jobject g_thejavaobject = NULL;
void InitializeNativeSide(JNIEnv *env, jclass, jobject object)
{
env->GetJavaVM(&g_JVM);
g_thejavaobject = env->NewGlobalRef(object);
}
// this executes in another thread running in parallel with UI thread
void StartExecuting(JNIEnv *_env, jclass) {
struct sigaction sa;
memset(&sa, 0, sizeof(struct sigaction));
sigemptyset(&sa.sa_mask);
sa.sa_sigaction = SignalErrorHandler;
sa.sa_flags = SA_SIGINFO;
sigaction(SIGSEGV, &sa, NULL);
// native starts executing here. after a while, a SEGFAULT is encountered
// triggering SignalErrorHandler()
...
}
void SignalErrorHandler(int signal, siginfo_t *si, void *arg)
{
JNIEnv *env;
g_JVM->GetEnv((void**)&env, JNI_VERSION_1_6);
jclass myClass = env->FindClass("com/company/MyClass");
jmethodID myMethod = env->GetMethodID(myClass, "nativeCrashed", "()V" );
env->CallVoidMethod(g_thejavaobject, myMethod);
env->DeleteLocalRef(myClass);
}
Everything works fine but the call to myClass.nativeCrashed() does not work. What am I doing wrong?
You can't do this:
void SignalErrorHandler(int signal, siginfo_t *si, void *arg)
{
JNIEnv *env;
g_JVM->GetEnv((void**)&env, JNI_VERSION_1_6);
jclass myClass = env->FindClass("com/company/MyClass");
jmethodID myMethod = env->GetMethodID(myClass, "nativeCrashed", "()V" );
env->CallVoidMethod(g_thejavaobject, myMethod);
env->DeleteLocalRef(myClass);
}
That will not work for at least two fundamental reasons.
First, only async-signal-safe functions may be called from within a signal handler. The POSIX-specified list can be found at http://pubs.opengroup.org/onlinepubs/9699919799/functions/V2_chap02.html#tag_15_04_03.
No Java JNI call is async-signal-safe.
Second, the Java JVM uses SIGSEGV internally - getting a SIGSEGV is not necessarily fatal:
Signals Used in Oracle Solaris, Linux, and macOS
...
SIGSEGV, SIGBUS, SIGFPE, SIGPIPE, SIGILL These signals are used in
the implementation for implicit null check, and so forth.
Android's JNI tips page mentions this FAQ: Why didn't FindClass find my class?
They mention multiple solutions and the last option there is this one:
Cache a reference to the ClassLoader object somewhere handy, and issue
loadClass calls directly. This requires some effort.
So, I tried to get it working and it seems that no matter what, this method simply does not work for me. Eventually, I figured how to use ClassLoader but it won't work if from a native thread I try to loadClass that hasn't been touched/loaded yet. Essentially, it's the identical to env->FindClass in behavior when called from a native thread, with the exception that it won't return 0 for classes that were already use in the app. Any idea if I didn't get it right, or it's impossible to access classes from a native thread that weren't used/loaded yet.
EDIT: I'll give more info to explain what exactly I mean. There is regular JNI env->FindClass(className), and another one that I wrote myFindClass(env, className) that uses cached ClassLoader->loadClass.
The class that I'm trying to access from native c/c++ is "com/noname/TestClient". Inside myFindClass I also use env->FindClass and log value that it returns:
jclass myFindClass(JNIEnv * env, const char* name)
{
...
jclass c0 = env->FindClass(name);
jclass c1 = (jclass)env->CallObjectMethod(ClassLoader,
MID_loadClass, envNewStringUTF(name));
dlog("myFindClass(\"%s\") => c0:%p, c1:%p, c0 and c1 are same: %d",
name, c0, c1, env->IsSameObject(c0, c1));
...
}
Then, I have these 3 combinations to explain the issue.
1)
//inside JNI_OnLoad thread
myFindClass(env, "com/noname/TestClient");
...
//inside native thread created by pthread_create
myFindClass(env, "com/noname/TestClient");
I get this logcat:
myFindClass("com/noname/TestClent") => c0:0x41b64558, c1:0x41b64558,
c0 and c1 are same: 1 ...myFindClass("com/noname/TestClent") => c0:0,
c1:0x41b64558, c0 and c1 are same: 0
2)
//inside JNI_OnLoad thread
env->FindClass("com/noname/TestClient");
...
//inside native thread created by pthread_create
myFindClass("com/noname/TestClient");
I get this logcat:
myFindClass("com/noname/TestClent") => c0:0, c1:0x41b64558, c0 and c1 are same: 0
3)
//inside JNI_OnLoad thread
//"com/noname/TestClient" isn't touched from JNI_OnLoad.
...
//inside native thread created by pthread_create
myFindClass(env, "com/noname/TestClient");
I get this logcat:
myFindClass("com/noname/TestClent") => c0:0, c1:0, c0 and c1 are same: 1
Basically, my issue is that ClassLoader doesn't find my class in the 3rd case. Is it a bug? What can be done to fix the problem?
EDIT2:
On top of that, it seems that ClassLoader::loadClass is plainly buggy. If I ask myFindClass("noname/TestClent") then it returns some garbage, and when I use that returned jclass in any way the app crashes.
After much trying and crashing of my app, a colleague and I managed to cache and succesfully use the class loader in another, native, thread. The code we used is shown below (C++11, but easily converted to C++2003), posted here since we couldn't find any examples of the aforementioned "Cache a reference to the ClassLoader object somewhere handy, and issue loadClass calls directly. This requires some effort.". Calling findClass worked perfectly when called from a thread different from the one of JNI_OnLoad. I hope this helps.
JavaVM* gJvm = nullptr;
static jobject gClassLoader;
static jmethodID gFindClassMethod;
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *pjvm, void *reserved) {
gJvm = pjvm; // cache the JavaVM pointer
auto env = getEnv();
//replace with one of your classes in the line below
auto randomClass = env->FindClass("com/example/RandomClass");
jclass classClass = env->GetObjectClass(randomClass);
auto classLoaderClass = env->FindClass("java/lang/ClassLoader");
auto getClassLoaderMethod = env->GetMethodID(classClass, "getClassLoader",
"()Ljava/lang/ClassLoader;");
gClassLoader = env->CallObjectMethod(randomClass, getClassLoaderMethod);
gFindClassMethod = env->GetMethodID(classLoaderClass, "findClass",
"(Ljava/lang/String;)Ljava/lang/Class;");
return JNI_VERSION_1_6;
}
jclass findClass(const char* name) {
return static_cast<jclass>(getEnv()->CallObjectMethod(gClassLoader, gFindClassMethod, getEnv()->NewStringUTF(name)));
}
JNIEnv* getEnv() {
JNIEnv *env;
int status = gJvm->GetEnv((void**)&env, JNI_VERSION_1_6);
if(status < 0) {
status = gJvm->AttachCurrentThread(&env, NULL);
if(status < 0) {
return nullptr;
}
}
return env;
}
Try attaching your native thread to the JVM first.
The pointer to jvm you can obtain first thing in JNI_OnLoad
env->GetJavaVM(&jvm);
Then from your native thread
JNIEnv *env;
jvm->AttachCurrentThread((void **)&env, NULL);
Then use that env for FindClass
I want to call a java method inside a pthread.
the C++ method start like this :
char* FileLoader::getStringFromFile(char* a_filename)
{
JNIEnv *env;
g_jvm->AttachCurrentThread (&env, NULL);
jclass cls = env->FindClass(JAVA_FILE_LOADER_CLASS);
...
g_jvm points to the JavaVM object. it is set when the app start in the JNI_OnLoad() method.
When "FindClass" is called, it throws a "noClassDefFoundError" but if I call this method in the main thread, it works as expected.
Have I forgotten something ?
Ok, I solve the problem with the advice of technomage :
In my JNI_OnLoad() method :
jint JNI_OnLoad(JavaVM* vm, void * reserved)
{
JNITools::g_jvm = vm;
JNIEnv *env;
g_jvm->AttachCurrentThread (&env, NULL);jclass tmpClass = env->FindClass("com/Framework/IO/CFileLoader");
g_fileLoaderClass = (jclass)env->NewGlobalRef(tmpClass);
...
My first test has failed because I forgot to call NewGlobalRef().
This doc helped me to understand why it is needed.
I have an issue with the NDK.
In my JNI_OnLoad method, I cache the JavaVm pointer, the class that called the method, and a method id which I use later on:
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *jvm, void *reserved){
JNIEnv *env;
cachedJVM = jvm;
if((*jvm)->GetEnv(jvm, (void**)&env, JNI_VERSION_1_6)){
LOG_ERROR("Could not get JNIEnv*");
return JNI_ERR;
}
javaClass = (*env)->FindClass(env, "org/test/opensl/AudioProcessor");
if(javaClass == NULL){
LOG_ERROR("Could not get java class");
return JNI_ERR;
}
javaCallbackMID = (*env)->GetMethodID(env, javaClass, "enqueueAudio", "([B)V");
if(javaCallbackMID == NULL){
LOG_ERROR("Could not get method identifier");
return JNI_ERR;
}
return JNI_VERSION_1_6;
}
I have a small utility method defined as follows that should get me a pointer to the JNIEnv:
JNIEnv* JNU_GetEnv(){
JNIEnv* env;
(*cachedJVM)->GetEnv(cachedJVM, (void**)&env, JNI_VERSION_1_6);
return env;
}
And finally, I have a callback from an OpenSL ES SLAndroidSimpleBufferQueueItf which I want to handle the recorded audio from a SLRecordItf:
void recorderCallback(SLAndroidSimpleBufferQueueItf bq, void *context){
SLresult result;
JNIEnv* env;
recorderContext* thisContext = (recorderContext*)context;
env = JNU_GetEnv();
if(env == NULL){
LOG_ERROR("Could not get JNIEnv*");
return;
}
jbyteArray data = (*env)->NewByteArray(env, MAX_PACKET_SIZE);
if(data == NULL){
LOG_ERROR("No memory could be allocated for buffer");
return;
}
(*env)->SetByteArrayRegion(env, data, 0, MAX_PACKET_SIZE, recorderBuffer);
(*env)->CallByteMethodA(env, thisContext->caller, javaCallbackMID, data);
(*env)->DeleteLocalRef(env, data);
result = (*bq)->Enqueue(bq, recorderBuffer,
RECORDER_FRAMES * sizeof(jbyte));
checkError(result, "Unable to enqueue new buffer");
}
Where the context parameter for the callback method only holds a reference to the object that called the native method. It is a self defined struct like this:
typedef struct recorderContext{
jobject caller;
} recorderContext;
However, every time I try to run this, I get the "Could not get JNIEnv*" error message from the callback method.
My question basically comes down to this: Why can I get a pointer to the JNIEnv in the JNI_OnLoad method, but not in the recorderCallback, as both use the same Java VM pointer to get the JNIEnv?
I need this callback to pass the recorded Audio back up to my Java layer for further processing...
Why can I get a pointer to the JNIEnv
in the JNI_OnLoad method, but not in
the recorderCallback, as both use the
same Java VM pointer to get the
JNIEnv?
Because the callback happens on some native thread, different from the VM thread which loads the library. The JNI implementation maintains a JNIEnv per thread, and puts the pointer in thread-local storage. It is only initialized for native threads which are attached to the VM. You need to call AttachCurrentThread() (or more probably AttachCurrentThreadAsDaemon()) inside the callback to get a JNIEnv pointer valid for that thread. This attaches the thread to the VM on the first call and is a nop thereafter.
See http://download.oracle.com/javase/1.5.0/docs/guide/jni/spec/invocation.html
Caveat: This answer assumes proper Java. The problems you're seeing suggest that Dalvik behaves the same as the JVM.