Skip to content

Initialization

Eugene Gershnik edited this page Oct 9, 2021 · 3 revisions

JNI can operate in 2 modes:

  • Native code loaded from Java
  • Java VM loaded from native code The first scenario is by far the most common, but SimpleJNI can work in either. In both case SimpleJNI needs to be properly initialized. Doing so is easy but is important to get it, especially the error handling part right. If you haven't already done so please read Error Handling section that explain how SimpleJNI reports errors in general. In either scenario SimpleJNI initialization consists of two parts
  1. Initialize SimpleJNI underlying machinery
  2. Initialize any SimpleJNI global data, most notably java_class_table (see Representing Java Classes) The two parts differ in how you can react to any exceptions reported. if there is any exception reported at stage 1 you cannot use SimpleJNI to handle it. After all it hadn't successfully been initialized. At stage 2 you can use the same error handling facilities as you do elsewhere. SimpleJNI itself is available.

The stage 1 initialization consist of at least the following calls

JavaVM * vm = ...;

//Initialize jni_provider. 
jni_provider::init(vm);
//Now we can get JNIEnv anywhere
JNIEnv * env = jni_provider::get_jni();
//Initialize core Java support
java_runtime::init(env);

The stage 2 initialization is usually an initialization of java_class_table as described in Representing Java Classes

Native code loaded from Java

When JNI shared library is loaded code JNI calls JNI_OnLoad exported function where your code can initialize itself. SimpleJNI is best initialized there too. Note that JNI_OnLoad can fail, including failing with Java exception, that will be reported to Java code attempting to load your library. The general structure of JNI_OnLoad should be along these lines.

typedef java_class_table<...> java_classes;

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved)
{
    try
    {
        jni_provider::init(vm);
        JNIEnv * env = jni_provider::get_jni();
        java_runtime::init(env);
        
        try
        {
            //Let's load everything we need
            java_classes::init(env);
            return JNI_VERSION_1_6;
        }
        catch(java_exception & ex)
        {
            //pass any Java exception back to Java
            ex.raise(env);
        }
        catch(std::exception & ex)
        {
            //Translate any other error
            //This will use built-in translation. This is fine for simple cases
            //but for more complicated scenarios you might want to write your
            //own translation logic
            java_exception::translate(env, ex);
        }
    }
    catch(std::exception & ex)
    {
    	//If we are here there is no way to communicate with
    	//Java - something really bad happened.
    	//Let's just log and report failure
        __android_log_print(ANDROID_LOG_ERROR, "mystuff", "%s\n", ex.what());
    }
    return 0;
}

The example above uses Android's __android_log_print for failure logging. Substitute as needed for your environment.

Java VM loaded from native code

When you load Java VM from native code yourself the flow is similar to the one above. Your loading will produce JavaVM * that you can use as above. The only difference is that there is no Java code above to report exception so you are probably better off doing uniform error handling.

JavaVM * vm = ...;
try
{
    jni_provider::init(vm);
    JNIEnv * env = jni_provider::get_jni();
    java_runtime::init(env);
        
    //Let's load everything we need
    java_classes::init(env);
}
catch(std::exception & ex)
{
    //alternatively let the exception terminate the process
    fprintf(stderr, "%s\n", ex.what());
    abort();
}