I have c++ lib used with my application. I passed java object to jni and saved it to global reference. Then, I wish to call method of this java object from jni from antoher thread (I use pthread).
Java class is:
public class WaitingServiceReadyCallback {
public void ready(String serviceName) throws Exception { ... // some code }
}
To call java method I use next code:
jvm->AttachCurrentThread(&env, 0);
cls = env->GetObjectClass(__obj__); // __obj__ is global reference to object.
if (!cls)
goto detach;
mid = env->GetMethodID(cls, "ready", "(Ljava/lang/String;)V");
There GetMethodID fails to find method.
When I use
cls = env->FindClass("com/mypackage/WaitingServiceReadyCallback");
instead of GetObjectClass, FindClass fails to find this class.
I tried to check class name of the object referencd by my __obj__ global reference (used getName from com/java/Class, made call to getName in the same place of my code as above call to ready), I got right class name - com.mypackage.WaitingServiceReadyCallback.
I am sure that class exists and loaded (java code executed before jni and instance of this class is created there), I am sure that method exists in the class.
So, I can't understand, what I done wrong?
I met this problem. The reason in short: within another thread VM does not provide us an info about loaded classes. I've solved this by storing the classloader of some sample java object and then using it for manual loading of needed classes from another threads.
Related
I am trying to make sense of a class from a library. The class has no constructor, and I think it is being instantiated via reflection. It is a confusing library, and I want to figure out what is creating instances of this class... but I cannot figure out where to put a breakpoint, since there is no constructor.
I have tried the following, and Android Studio 3.4.1 blows right past them:
Line breakpoints on fields in the class that have initializers
Field watchpoints, set for both field access and field modification, for fields that have initializers
A breakpoint on the class declaration (class Foo)
Breakpoints in methods work, and field watchpoints work when the field is accessed later on (but not when it is initialized). So the debugger is working with this class in general.
I cannot readily recompile the library to add a constructor, though I do have source code (not just decompiled bytecode).
Is there another spot that I can put a breakpoint that will show me the stack trace of instantiation of this class?
From the InitelliJ IDEA documentation (on which AS is based):
If you want to set a breakpoint in the default class constructor, set it on the first line of this class, since the default constructor is mapped to it.
https://www.jetbrains.com/help/idea/using-breakpoints.html
Simple test-case to determine breakpoint position:
public class ReflectionTest {
static int test = 1;
public static void main(String[] args) throws Exception {
System.out.println(ReflectionTest.class.getDeclaredConstructor().newInstance());
}
}
Placing the breakpoint on public class ReflectionTest seems to trigger for me.
it's known that jni function usually named as follows (from Oracle docs):
Dynamic linkers resolve entries based on their names. A native method name is concatenated from the following components:
the prefix Java_
a mangled fully-qualified class name
an underscore (“_”) separator
a mangled method name
So method's name always should contain corresponding Java class name and cannot be called from outside of this class. In case u're call this method from other class Android studio inspector gives the warning like this:
Reports native method declarations in Java where no corresponding JNI function is found in the project.
And if u run that u will receive java.lang.UnsatisfiedLinkError: Native method not found exception.
And thus my question is: how to create shared library with native functions, which could be called from any Java class?
To be more concrete. I have Java class MyActivity. And i need to call some jni function from this class. As i understand according Oracle doc that function should be named smth like Java_xxx_yyy_zz_MyActivity_func1. Then imagine, that i also want to call this function from another android app. I copy my lib*.so to libs folder but i will not be able to call my func1, coz native function will not be found. For possibility of using that function in new app i need to create the same folders hierarchy that is xxx/yyy/zzz and class MyActivity there and after that i will be able to call func1 but only from that MyActivity class. But what should i do if i want to call that fron another class?
-- new answer, given question edits --
Thanks for the clarification. AFAIK, there is no automatic way to associate the same native method with multiple Java methods. However, there are a couple workarounds that you might find useful:
(1) Call a shared facade. Assuming you don't need to pass the Java instance object to the method, first declare a facade class as in my original answer, and implement it in C:
public class JniStuff {
public static native void method1(String s, int i);
}
JNIEXPORT void JNICALL Java_net_redpoint_scratch_JniStuff_method1
(JNIEnv *, jclass, jstring, jint);
then call it from any other class you want:
public class MyClass {
public static void method1(String s, int i) {
JniStuff.method1(s, i);
}
}
(2) Use the RegisterNatives call to associate the one C method with multiple java methods, as explained here. However, I doubt that you will find this to be convenient as the registration must be done from "C", so you would also need some startup method that is called first to do that.
--- old answer --
I'm not sure what you are after, exactly. If your problem is that you don't want to create an object containing the methods, just declare them static:
package net.redpoint.scratch;
public class JniStuff {
public static native void method1(String s, int i);
}
javah then generates this native method signature:
JNIEXPORT void JNICALL Java_net_redpoint_scratch_JniStuff_method1
(JNIEnv *, jclass, jstring, jint);
Your other Java code is free to call:
JniStuff.method1("something", 123);
without instantiating an object of type JniStuff
When I run the code, I get an error "failed adding to JNI local ref table has 512 entries"
This is my code:
jstring pJNIData = pJNIEnv->NewStringUTF ( variables[0].GetStringValue() );
pJNIEnv->CallStaticVoidMethod ( pJNIActivityClass, pJNIMethodIDStartTime, pJNIData ) ;
pJNIEnv->DeleteLocalRef(pJNIData);
I have read several suggestions, but none of them work! In spite of the DeleteLocalRef, it fails to works. The function is used in a profiler that literally calls all the functions...
I have seen this when a JNI method called Java code (in my case, the method was not static). As I understand, unused local references are not automatically deleted when a Java method is called from JNI (I mean, until the top-level JNI function returns).
IIRC either there already was information about memory objects in the log, or I could add some logging; from that information I identified garbage items that I did not mention before. They were two arrays and a class, created in subsequent calls but not garbage-collected.
// in a function that calls a Java method from JNI
jbyteArray srcArray = env->NewByteArray(len);
jclass cls = env->FindClass("com/something/MyClass");
jmethodID mid = env->GetMethodID(cls, "mymethod", "([BI)[B");
jbyteArray resArray = (jbyteArray)env->CallObjectMethod(obj, mid, srcArray, XXXX);
...
env->DeleteLocalRef(cls);
env->DeleteLocalRef(resArray);
env->DeleteLocalRef(srcArray);
// no need to do anything with mid
Note that although these three local references were obtained differently, all of them were hanging around.
Useful link:
http://www.netmite.com/android/mydroid/dalvik/docs/jni-tips.html#local_vs_global_references
(or find the Dalvik VM docs dalvik/docs/jni-tips.html and locate the section "Local vs. Global References")
Every object that JNI returns is a "local reference". This means that it's valid for the duration of the current native method in the current thread. Even if the object itself continues to live on after the native method returns, the reference is not valid. This applies to all sub-classes of jobject, including jclass and jarray. [...] Note: method and field IDs are just 32-bit identifiers, not object references, and should not be passed to NewGlobalRef. The raw data pointers returned by functions like GetStringUTFChars and GetByteArrayElements are also not objects.
I thought I would chip in just in case anyone else runs into this issue. This is a weird case that kept me confused for hours!
Ok so I have an NDK app and the Java code being called is inside an apk that is loaded at runtime. I have no idea if the runtime loading effects this in any way but I thought I should mention it.
Now in a c++ method I use find class and getmethodid to get the constuctor to a HashMap and call it to get a new HashMap instance. I then populate the HashMap from the c++ side using jni calls. So far so good.
I then pass the HashMap to java code and, again, all is working as expected. Once the java code has returned I call DeleteLocalRef on the HashMap. No errors are thrown but the reference is not deleted.
This only came up when I finally ran over 512 local references (from multiple calls to this function) and the error dump showed that the last 10 items in the localref store were nearly all HashMaps. I would understand that the GC would not collect these references at the end of the method as I am make a multithreaded ndk app. However the DeleteLocalRef should have worked.
The Fix:
In the end I found that creating the HashMap from a jni call to a java method I wrote was fine, and the reference was then free'able. It seems crazy to have a java function that literally just returns a new HashMap but it worked so for now I am living with it :)
For an Android application, I have implemented an external function in C, which I would like to use in two separate classes.
In the first class (my main Activity UI), I call the appropriate loadLibrary:
System.loadLibrary(...);
In the same class, I define the function as native:
public native int dissectPacket(byte[] header, byte[] data, int encap);
After doing this, I can call the native function with no problem in the first class. I do not get any unsatisfied link error.
Now, I want to use this function in another class. I figure I do not need to load the library again. In the second class, at the bottom, I also define:
public native int dissectPacket(byte[] header, byte[] data, int encap);
However, when I try to use the native function in the second class, I get:
07-22 23:13:13.083: ERROR/AndroidRuntime(6737): Caused by: java.lang.UnsatisfiedLinkError: dissectPacket
What is the proper way to use the function in both classes? If I do not redefine the function as native in the second class (called Packet), I get the error:
The method dissectPacket(byte[], byte[], int) is undefined for the type Packet
BTW, I do NOT want to use: class1.dissectPacket(...); I am trying to avoid passing the class.
You defined actually two separate functions. One for the first class and another one for the second. They will need two separate JNI stubs. You, probably, only have stub and implementation for the first one.
JNI and Java, in general, always refer to methods of the specific class.
"BTW, I do NOT want to use: class1.dissectPacket(...); I am trying to avoid passing the class."
If you want to do that, the member functions need to be static, otherwise the class is implicitly passed as a parameter (I don't know how because I've never done it, static functions have always worked for me, but it has to happen to work properly).
So change your method stubs to:
public static native int dissectPacket(byte[] header, byte[] data, int encap);
I am using the TextToSpeech API and I want to seperate some logic into another class.
In the separate class I have put the following method:
public static void sayHello() {
// Select a random hello.
int helloLength = SoundGameScore.Questions.length;
String hello = SoundGameScore.Questions[currentHelloIndex];
currentHelloIndex = (currentHelloIndex + 1) % helloLength;
mTts.speak(hello, TextToSpeech.QUEUE_FLUSH, // Drop all pending entries
// in the playback queue.
null);
I have then created a variable in the main class: static mainclass object;
Within a button in the main class I call the method through this object by using:
object.sayHello();
I am quite new to android, and I know I am doing something wrong as this gives me a process closed error in the emulator. This also shows a nullexception error in logcat. Please help me, thanks.
I think you are getting a NullPointerException because the reference object is null. You would need to initialise the object in order to call an instance method on it.
However since sayHello() is a static method, you do not need to create an instance of the class in order to call the method. Just use mainclass.sayHello().
Your question and code suggests to me that you do not have much experience with Java. Perhaps you should do some tutorials to brush up on your Java coding before jumping into Android development. For example, Java convention is for class names to be capitalised (MainClass) and for references to have meaningful names (i.e. not things like object).