Skip to content

Representing Java Classes

gershnik-smartsheet edited this page May 22, 2016 · 9 revisions

Once you declared Java types as is explained in Declaring Java Types you will need to use the corresponding Java "class object" (represented by the jclass type in raw JNI). Following the SmJNI philosophy there are multiple ways of doing so from very low level (barely above raw JNI) to a very high. At the simplest level you can do this

JNIEnv * env = ...;

//Method 1. Exact equivalent of JNI's FindClass. Returns null-like object on failure
java_class<jSomething> cls = java_class<jSomething>::find(env);
if (cls)
{
    ...
}

//Method 2. Recommended if you want simple loading. This will throw exception on failure
java_class<jSomething> cls = java_runtime::find_class<jSomething>(env);

//Method 3. Advanced. Load using your own custom way of loading things
java_class<jSomething> cls(env, [] (JNIEnv * env) {
    //Load class here using your custom way
    return ...jclass object...;
});

Note that you don't need to pass any class names in. You already declared what the class name is when you defined jSomething.

Unless you are doing something one time accessing classes this way is wasteful and inefficient, however. You will spend time locating the class object every time this code is run. If you made a mistake you will only discover it when this code is run which might be a rare or hard to reproduce scenario. A better approach in almost all cases is to use early binding. This essentially mimics how native dynamic libraries are loaded by the OS loader. Everything is located and hooked up at once in JNI_OnLoad call. Let's use this approach for jSomething and jSomethingElse.

//Declare designated types to represent each class object

struct ClassOfSomething : public java_runtime::simple_java_class<jSomething>
{
    ClassOfSomething(JNIEnv * env):
        simple_java_class(env)
    {} 
};

struct ClassOfSomethingElse : public java_runtime::simple_java_class<jSomethingElse>
{
    ClassOfSomethingElse(JNIEnv * env):
        simple_java_class(env)
    {} 
};

//"Table" of all classes we use
typedef java_class_table<ClassOfSomething, ClassOfSomethingElse> java_classes;

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved)
{
    ...other things...

    //Load everything in the table
    java_classes::init(env);

   
    ...other things...
}

java_runtime::simple_java_class inherits from java_class and can be used anywhere java_class objects are needed. The java_class_table accepts a variable number of template arguments. You can put all the classes you need there. See Initialization for details about how to write JNI_OnLoad. With this in place you can easily get to a given class from anywhere like this

const auto & cls = java_classes::get<ClassOfSomething>();

In addition to convenience the power of this approach comes from the fact that you can also store and initialize methods and fields objects in the custom classes as well as register your native methods implementation. Consider the following Java class.

class Something
{
    void javaMethod();
    native void nativeMethod();
}

This can be represented as follows

DEFINE_JAVA_TYPE(jSomething,       "com.mystuff.Something");

class ClassOfSomething : public java_runtime::simple_java_class<jSomething>
{
    ClassOfSomething(JNIEnv * env):
        simple_java_class(env),
        javaMethod(env, *this, "javaMethod")
    {
        java_registration<jSomething> registration;
        registration.add_instance_method("nativeMethod", nativeMethod);
        registration.perform(env, *this);
    } 

    static void JNICALL nativeMethod(JNIEnv * env, jSomething self);

    const java_method<void, jSomething> javaMethod;
};

typedef java_class_table<ClassOfSomething, ...other classes...> java_classes;

...

//Call javaMethod on some object
JNIEnv * env = ...;
jSomething obj = ...;
java_classes::get<ClassOfSomething>().javaMethod(env, obj);

//Implement native method
void JNICALL ClassOfSomething::nativeMethod(JNIEnv * env, jSomething self)
{
    ...
}

When java_classes type is initialized the constructor of ClassOfSomething will run, locating the class object, resolving javaMethod and registering nativeMethod implementation. If any of these will fail an exception will be reported there and then. If all succeed you will be able to easily access the class and all Java methods anywhere in your native code without any more runtime lookups. More details about implementing native Java methods and registering them can be found in Implementing Native Methods.

Also note that the structure of ClassOfSomething is extremely regular corresponding in a straightforward manner to declaration of the Java class Something it represents. This makes it easy to write for any Java class you need in your code. In the future it will be possible to create a tool that will generate C++ representation classes automatically from Java declarations.

Clone this wiki locally