Issue with instance and class variable states when using JNI - android

I'm currently writing some Android code that uses JNI and I'm having difficulty how class and instance variables work. If I execute the following code I would expect the code to print a value of "18" but I always receive a value of "0". Can someone explain what I'm doing wrong?
// Java code
SampleClass sc = new SampleClass(18);
sc.printId() // returns 18, as expected
sc.nativePrintId() // returns 0, why?!
// Java Class
public class SampleClass
{
private int mId = -1;
public FFmpegMediaPlayer(int id) {
mId = id;
}
public void printId() {
System.out.println("id: " + mId);
}
public native void nativePrintId();
}
// JNI C++ code
static void nativePrintId(JNIEnv* env, jobject thiz)
{
jclass clazz = env->FindClass("wseemann/media/SampleClass");
jmethodID printId = env->GetMethodID(clazz, "printId", "()V");
env->CallVoidMethod(clazz, printId); // always prints zero?
}

You must pass the object, not the class, to CallVoidMethod.
Use:
env->CallVoidMethod(thiz, printId);
Also, you should get the class from the object, not from FindClass.
Use:
jclass clazz = env->GetObjectClass(thiz);

Related

JNI on Android: Callback from native with Integer parameter works, with String or Void it doesn't. Why?

I have a callback class for doing callbacks from native C++ code to Kotlin (not sure if Kotlin/Java makes a difference here, if so, is there any documentation on that?).
I have a working callback with an integer parameter, that I call from different native threads without a problem. Now I want to add a second one that sends a String, but for some reason that doesn't work.
My callback class implementation looks like this:
jclass target;
jmethodID id;
Callback::Callback(JavaVM &jvm, jobject object) : g_jvm(jvm), g_object(object) {
JNIEnv *g_env;
int getEnvStat = g_jvm.GetEnv((void **) &g_env, JNI_VERSION_1_6);
if (g_env != NULL) {
target = g_env->GetObjectClass(g_object);
id = g_env->GetMethodID(target, "integerCallback", "(I)V");
}
}
void Callback::call(int integerValue, const char *stringValue) {
JNIEnv *g_env;
int getEnvStat = g_jvm.GetEnv((void **) &g_env, JNI_VERSION_1_6);
if (getEnvStat == JNI_EDETACHED) {
if (g_jvm.AttachCurrentThread(&g_env, NULL) != 0) {
LOGD("GetEnv: Failed to attach");
}
} else if (getEnvStat == JNI_OK) {
LOGD("GetEnv: JNI_OK");
} else if (getEnvStat == JNI_EVERSION) {
LOGD("GetEnv: version not supported");
}
g_env->CallVoidMethod(g_object, id, (jint) integerValue);
}
It gets instantiated in my native-lib.cpplike this:
extern "C" {
std::unique_ptr<Callback> callback;
JavaVM *g_jvm = nullptr;
static jobject myJNIClass;
jint JNI_OnLoad(JavaVM *pJvm, void *reserved) {
g_jvm = pJvm;
return JNI_VERSION_1_6;
}
JNIEXPORT void JNICALL
Java_com_my_app_common_jni_JniBridge_loadNative(JNIEnv *env, jobject instance,
jstring URI, jboolean isWaitMode) {
myJNIClass = env->NewGlobalRef(instance);
if (callback == nullptr) {
callback = std::make_unique<Callback>(*g_jvm, myJNIClass);
}
}
The callback method it talks to is in my JniBridge.kt (the threading part is probbaly irrelevant to the problem):
object JniBridge {
init {
System.loadLibrary("native-lib")
Timber.d("Native Lib loaded!")
}
fun load(fileName: String, isWaitMode: Boolean) {
loadNative(fileName, isWaitMode)
}
fun integerCallback(value: Int) {
someListener?.invoke(value)
}
private external fun loadNative(fileName: String, isWaitMode: Boolean)
}
So now if my native code triggers the call() method in my callback, my integerCallback() in JniBridge.kt gets called correctly with an integer.
But here's what I don't get: If I change my callback to send a String it doesn't work. If I change it like this:
// in JniBridge.kt
fun stringCallback(value: String) {
someListener?.invoke(value)
}
// in Callback.cpp
//getting the method
id = g_env->GetMethodID(target, "stringCallback", "(Ljava/lang/String)V");
//making the call
g_env->CallVoidMethod(g_object, id, (jstring) stringValue);
Now my app crashes with this error:
JNI DETECTED ERROR IN APPLICATION: JNI GetStringUTFChars called with pending exception java.lang.NoSuchMethodError: no non-static method "Lcom/my/app/common/jni/JniBridge;.stringCallback(Ljava/lang/String)V"
The same happens if I try calling a void method (like this: id = g_env->GetMethodID(target, "voidCallback", "(V)V");or calling one that takes two integers (like this: id = g_env->GetMethodID(target, "twoIntegerCallback", "(I;I)V");, of course with having the corresponding methods in JniBridge.kt in place.
Why does this happen and how can I fix it?
Note: For clarity I have omitted all parts of my code that I believe are not related to the problem, if something crucial is missing, please let me know and I fix it.
You're missing a semi-colon in the method signature that you pass to GetMethodID.
It should be:
id = g_env->GetMethodID(target, "stringCallback", "(Ljava/lang/String;)V");
Note the semi-colon after Ljava/lang/String.
See the part about Type Signatures in Oracle's JNI documentation.
Regarding your other variations:
A Java void function() would have the signature ()V.
A Java void function(int i, int j) would have the signature (II)V.
As a side note, you really ought to verify the return value and check for exceptions after every JNI call.

JNI Method not found

I have been receiving this error for my JNI code while I tried find the method ,using GetMethodID, my Java method is in an Interface.
Here is my interface
public interface printReader
{
public printImg readerPrint(String selectedName) throws Exception;
}
Native code
WprintImgIMPL.h
class WprintImgIMPL: public IWprintReader {
public:
WprintImgIMPL(JNIEnv *env, jobject obj);
~WprintImgIMPL(void);
virtual WprintImg readerPrint(char* readerName) ;
.....
.....
private:
JNIEnv *m_Env;
jobject m_jObj;
}
WprintImgIMPL.cpp
WprintImg WprintImgIMPL::readerPrint(char* readerName) {
jclass cls = m_Env->GetObjectClass (m_jObj);
jmethodID mid = m_Env->GetMethodID (cls, "readerPrint", "(Ljava/lang/String;)Lcom/site/name/printImg;");
.......
.......
}
Java code
public class printReaderIMPL implements printReader {
static final String DEBUG_TAG = "";
android.net.wifi.WifiManager.MulticastLock lock;
Context _context;
public printReaderIMPL (Context context) {
_context = context;
}
#Override
public printImg readerPrint(String selectedName) throws Exception {
Log.e(DEBUG_TAG, "readerPrint");
}
}
Constructor/destructor
WprintImgIMPL(JNIEnv *env, jobject obj){
m_Env = env;
m_jobj = env->NewGlobalRef(obj);
}
~WprintImgIMPL(void) {
m_Env->DeleteGlobalRef(m_jobj);
}
Error: GetMethodID: method not found: Lcom/site/name/NativeCode;.printImg:(Ljava/lang/String;)Lcom/site/name/printImg;
Signature are checked twice , after failure I generated again using Javap tool .
Thank you if you can input /comment and help in fixing this bug.
It is invalid to save a JNIEnv* across JNI method calls. It's only valid for the duration of the JNI method you are currently in. Out of the blue, e.g. in arbitrary C++ code, you need to call AttachCurrentThread() to get a current valid JNIEnv*.
However you can cache the methodID. There no need to look it up every time. Look it up in your constructor.

Fill java class members with c structure members

I have a code something similar to this
struct time
{
long milliscnds;
int secs;
}
In my java file , I had something like this
class jtime
{
long millscnds;
int secs;
}
new jtime time = new jtime();
public int native getTimeFromC(object time);
in native class
getTimeFromc(JNIEnv* env, jobject thiz,jobject jtime)
{
struct time *mytime = getTime();
now to fill the jtime with mytime
}
Suggestions please?
You can simplify your Java class and the required JNI code.
Currently, your native method has some issues:
public int native getTimeFromC(object time);
Parameter is Object but should be jtime.
Return value doesn't seem to have a purpose.
Since the method completely initializes a jtime object, why not create and return a jtime object?
This class definition has a factory method to create the object and a constructor that moves some the initialization work over from the JNI side.
public class jtime {
long millscnds;
int secs;
public jtime(long millscnds, int secs) {
this.millscnds = millscnds;
this.secs = secs;
}
public native static jtime FromC();
}
The factory method can be implemented like this:
JNIEXPORT jobject JNICALL Java_jtime_FromC
(JNIEnv * env, jclass clazz)
{
struct time *mytime = getTime();
jmethodID ctor = (*env)->GetMethodID(env, clazz, "<init>", "(JI)V");
jobject obj = (*env)->NewObject(env, clazz, ctor, mytime->milliscnds, mytime->secs);
return obj;
}
Tip: The javap tool is like javah but shows the signatures of non-native methods. Using javap -s jtime, you can see the signature of the constructor.
Something like the following:
void getTimeFromc(JNIEnv* env, jobject thiz, jobject jtime)
{
struct time *mytime = getTime();
// now to fill the jtime with mytime
jclass jtimeClazz = (*env)->GetObjectClass(jtime); // Get the class for the jtime object
// get the field IDs for the two instance fields
jfieldID millscndsFieldId = (*env)->GetFieldID(jtimeClazz, "milliscnds", "J"); // 'J' is the JNI type signature for long
jfieldID secsFieldId = (*env)->GetFieldID(jtimeClazz, "secs", "I"); // 'I' is the JNI type signature for int
// set the fields
(*env)->SetLongField(jtime, millscndsFieldId, (jlong)mytime.milliscnds);
(*env)->SetIntField(jtime, secsFieldId, (jint)mytime.secs);
}
Ideally you should cache the values of millscndsFieldId and secsFieldId as they won't change during execution (and you could also cache jtimeClazz if you NewGlobalRef it).
All JNI functions are documented here: http://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/functions.html

JNI call fail on non static methods

I'm trying to call a non static java method from C++ using the JNI on an Android project, but GetMethodID always return NULL.
this is my code:
void Java_com_kungfu_rabbit_KungFuRabbitActivity_nativeOnCreate(JNIEnv *env, jobject obj)
{
// this code works fine
jclass cls = env->FindClass("com/kungfu/rabbit/KungFuRabbitActivity");
jmethodID mid = env->GetStaticMethodID(cls, "foo", "()V");
env->CallStaticVoidMethod(cls, mid);
// this one fails:
jclass cls = env->GetObjectClass(obj);
jmethodID mid = env->GetMethodID(cls, "foo2", "()V");
env->CallVoidMethod(obj, mid);
}
I'm calling this native function from the class that extends Activity. foo is a public static void function, and foo2 is a public void function.
I can't understand why it fails...
Can anyone help me to understand?
Thanks in advance

Android - JNI NewObject() does not save values when passing it to Java

I am using native code to create an object which I then pass as an argument to a method I call.
Nothing appears to go wrong in native code, but when I get the call in Java, the object seems to have nulled values.
Here's the code of the java object I create in native:
package org.test;
import java.util.ArrayList;
public class JNITeam {
public int mTeamID;
public String mTeamName;
private ArrayList<String> mMembers;
public JNITeam(int id, String name) {
mTeamID = id;
mTeamName = name;
}
public void addMember(String name) {
mMembers.add(name);
}
}
Here's the native code used to create an instance of the class and pass it up to the Java method "onGetTeam", which takes an instance of the above class as a parameter. It is run from a thread created in Native code, hence I have to attach the thread.
JNIEnv* jenv = 0;
clientHandle->runningJVM->AttachCurrentThread(&jenv,0);
if (!jenv)
__android_log_print(ANDROID_LOG_INFO, ANDROID_DEBUG_TAG, "jenv is null");
jclass cls = jenv->GetObjectClass(clientHandle->job);
if (!cls)
__android_log_print(ANDROID_LOG_INFO, ANDROID_DEBUG_TAG, "cls is null");
jmethodID constructor = jenv->GetMethodID(clientHandle->JNITeamCls, "<init>", "(ILjava/lang/String;)V");
jint teamID = 2;
jstring js = jenv->NewStringUTF("test");
jobject dataObject = jenv->NewObject(clientHandle->JNITeamCls, constructor, teamID, js);
if (!dataObject)
__android_log_print(ANDROID_LOG_INFO, ANDROID_DEBUG_TAG, "dataobject is null");
if (jenv && cls && dataObject) {
jmethodID mid = jenv->GetMethodID(cls,"onGetTeam","(Lorg/test/JNITeam;)V");
if (mid) {
jenv->CallVoidMethod(clientHandle->job,mid);
}
else {
__android_log_print(ANDROID_LOG_INFO, ANDROID_DEBUG_TAG, "mid is null");
}
}
I do not want the object to be persistent; I want it to ony be valid during the call to Java, then it can be garbage-collected. However its data fields - that are set in the constructor - are just null when I call it, Why?
You're not passing the object you constructed to the method (or indeed doing anything with it).
Instead of
jenv->CallVoidMethod(clientHandle->job,mid);
don't you want
jenv->CallVoidMethod(clientHandle->job,mid,dataObject);
?
Also you're not checking whether that call succeeded or not.

Categories

Resources