-
Notifications
You must be signed in to change notification settings - Fork 6
Representing Java Classes
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).
First of all here is how to get a smart reference to jclass
JNIEnv * env = ...;
//Exact equivalent of JNI's FindClass. Returns null pointer on failure
local_java_ref<jclass> cls = java_runtime::find_class<jSomething>(env);
if (cls)
{
...
}
//Exact equivalent of JNI's FindClass. Throws on failure and never returns null.
local_java_ref<jclass> cls = java_runtime::get_class<jSomething>(env);
This reference can be used to initialize java_class<jSomething>
wrapper like this
java_class<jSomething> cls(env, [] (JNIEnv * env) {
java_runtime::get_class<jSomething>(env)
});
If your class comes from some custom loading method you can do something else in the lambda
java_class<jSomething> cls(env, [] (JNIEnv * env) {
//Load class here using your custom way
return ...local_java_ref<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
.
Also note that the loading code is passed to java_class
constructor as a lambda. The lambda will be invoked only if the class needs to be loaded and the constructor cannot fetch it more efficiently.
All instances of java_class<T>
share the same jclass
pointer. As long as one instance of java_class<T>
exists all newly created ones will be able to reuse the previously loaded one.
Unless you are doing something one time accessing classes on-demand as shown above is problematic, however. You risk spending 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.
package com.mystuff;
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")
{
register_natives(env, {
bind_native("nativeMethod", nativeMethod)
});
}
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. SimpleJNI provides a tool - JniGen to automatically generate code like this from annotations on your Java classes.
- Building
-
User's Guide
Declaring Java Types
Accessing Methods and Fields
Representing Java Classes
Implementing Native Methods
Smart References
Error Handling
Obtaining JNIEnv
Initialization
Strings
Arrays
Direct Buffers
Booleans
Sizes -
JniGen Code Generator
Integrating JniGen
Annotations
Processor Options