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.
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.
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.
I am trying to call a method in my java class from within a thread in the native code but not having any success. These are the global vars:
JavaVM* javaVM = NULL;
jclass activityClass;
jobject activityObj;
Code called on initialisation of the native code:
extern "C" {
JNIEXPORT jint JNICALL
naInit(JNIEnv *pEnv, jobject pObj, jstring pFileName, jstring, defaultStorageDirectory) {
pEnv->GetJavaVM(&javaVM);
jclass cls = pEnv->GetObjectClass(pObj);
activityClass = reinterpret_cast<jclass>((jclass) pEnv->NewGlobalRef(cls));
activityObj = pEnv->NewGlobalRef(pObj);
}
}
Code used within the thread function:
void *decodeAndRender(void * args) {
JNIEnv *env;
javaVM->AttachCurrentThread(&env, NULL);
jmethodID retryStartVideoMethodID = env->GetMethodID(activityClass, "retryStartVideo", "()V");
env->CallVoidMethod(activityObj, retryStartVideoMethodID);
javaVM->DetachCurrentThread();
return 0;
}
Java code :
public void retryStartVideo() {
Log.d(TAG, "METHOD CALLED FROM CPP ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
}
Error in logcat:
JNI DETECTED ERROR IN APPLICATION: JNI CallVoidMethodV called with pending exception java.lang.NoSuchMethodError: no non-static method "Ljava/lang/Class;.retryStartVideo()V"
I have set the instances of the calling class to be a global ref and used that when calling from within the thread function but it is still failing to find the method. I can use similar code to call a static method no problem but I need to be able to call a non static one.
I am using following code to access a java class from native code
JNIEnv *env = nullptr;
JMVEnv::attachCurrentJNIENv(&env);
jclass jXYZClass = env->FindClass("com/xxx/xx/xx/XYZClassName");
This call passes 4-5 times but fails after that. jXYZClass is null after some calls.
So, while compilation class is found and during execution also it was found 4-5 times. I am calling this code from different locations. Can it be some threading issue?
I think it is a multhreading problems... Especially when you say that " it was found 4-5 times. I am calling this code from different locations".
See this post for more details.
Firstly, you can print the thread id of each thread to see if they are the same thread.
You can Findclass in JNI_OnLoad:
jobject g_class;
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *pjvm, void *reserved) {
JavaVM *gJvm = pjvm; // cache the JavaVM pointer
JNIEnv *env= NULL;
env = gJvm->GetEnv((void**)&env, JNI_VERSION_1_6);
jclass tmp = env->FindClass("com/xxx/xx/xx/XYZClassName");
g_class = env->NewGlobalRef(tmp);
}
whenever you want to use this class:
// extern jobject g_class; Add this Line if this is in another cpp file
jmethodID methodID = env->GetMethodID((jclass)g_class, "<init>", "()V");
jobject new_object = env->NewObject((jclass)g_class, methodID);
I am having a hard time finding an answer to this. But, what is "jboject thiz" used for in JNI function calls? For example:
jobjectArray Java_com_gnychis_awmon_Test( JNIEnv* env, jobject thiz ) {
I use env to allocate objects often, but I've never used thiz and I'm not sure what it is for. Just for knowledge purposes.
The following is a JNI wrapper function which has two parameters, and returns a primitive array of objects:
jobjectArray Java_com_gnychis_awmon_Test( JNIEnv* env, jobject thiz );
From the function name you have given I don't think it is complete, that is, you haven't respected the obligatory function name convention which is:
Start the function with Java_
Append the package name separated by _ (undescores) i.e. com_company_awesomeapp. So far the function name is composed of: Java_com_company_awesomeapp
Append the Java class name where the native method has been defined,
followed by the actual function name. So at this point we should have the following function name: Java_com_company_awesomeapp_MainActivity_Test
The first parameter is a pointer to a structure storing all JNI function pointers, i.e. all the predefined functions you have available after you #include <jni.h>.
The second parameter is a reference to the Java object inside which this native method has been declared in. You can use it to call the other methods of the Java object from the current JNI function, i.e. Call Java instance methods from JNI code written in C or C++.
If for example you have the following Java class inside the MainActivity.java file:
public class MainActivity extends Activity
{
static
{
try
{
System.loadLibrary("mynativelib");
}
catch (UnsatisfiedLinkError ule)
{
Log.e(TAG, "WARNING: Could not load native library: " + ule.getMessage());
}
}
public static native Object[] Test();
}
Then, the jobject thiz parameter of the JNI function would be a reference to an object of type MainActivity.
I found this link that should help clarify the question.
https://library.vuforia.com/articles/Solution/How-To-Communicate-Between-Java-and-C-using-the-JNI
Here is an example in it that uses the "jobject".
JNIEXPORT void JNICALL
Java_com_qualcomm_QCARSamples_ImageTargets_ImageTargets_initApplicationNative(
JNIEnv* env, jobject obj, jint width, jint height)
{
...
jclass activityClass = env->GetObjectClass(obj);
jmethodID getTextureCountMethodID = env->GetMethodID(activityClass,
"getTextureCount", "()I");
if (getTextureCountMethodID == 0)
{
LOG("Function getTextureCount() not found.");
return;
}
textureCount = env->CallIntMethod(obj, getTextureCountMethodID);
...
}
jobject thiz means the this in java class.
Sometimes if you create a static native method like this.
void Java_MyClass_method1 (JNIEnv *, jclass);
jclass means the class itself.