Java allows to write native code, and to expose it through a Java interface. For example, in Instance.java
, we can read:
class Instance { private native long nativeInstantiate(Instance self, byte[] moduleBytes) throws RuntimeException; // … }
We define a public method to call a native method, such as:
public Instance(byte[] moduleBytes) throws RuntimeException { long instancePointer = this.instantiate(this, moduleBytes); this.instancePointer = instancePointer; }
The native implementation is written in Rust. First, a C header is generated with just build-headers
which is located in include/
. The generated code for nativeInstantiate
is the following:
/* * Class: org_wasmer_Instance * Method: nativeInstantiate * Signature: (Lorg/wasmer/Instance;[B)J */ JNIEXPORT jlong JNICALL Java_org_wasmer_Instance_nativeInstantiate (JNIEnv *, jobject, jobject, jbyteArray);
Second, on the Rust side, we have to declare a function with the same naming:
#[no_mangle] pub extern "system" fn Java_org_wasmer_Instance_nativeInstantiate( env: JNIEnv, _class: JClass, this: JObject, module_bytes: jbyteArray, ) -> jptr { // … }
And the dynamic linking does the rest (it's done with the java.library.path
configuration on the Java side). It uses a shared library (.dylib
on macOS, .so
on Linux, .dll
on Windows).
Then, we have to convert “Java data” to “Rust data”. jni-rs
's documentation is our best friend here.