C using JNI in connection with Using Go to call Java libraries that I talked about at TechTalk. I will explain how to call Java from.
Java Native Interface(JNI)
JNI is a mechanism that allows you to use Java libraries from C and vice versa. The official documentation can be found in the Java Native Interface Specification Table of Contents (https://docs.oracle.com/javase/jp/8/docs/technotes/guides/jni/spec/jniTOC.html). This article focuses on calling methods in the direction of using Java from C.
JNI loads the JVM on a native app and runs Java code on it.
You can create a JVM with JNI_CreateJavaVM ()
.
include <jni.h>
JNIEnv *env;
JavaVM *jvm;
// JVM startup options, etc.
JavaVMInitArgs vm_args;
vm_args.version = JNI_VERSION_1_8;
JNI_GetDefaultJavaVMInitArgs(&vm_args);
// load the JVM
JNI_CreateJavaVM(&jvm, (void **)&env, &vm_args);
I wondered if I should call JNI_DestroyJavaVM ()
when I finished using it, [Call API](https://docs.oracle.com/javase/jp/8/docs/technotes/guides/jni/spec/ invocation.html # unloading_the_vm) says "VM unloading is not supported."
Certainly, if you try to reload after unloading, you will get an error (JNI_EEXIST
). That doesn't mean you need to route the jvm
variable. Attach and use the JVM loaded in the same process.
// Get the Java VM loaded in the past
JNI_GetCreatedJavaVMs(&jvm, 1, NULL);
// Attach to get JNIEnv
(*jvm)->AttachCurrentThread(jvm, (void **)&env,NULL);
For the method call, specify the method name and its signature (all argument and return types, details will be described later). Since Java is a language that can overload methods, the process to be called is uniquely determined by the set of argument types and method names.
First, get the method ID that indicates the process to be executed by GetMethodID ()
or GetStaticMethodID ()
. Only the constructor gets the special method name <init>
with GetMethodID ()
. Others are specified according to the method definition.
The following example is an example of executing the static method hello ()
of the self-made class Test
.
// First, find the applicable class
jclass clazz = (*env)->FindClass(env, "Test");
// Get the method ID, static method with no arguments and return value void in this example
jmethodID id = (*env)->GetStaticMethodID(env, clazz, "hello", "()V");
Next, execute the following routine that takes the obtained method ID as an argument. <Type>
contains a character string according to the return value such as Void
or ʻObject. For each suffix with ʻA
, pass the argument as an array, and for V
, pass it as va_list
. No mark takes a variadic argument.
NewObject ()
for instantiationCall <type> Method ()
CallStatic <type> Method
If you call hello ()
earlier, it will look like this.
// Call the method by passing the class, method and arguments obtained above.
(*env)->CallStaticVoidMethodA(env, clazz, id, NULL);
When calling a method, a Java object may be returned as an argument or return value. Each primitive type has a corresponding C type, and reference type variables (instances and arrays) are represented as references in the JVM with the type jobject. All of these types are members of the jvalue union. The table for each size etc. can be found in JNI Type and Data Structure. There is.
typedef union jvalue {
jboolean z;
jbyte b;
jchar c;
jshort s;
jint i;
jlong j;
jfloat f;
jdouble d;
jobject l;
} jvalue;
Signatures are a way to represent Java types. All patterns are shown in the table below. Arrays and methods are a combination of other types, and fully qualified classes specify the package delimiter as /
, such as Ljava / lang / Object;
.
Type Signature | Java Type |
---|---|
Z | boolean |
B | byte |
C | char |
S | short |
I | int |
J | long |
F | float |
D | double |
Lfully-qualified-class; | Class |
[type | array |
(arg-types) ret-type | method |
You can see the actual signature with the -s
option of the javap
command.
In the example below, the class file generated from jnigo / TestClass.java is read.
$ javap -s TestClass.class
Compiled from "TestClass.java"
public class TestClass {
public boolean vboolean;
descriptor: Z
public byte vbyte;
descriptor: B
...
public static double smvdouble();
descriptor: ()D
public static TestClass smvclass();
descriptor: ()LTestClass;
...
For example, the signature of TestClass
isLTestClass;
, so the function that takes TestClass
as the return value with no arguments is () LTestClass;
. In addition to specifying the class file directly, you can also specify FQCN and display it like javap -s java.lang.String
.
On the native code side, the exception that is raised in JNI cannot be known unless it is actively checked.
ʻExceptionCheck () can only get exceptions with or without exceptions, and ʻExceptionOccurred ()
can get exceptions. ʻExceptionDescribe ()` prints a stack trace to stderr.
If you use ʻExceptionOccurred ()`, you can write the equivalent of try ~ catch in C, but for panic debugging for the time being, it is easy to stop the process as follows.
if ((*env)->ExceptionCheck(env)) {
(*env)->ExceptionDescribe(env);
exit(1);
}
The error code of JNI itself is a failure unless it is JNI_OK
. If the error requires resolution, you can handle it with the following error numbers.
/*
* possible return values for JNI functions.
*/
define JNI_OK 0 /* success */
define JNI_ERR (-1) /* unknown error */
define JNI_EDETACHED (-2) /* thread detached from the VM */
define JNI_EVERSION (-3) /* JNI version error */
define JNI_ENOMEM (-4) /* not enough memory */
define JNI_EEXIST (-5) /* VM already created */
define JNI_EINVAL (-6) /* invalid arguments */
Even if the reference type variable is in use in C, it will be freed by garbage collection after the reference disappears on the Java side. By controlling the acquisition and release of global references on the native code side, its behavior can also be controlled.
You can get a global reference with NewGlobalRef ()
and release it with DeleteGlobalRef ()
.
// Get global reference, obj is a reference type variable
jobject ref = (*env)->NewGlobalRef(env, obj);
// Release the created global reference
(*env)->DeleteGlobalRef(env, ref);
I mainly explained how to call Java methods from C in JNI. The above information is sufficient for simple purposes, but if you want to use it in earnest, you will need to use JNI more, such as the behavior of garbage collection and consideration for performance. It looks like you can do something interesting with the JVM from native code.
[About IBM Knowledge Center-Java Native Interface (JNI)](https://www.ibm.com/support/knowledgecenter/ja/SSYKE2_8.0.0/com.ibm.java.lnx.80.doc/diag/understanding/ jni.html) Best Practices for Using Java Native Interface juntaki/jnigo: JNI wrapper for Go
Recommended Posts