Rust实现JNI

1,430 阅读3分钟

JNI(Java Native Interface) 具体含义就不在此解释了,相信能看到这篇文章的大家肯定已经了解了。我们直接上正菜,跟大家分享如何从零开始开发JNI, all right, let's go。

新建java工程,工程接口如下所示:

image.png

这个只是示例,具体的大家可以灵活变通。

定义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

最后生成的动态库文件所在路径为:

image.png

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!"));
    }
}

然后运行:

image.png

如何处理复杂类型

数组作为参数

  • 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()
}

未完待续