这是一个简单的单文件项目,用于演示如何使用jni crate:
Java端
package org.example.mcve.standalone;
public class Mcve {
static {
System.load("/Users/svetlin/CLionProjects/mcve/target/debug/libmcve.dylib");
}
public static void main(String[] args) throws Exception {
doStuffInNative();
}
public static native void doStuffInNative();
public static void callback() {
System.out.println("Called From JNI");
}
}
启动时加载本地库。我使用load
需要绝对路径。或者你可以使用loadLibrary
只需要库的名称,但另一方面需要将其放置在特定位置。
为了能够从Java中调用本地方法,您必须找到在库中使用的签名。为此,您必须生成一个C头文件。可以按以下方式执行:
cd src/main/java/org/example/mcve/standalone/
javac -h Mcve.java
结果应该得到像这样的文件
#include <jni.h>
#ifndef _Included_org_example_mcve_standalone_Mcve
#define _Included_org_example_mcve_standalone_Mcve
#ifdef __cplusplus
extern "C" {
#endif
JNIEXPORT void JNICALL Java_org_example_mcve_standalone_Mcve_doStuffInNative
(JNIEnv *, jclass);
#ifdef __cplusplus
}
#endif
#endif
Rust方面
现在,我们知道了所需的方法签名,可以创建我们的Rust库了!首先使用crate_type = "cdylib"
创建Cargo.toml文件:
[package]
name = "mcve"
version = "0.1.0"
authors = ["Svetlin Zarev <svetlin.zarev@hidden.com>"]
edition = "2018"
[dependencies]
jni = "0.12.3"
[lib]
crate_type = ["cdylib"]
接下来添加一个lib.rs
文件,其内容如下:
use jni::objects::JClass;
use jni::JNIEnv;
#[no_mangle]
#[allow(non_snake_case)]
pub extern "system" fn Java_org_example_mcve_standalone_Mcve_doStuffInNative(
env: JNIEnv,
_class: JClass,
) {
let class = env
.find_class("org/example/mcve/standalone/Mcve")
.expect("Failed to load the target class");
let result = env.call_static_method(class, "callback", "()V", &[]);
result.map_err(|e| e.to_string()).unwrap();
}
请注意,我们使用了生成的头文件中丑陋的方法名称和签名。否则,JVM将无法找到我们的方法。
首先,我们加载所需的类。在这种情况下,并不是真正必要的,因为我们已经将完全相同的类作为名为“_class”的参数传递了进来。然后,我们使用作为参数接收到的
env
来调用所需的java方法。
第一个参数是目标类。
第二个-目标方法名称。
第三个-描述参数类型和返回值:
(arguments)return-type
。您可以在此处了解有关该花式语法和神秘字母的更多信息
here。在我们的情况下,我们没有任何参数,返回类型为
V
,表示
VOID
第四个-包含实际参数的数组。由于该方法不需要任何参数,因此我们传递一个空数组。
现在构建Rust库,然后运行Java应用程序。结果,您必须在终端中看到
Called From JNI
从Rust的
main()
中调用Java
首先,您必须生成JVM实例。您必须在jni crate上使用“invocation”功能:
[dependencies.jni]
version = "0.12.3"
features = ["invocation", "default"]
您可以使用 .option()
来自定义 jvm 设置:
fn main() {
let jvm_args = InitArgsBuilder::new()
.version(JNIVersion::V8)
.option("-Xcheck:jni")
.build()
.unwrap();
let jvm = JavaVM::new(jvm_args).unwrap();
let guard = jvm.attach_current_thread().unwrap();
let system = guard.find_class("java/lang/System").unwrap();
let print_stream = guard.find_class("java/io/PrintStream").unwrap();
let out = guard
.get_static_field(system, "out", "Ljava/io/PrintStream;")
.unwrap();
if let JValue::Object(out) = out {
let message = guard.new_string("Hello World").unwrap();
guard
.call_method(
out,
"println",
"(Ljava/lang/String;)V",
&[JValue::Object(message.into())],
)
.unwrap();
}
}
除了现在使用AttachGuard
来调用Java方法外,其他都一样,而不是使用传递的JNIEnv
对象。
这里的棘手部分是在启动Rust应用程序之前正确设置LD_LIBRARY_PATH
环境变量,否则它将无法找到libjvm.so。在我的情况下,它是:
export LD_LIBRARY_PATH=/usr/lib/jvm/java-1.11.0-openjdk-amd64/lib/server/
但是在您的系统上路径可能不同。