Friday, December 28, 2012

Using JNI_OnLoad() in Adroid NDK development




Previous parts

  Last time I wrote how to combine C and Java for Android app to load textures. I also mentioned I will write some short article on how and why to use JNI_OnLOad() method. So, here it is.

JNI_OnLoad()  - am I forced to use it?

 The answer is: no. If you do not want you do not have to implement this method. But if you do so you can  gain some benefits from it. These benefits includes java class instance caching and native method registration.
 The JNI_OnLoad is called when the native library is loaded. If you do not implement this method then you can see "No JNI_OnLoad found in ..." message in your Logcat view. This is not error just debug message. So if your code does not work the reason is not because of this message...

 If you did not implement JNI_OnLoad() and did not register your native methods, you have probably something like this in your code:

jstring Java_com_example_hellojni_HelloJni_stringFromJNI(JNIEnv* env, jobject thiz)
{
    return (*env)->NewStringUTF(env, "Hello from JNI !");
}

 The example was taken from Android NDK example hello-jni.c. In the name of the method you have to put in "Java_". Then the whole package name "com.example.hellojni" but with dots replaced with underscores. Then class name "HelloJni" and finally the name of the native method. Of course, you can use javah.exe to generate this for you but it would be better to bypass it somehow.

Implementation

 Here is the implementation of JNI_OnLoad() as I have it in my engine:

extern "C"
{
JavaVM* gJavaVM = NULL;
jobject gJavaActivityClass;
const char* kJavActivityClassPath = "com/sbcgames/sbcengine/SBCEngine";

static JNINativeMethod methodTable[] = {
  {"engine_tick", "()V", (void *) engine_tick},
  {"engine_start", "(Landroid/content/res/AssetManager;)V", (void *) engine_start},
  {"engine_stop", "(Z)V", (void *) engine_stop},
  {"engine_pause", "()V", (void *) engine_pause},
  {"engine_resume", "()V", (void *) engine_resume},
  {"engine_message", "(III)V", (void *) engine_message},
  {"engine_set_screen_size", "(II)V", (void *) engine_set_screen_size},
  {"engine_on_touch", "(III)V", (void *) engine_on_touch},
};

//------------------------------------------------------------------------
jint JNI_OnLoad(JavaVM* aVm, void* aReserved)
{
 // cache java VM
 gJavaVM = aVm;

 JNIEnv* env;
 if (aVm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK)
 {
  LOGE("Failed to get the environment");
  return -1;
 }

 // Get SBCEngine activity class
 jclass activityClass = env->FindClass(kJavActivityClassPath);
 if (!activityClass)
 {
  LOGE("failed to get %s class reference", kJavActivityClassPath);
  return -1;
 }
 gJavaActivityClass = env->NewGlobalRef(activityClass);

 // Register methods with env->RegisterNatives.
 env->RegisterNatives(activityClass, methodTable, sizeof(methodTable) / sizeof(methodTable[0]));

 return JNI_VERSION_1_6;
}

} // extern "C"
 
 On the top there are two variables: gJavaVM and gJavaActivityClass (g means these methods are global). In these variables I will cache object that I use during game in it (remember using it in previous article on loading textures).

 Next is constant string that contains Java package name and Java class that contains native methods. The dots are replaced with slashes.

 It is followed with table of native methods. It is not important what each of these methods do in the engine. What is important is the structure. For each method there are three attributes:
  • the method name - the same as in Java
  • the method signature - the signature follows the rules for JNI Types and Data structures (see detailed doc at Oracle)
  • the method function pointer - the void* pointer to C implementation of the method
 The method itself first caches java virtual machine and engine activity class. Then it simply registers all the native methods with RegisterNatives() call. For example the engine_stop method that returns void and takes bool does not need to look like this more:
void Java_com_sbcgames_sbcengine_SBCEngine_engine_stop(
        JNIEnv* aEnv, jobject aObj, jboolean aTerminating)
{
  :
  :
}

 but it can look as friendly as this:

void engine_stop(JNIEnv* aEnv, jobject aObj, jboolean aTerminating)
{
  :
  :
}

 However, note that every single native method takes as its first two arguments "JNIEnv* aEnv" and  "jobject aObj". These arguments are not part of method signature in registration table but you have to include them.