Calling Java Method via JNI

JNI is killing me! I am trying to call a non-static java method from c++. I am able to call static method without any problems but when I try to call a similar non static method I get a runtime error “Fatal signal 11 (SIGSEGV) at 0x00000004 (code=1).
So pretty much what I did is I looked that the CocosDenshion system so inside SimpleAudioEngineJNI.cpp and I setup a similar system in my class file. Here is what it looks like
<pre>
#include”platform/android/jni/JniHelper.h"

#include <android/log.h>

#define LOG_TAG “libSimpleAudioEngine”
#define LOGD (…) _android_log_print
#define CLASS_NAME “org/rocknrolla/games/Wrapper”
typedef struct JniMethodInfo

{
JNIEnv * env;
jclass classID;
jmethodID methodID;
} JniMethodInfo;

extern “C”
{
// get env and cache it
static JNIEnv* getJNIEnv(void)
{

JavaVM* jvm = cocos2d::JniHelper::getJavaVM();
if (NULL == jvm) {
LOGD ("Failed to get JNIEnv. JniHelper::getJavaVM() is NULL“);
return NULL;
}
JNIEnv env = NULL;
// get jni environment
jint ret = jvm~~>GetEnv&env, JNI_VERSION_1_4);
switch {
case JNI_OK :
// Success!
return env;
case JNI_EDETACHED :
// Thread not attached
// TODO : If calling AttachCurrentThread on a native thread
// must call DetachCurrentThread in future.
// see: http://developer.android.com/guide/practices/design/jni.html
if < 0)
{
LOGD (“Failed to get the environment using AttachCurrentThread()”);
return NULL;
} else {
// Success : Attached and obtained JNIEnv!
return env;
}
case JNI_EVERSION :
// Cannot recover from this error
LOGD (“JNI interface version 1.4 not supported”);
default :
LOGD (“Failed to get the environment using GetEnv()”);
return NULL;
}
}
// get class and make it a global reference, release it at endJni.
static jclass getClassID
{
jclass ret = pEnv~~>FindClass;
if
{
LOGD (“Failed to find class of %s”, CLASS_NAME);
}
return ret;
}
static bool getStaticMethodInfo
{
jmethodID methodID = 0;
JNIEnv
pEnv = 0;
bool bRet = false;
do
{
pEnv = getJNIEnv();
if (! pEnv)
{
break;
}
jclass classID = getClassID(pEnv);
methodID = pEnv~~>GetStaticMethodID;
if
{
LOGD (“Failed to find static method id of %s”, methodName);
break;
}
methodinfo.classID = classID;
methodinfo.env = pEnv;
methodinfo.methodID = methodID;
bRet = true;
} while ;
return bRet;
}
static bool getMethodInfo
{
jmethodID methodID = 0;
JNIEnv *pEnv = 0;
bool bRet = false;
do
{
pEnv = getJNIEnv;
if
{
break;
}
jclass classID = getClassID;
methodID = pEnv~~>GetMethodID(classID, methodName, paramCode);
if (! methodID)
{
LOGD (“Failed to find method id of %s”, methodName);
break;
}
methodinfo.classID = classID;
methodinfo.env = pEnv;
methodinfo.methodID = methodID;

bRet = true;
} while (0);s
return bRet;
}
void upgradeAdFreeJNI()
{
LOGD (“upgradeAdFreeJNI”);
JniMethodInfo methodInfo;
if (! getStaticMethodInfo(methodInfo,”upgradeAdFree“,”()V“))
{
return;
}
methodInfo.env~~>CallVoidMethod;
methodInfo.env~~>DeleteLocalRef(methodInfo.classID);
}
}
</pre>
My java file is also brutally simple and I am just trying to get at the apps main activity:
<pre>
package org.rocknrolla.games;
import org.cocos2dx.lib.Cocos2dxActivity;
import android.os.Bundle;
import android.widget.LinearLayout;
import org.rocknrolla.games.util.*;
public class Wrapper extends Cocos2dxActivity{
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);

}

// User clicked the”Upgrade to Premium" button.
public void upgradeAdFree() {
Log.d(TAG, “Upgrade button clicked; launching purchase flow for upgrade.”);
}
}

Any ideas I’ve looked online and it seems like I have the correct setup. In fact I placed logs in my function upgradeAdFreeJNI() to see where it is holding up and it fails at this call methodInfo.env-\>CallVoidMethod(methodInfo.classID, methodInfo.methodID);.

Anyone else been able to call a non static method?

Thanks

Wanted to do a quick update about this. I was looking around online for some clear documentation on doing this (nothing was found). But I did seem to figure out that you need a instance parameter and not the classID parameter found in the JniMethodInfo struct. So what I ended up doing was calling a C*+ function from the onCreate in my main activity in hopes to get the instance parameter and then I would cache this for reuse. The Problem I run into is that I can’t seem to store it in a variable to use later and when I try to call the java method I get the following error:
JNI ERROR : accessed stale local reference 0x19800019
VM aborting
Fatal signal 11 at 0xdead00d
Here is my code:
C*+

extern "C"
{
jobject javaObj;

     JNIEXPORT void JNICALL Java_org_testing_Wrapper_initJNIBridge(JNIEnv *, jobject jobj){
        LOGD("Java_org_testing_Wrapper_initJNIBridge()");

        javaObj = jobj;

        return;
    }

    void upgradeAdFreeJNI()
    {      
        LOGD("upgradeAdFree");

        cocos2d::JniMethodInfo methodInfo;

        if(!cocos2d::JniHelper::getMethodInfo(methodInfo, "org/rocknrolla/games/Wrapper", "upgradeAdFree", "()V"))
        {
            return;
        }

        methodInfo.env->CallVoidMethod(javaObj, methodInfo.methodID);
    }

This is the java function I am trying to call:

public void upgradeAdFree() {
        Log.d(TAG, "Wrapper::upgradeAdFree()");
        // TODO: do sstuff.
}

Now I know that I can call the static function because I did this in the JNIEXPORT void JNICALL Java_org_testing_Wrapper_initJNIBridge(…) function:

JNIEXPORT void JNICALL Java_org_rocknrolla_games_Wrapper_initJNIBridge(JNIEnv *, jobject jobj){
        LOGD("Java_org_rocknrolla_games_Wrapper_printTest()");

        javaObj = jobj;

                cocos2d::JniMethodInfo methodInfo;

        if(!cocos2d::JniHelper::getMethodInfo(methodInfo, "org/rocknrolla/games/Wrapper", "upgradeAdFree", "()V"))
        {
            return;
        }

        methodInfo.env->CallVoidMethod(javaObj, methodInfo.methodID);

        return;
    }

I had no prblems with this code it executed without a crash. So I guess it boils down to 2 questions is there a better way of setting myself up to call non-static java methods for c++? OR How can I store the jobject without it going “stale” as the error says?

Thanks!

Hi just wanted to do a quick update to this. I found the issue to this so what I had to do on the C++ side was to call NewGlobalRef on the jObj I recieve heres the code:

    JNIEXPORT void JNICALL Java_org_test_Wrapper_initBridge(JNIEnv *env, jobject jobj){
        javaObj = env->NewGlobalRef(jobj);

        return;
    }

So java side calls the native function above and I cache the jobj into the javaObj. This will allow me to call a non-static method later on.

Thank you Jakub,
this post contains so mutch usefull informations and this is exactly what I was searching. I wonder why I was not able to find something similar in a blog post or a tutorial. Anyway your solution solved my problem.

javaObj = env->NewGlobalRef(jobj);

One suggestion for your code… you can get the class of an object using:

jclass javaActivityClass = jniEnvironment->GetObjectClass(javaObj);

With this line of code you can avoid using the CLASS_NAME to make namespace changes possible.
Have fun.