-
Notifications
You must be signed in to change notification settings - Fork 6
Implementing Native Methods
One of the most common tasks when writing JNI code is to implement native
Java methods. SimpleJNI makes it very easy and straightforward. Make sure you read about Declaring Java Types and Representing Java Classes before reading this page; it builds upon information explained there.
There are 2 ways of implementing native methods in raw JNI. One is by giving special names to methods exposed from a shared library. Another is by manually registering implementations of Java methods. The first approach can be used with SimpleJNI, if desired, but it has many known deficiencies and is not generally recommended. SimpleJNI provides direct support for manual registration.
In order to register a native method using SimpleJNI you will need to
-
Define all the Java types used in method signature as described in Declaring Java Types. For example if the method you are implementing uses java.util.Map as an argument or return value, you will need to do something along the lines of
DEFINE_JAVA_TYPE(jMap, "java.util.Map");
-
Declare the C++ implementation using the precise mapping of Java types to C++. The C++ method will have 2 parameters in addition to whatever parameters Java method takes. For static methods these will be:
JNIEnv *, jclass
For instance methods these will be
JNIEnv *, <mapped type you defined>
For example consider
class Something { native boolean instanceMethod(int i, Map m); static native boolean staticMethod(int i, Map m); }
These will require the following C++ declarations
DEFINE_JAVA_TYPE(jMap, "java.util.Map"); DEFINE_JAVA_TYPE(jSomething, "com.mystuff.Something"); jboolean instanceMethod(JNIEnv * env, jSomething obj, jint i, jMap m); jboolean staticMethod(JNIEnv * env, jclass cls, jint i, jMap m);
-
Obtain a
java_class<type>
instance for the type your methods belong to somehow. See Representing Java Classes for various ways to do so. -
Register C++ methods. For the example above you would do something like this
java_class<jSomething> cls = ...; cls.register_natives(env, { bind_native("instanceMethod", instanceMethod), bind_native("staticMethod", staticMethod) });
Note that, as usual with SimpleJNI, registering only requires the simple name of Java method. You do not need to to know the encoded signature.
Similar to other reasons given in Representing Java Classes it is usually advantageous to perform the registration together with all other Java resolving from within JNI_OnLoad
. You can accomplish this by putting the registration code into the constructor of the C++ representation class. The representation class also provides a good scope to put the native implementations in. For the example above
class ClassOfSomething : public java_runtime::simple_java_class<jSomething>
{
ClassOfSomething(JNIEnv * env):
simple_java_class(env)
{
register_natives(env, {
bind_native("instanceMethod", instanceMethod),
bind_native("staticMethod", staticMethod)
});
}
static jboolean instanceMethod(JNIEnv * env, jSomething obj, jint i, jMap m);
static jboolean staticMethod(JNIEnv * env, jclass cls, jint i, jMap m);
};
jboolean ClassOfSomething::instanceMethod(JNIEnv * env, jSomething obj, jint i, jMap m)
{
//implementation
}
jboolean ClassOfSomething::staticMethod(JNIEnv * env, jclass cls, jint i, jMap m)
{
//implementation
}
register_natives
deals with overloads transparently on the Java side but if you overload C++ implementations you might want to manually disambiguate them. Consider the following Java class
class Something
{
native void method();
native void method(int i);
static native void method(float f);
}
On the C++ side you can use different names like this
class ClassOfSomething : public java_runtime::simple_java_class<jSomething>
{
ClassOfSomething(JNIEnv * env):
simple_java_class(env)
{
register_natives(env, {
bind_native("method", instanceMethod1);
bind_native("method", instanceMethod2);
bind_native("method", staticMethod);
});
}
static void instanceMethod1(JNIEnv * env, jSomething obj);
static void instanceMethod2(JNIEnv * env, jSomething obj, jint);
static void staticMethod(JNIEnv * env, jclass cls, jfloat);
};
Note that Java name given in registration is the same. SimpleJNI figures out which Java method to use from your C++ method signature.
If you overload C++ methods too you will get a compilation error since C++ doesn't know which method
you meant to pass on its own. You will need to manually disambiguate function pointers which is exceedingly unpleasant in C++. Here is what the casts would look like
class ClassOfSomething : public java_runtime::simple_java_class<jSomething>
{
ClassOfSomething(JNIEnv * env):
simple_java_class(env)
{
register_natives(env, {
bind_native("method", (void (*)(JNIEnv *, jSomething))(method));
bind_native("method", (void (*)(JNIEnv *, jSomething, jint))(method));
bind_native("method", (void (*)(JNIEnv *, jclass, jfloat))(method));
});
}
static void method(JNIEnv * env, jSomething obj);
static void method(JNIEnv * env, jSomething obj, jint);
static void method(JNIEnv * env, jclass cls, jfloat);
};
Best practice is to simply use different names and not to deal with this issue.
- 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