JNI(Java Native Interface) 具体含义就不在此解释了,相信能看到这篇文章的大家肯定已经了解了。我们直接上正菜,跟大家分享如何从零开始开发JNI, all right, let's go。
新建java工程,工程接口如下所示:
这个只是示例,具体的大家可以灵活变通。
定义native接口
首先新建一个类,我们假设叫:JNIDemo
public class JNIDemo {
// 静态方法
public static native String staticSayHello(String msg);
// 实例方法
public native String instSayHello(String msg);
}
万物以HelloWorld开始,所以我们新建了2个接口,一个是静态的sayHello,一个是实例方法sayHello, 后面我们可以看到最后生成的JNI接口具体区别是什么。
javah命令自动生成jni的C++头文件
我们可以方便地使用javah命令来自动生成。
javah -d ../headers pers.scathon.learn.jni.JNIDemo
其中-d参数指定了输出目录是哪里,可以不填,默认就是生成在当前命令执行的目录。生成的JNI头文件如下所示:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class pers_scathon_learn_jni_JNIDemo */
#ifndef _Included_pers_scathon_learn_jni_JNIDemo
#define _Included_pers_scathon_learn_jni_JNIDemo
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: pers_scathon_learn_jni_JNIDemo
* Method: staticSayHello
* Signature: (Ljava/lang/String;)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_pers_scathon_learn_jni_JNIDemo_staticSayHello
(JNIEnv *, jclass, jstring);
/*
* Class: pers_scathon_learn_jni_JNIDemo
* Method: instSayHello
* Signature: (Ljava/lang/String;)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_pers_scathon_learn_jni_JNIDemo_instSayHello
(JNIEnv *, jobject, jstring);
#ifdef __cplusplus
}
#endif
#endif
可以看到,2个方法的不同之处在于,对于静态方法而言,第二个参数是jclass,而对于实例方法而言,第二个对象是jobject
OK 准备工作就到这里,下面就是创建Rust工程。
创建Rust工程
cargo new --lib rust-jni(工程名称随意)
Cargo.toml配置
# 省略其他配置,只保留需要添加的配置。
# 告诉rust编译为动态库.
[lib]
crate-type = ["cdylib"]
# 添加jni依赖.
[dependencies]
jni = "0.18.0"
OK,接下来就是编码
将C++头文件中定义的native接口用Rust实现
我们将C++接口一一对应到Rust中来。
// 非必要,只是为了规避Rust命名不规范检查警告,因为默认生成的JNI方法名是不符合Rust命名规范的。
#![allow(non_snake_case)]
use jni::objects::{JClass, JObject, JString};
use jni::sys::jstring;
use jni::JNIEnv;
// #[no_mangle]是为了让编译器不会为它进行函数名混淆
#[no_mangle]
pub extern "system" fn Java_pers_scathon_learn_jni_JNIDemo_staticSayHello(
env: JNIEnv,
_class: JClass,
msg: JString,
) -> jstring {
let arg: String = env.get_string(msg).unwrap().into();
let return_result = env.new_string(format!("Hello, {}", arg)).unwrap();
return_result.into_inner()
}
#[no_mangle]
pub extern "system" fn Java_pers_scathon_learn_jni_JNIDemo_instSayHello(
env: JNIEnv,
_obj: JObject,
msg: JString,
) -> jstring {
let arg: String = env.get_string(msg).unwrap().into();
let return_result = env.new_string(format!("Hello, {}", arg)).unwrap();
return_result.into_inner()
}
构建:
cargo build
最后生成的动态库文件所在路径为:
Java代码中调用实现的JNI
public class JNIDemo {
static {
System.load("/Users/scathonlin/Projects/java/java-training/jni/src/lib/librust_jni.dylib");
}
// 静态方法
public static native String staticSayHello(String msg);
// 实例方法
public native String instSayHello(String msg);
public static void main(String[] args) {
JNIDemo jniDemo = new JNIDemo();
System.out.println(jniDemo.instSayHello("World From Instance Method!"));
System.out.println(JNIDemo.staticSayHello("World From Class Method!"));
}
}
然后运行:
如何处理复杂类型
数组作为参数
- Java侧JNI接口定义:
public native int getTotalLengthOfStringArray(String[] strArr);
我们假设定义一个获取字符串数组中所有字符串长度的总和的函数。
- Rust侧JNI接口实现:
#[no_mangle]
pub extern "system" fn Java_pers_scathon_learn_jni_JNIDemo_getTotalLengthOfStringArray(
env: JNIEnv,
_obj: JObject,
strArr: jobjectArray,
) -> jint {
let len = env.get_array_length(strArr).unwrap(); // 获取数组长度
let mut total_len = 0;
for i in 0..len {
// 通过下标访问数组元素
let elem: JString = env.get_object_array_element(strArr, i).unwrap().into();
// 转成String
let result: String = env.get_string(elem).unwrap().into();
// 累加长度.
total_len += result.len();
}
(total_len as i32).into()
}