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);
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 an image buffer allocated on the heap in my c++ code that I would like to share with some other c++ objects as well as Java objects through JNI. On the native side im using shared_ptr and I was wondering what is the best way to do so ? my thought is to allocate the buffer on the heap once and share a reference everywhere. I'm taking advantage of smart pointers so that the buffer will be deallocated as soon as all the references go out of scope, but I'm facing an issue when sharing a reference to the java side.
how can I ensure that my java object has a valid reference to the buffer all the time ? how can c++ determine that the reference counter reaches 0 when java object is done using its reference. My concern is to avoid memory leak and also ensure that buffer doesn't get destroyed too soon before getting processed by the java class.
thanks for the help
The general answer is "make the Java object's lifetime influence the lifetime of the C++ object".
Start with the following Java class:
class Refholder implements AutoCloseable {
private long ptr; // the actual pointer
private long shared_ptr; // a pointer to a shared_ptr keeping `ptr` alive
public Refholder(long ptr, long shared_ptr) {
this.ptr = ptr;
this.shared_ptr = shared_ptr;
}
public native void close();
public void finalize() { close(); }
// Other methods to access the contents of `ptr` go here.
};
This will contain both the actual pointer and a pointer to a shared_ptr.
When you want to hand a reference to Java, use the following:
jobject passToJava(JNIEnv *env, std::shared_ptr<Foo> instance) {
jclass cls_Refholder = env->FindClass("Refholder");
jmethodID ctr_Refholder = env->GetMethodID(cls_Refholder, "<init>", "(JJ)V");
// This line increases the reference count and remembers where we put the copy
std::shared_ptr<Foo> *copy = new std::shared_ptr<Foo>(std::move(instance));
jobject ret = env->NewObject(cls_Refholder, ctr_Refholder, copy->get(), copy);
return ret;
}
Finally, the close method is responsible for extracting the shared_ptr and deallocating it:
JNIEXPORT void Java_Refholder_close(JNIEnv *env, jobject obj) {
jclass cls_Refholder = env->GetObjectClass(obj);
jfieldID fld_Refholder_ptr = env->GetFieldID(cls_Refholder, "ptr", "J");
jfieldID fld_Refholder_shared_ptr = env->GetFieldID(cls_Refholder, "shared_ptr", "J");
std::shared_ptr<Foo> *copy = (std::shared_ptr<Foo>*)env->GetLongField(obj, fld_Refholder_shared_ptr);
if (!copy)
return;
env->SetLongField(obj, fld_Refholder_ptr, 0);
env->SetLongField(obj, fld_Refholder_shared_ptr, 0);
delete copy;
}
I have decided to implement both AutoCloseable and finalize because I do not know how your Java code plans to use the reference. If you need deterministic destruction of the C++ object you need to use try-with-resources or explicit calls to the close method. If you do not, at last the finalize will close it.
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.
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);
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.