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.