美容养生连锁东莞网站建设常州做的网站的公司
news/
2025/9/24 6:10:06/
文章来源:
美容养生连锁东莞网站建设,常州做的网站的公司,80后陈某做盗版视频网站,小程序开发者近期工作中有Rust和Java互相调用需求#xff0c;这篇文章主要介绍如何用Rust通过JNI和Java进行交互#xff0c;还有记录一下开发过程中遇到的一些坑。 JNI简单来说是一套Java与其他语言互相调用的标准#xff0c;主要是C语言#xff0c;官方也提供了基于C的C接口。 既然是C… 近期工作中有Rust和Java互相调用需求这篇文章主要介绍如何用Rust通过JNI和Java进行交互还有记录一下开发过程中遇到的一些坑。 JNI简单来说是一套Java与其他语言互相调用的标准主要是C语言官方也提供了基于C的C接口。 既然是C语言接口那么理论上支持C ABI的语言都可以和Java语言互相调用Rust就是其中之一。 关于JNI的历史背景以及更详细的介绍可以参考官方文档 在Rust中和Java互相调用可以使用原始的JNI接口也就是自己声明JNI的C函数原型在Rust里按照C的方式去调用但这样写起来会很繁琐而且都是unsafe的操作 不过Rust社区里已经有人基于原始的JNI接口封装好了一套safe的接口crate的名字就叫jni用这个库来开发就方便多了 文中涉及的代码放在了这个github仓库 https://github.com/metaworm/rust-java-demo Rust JNI 工程配置 如果你熟悉Cargo和Maven可以跳过这一节直接看我提供的github源码即可 Rust工程配置 首先通过cargo new java-rust-demo创建一个rust工程 然后切换到工程目录cd java-rust-demo并编辑Cargo.toml修改类型为动态库、加上对 jni crate 的依赖 [package]
name rust-java-demo
version 0.1.0
edition 2021[lib]
crate-type [cdylib][dependencies]
jni {version 0.19} 重命名src目录下的main.rs为lib.rsRust库类型的工程编译入口为 lib.rs然后添加以下代码 use jni::objects::*;
use jni::JNIEnv;#[no_mangle]
pub unsafe extern C fn Java_pers_metaworm_RustJNI_init(env: JNIEnv, _class: JClass) {println!(rust-java-demo inited);
} 然后执行cargo build构建生成的动态库默认会位于target/debug目录下我这里用的linux系统动态库文件名为librust_java_demo.so如果是Windows系统文件名为rust_java_demo.dll 这样我们第一个JNI函数就创建成功了 通过Java_pers_metaworm_RustJNI_init这个导出函数给了Java的pers.metaworm.RustJNI这个类提供了一个native的静态方法init 这里只是简单地打印了一句话后面会通过这个初始化函数添加更多的功能 Java工程配置 还是在这个工程目录里把Java部分的代码放在java这个目录下在其中创建pers/metaworm/RustJNI.java文件 package pers.metaworm;public class RustJNI {static {System.loadLibrary(rust_java_demo);}public static void main(String[] args) {init();}static native void init();
} 我们使用流行的 maven 工具来构建Java工程在项目根目录下创建 maven 的工程文件 pom.xml ?xml version1.0 encodingUTF-8?
project xmlnshttp://maven.apache.org/POM/4.0.0xmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsdmodelVersion4.0.0/modelVersiongroupIdpers.metaworm/groupIdartifactIdRustJNI/artifactIdversion1.0-SNAPSHOT/versionpropertiesexec.mainClasspers.metaworm.RustJNI/exec.mainClassmaven.compiler.source1.8/maven.compiler.sourcemaven.compiler.target1.8/maven.compiler.targetmaven.compiler.encodingUTF-8/maven.compiler.encoding/propertiesdependencies/dependenciesbuildsourceDirectoryjava/sourceDirectorypluginsplugingroupIdorg.apache.maven.plugins/groupIdartifactIdmaven-compiler-plugin/artifactIdversion2.4/versionconfigurationencodingUTF-8/encoding/configuration/plugin/plugins/build
/project 运行 DMEO 工程 上面的工程配置弄好之后就可以使用cargo build命令构建Rust提供的JNI动态库mvn compile命令来编译Java代码 Rust和Java代码都编译好之后执行java -Djava.library.pathtarget/debug -classpath target/classes pers.metaworm.RustJNI来运行 其中-Djava.library.pathtarget/debug指定了我们JNI动态库所在的路径-classpath target/classes指定了Java代码的编译输出的类路径pers.metaworm.RustJNI是Java main方法所在的类 不出意外的话运行之后会在控制台输出init函数里打印的rust-java-demo inited Java调用Rust 接口声明 前面的Java_pers_metaworm_RustJNI_init函数已经展示了如何给Java暴露一个native方法即导出名称为Java_类完整路径_方法名的函数然后在Java对应的类里声明对应的native方法 拓展除了通过导出函数给Java提供native方法还可以通过 RegisterNatives 函数动态注册native方法对应的jni封装的函数为JNIEnv::register_native_methods一般动态注册会在JNI_Onload这个导出函数里执行jvm加载jni动态库时会执行这个函数(如果有的话) 当在Java里首次调用native方法时JVM就会寻找对应名称的导出的或者动态注册的native函数并将Java的native方法和Rust的函数关联起来如果JVM没找到对应的native函数则会报java.lang.UnsatisfiedLinkError异常 为了演示我们再添加一些代码来覆盖更多的交互场景 lib.rs use jni::objects::*;
use jni::sys::{jint, jobject, jstring};
use jni::JNIEnv;#[no_mangle]
pub unsafe extern C fn Java_pers_metaworm_RustJNI_addInt(env: JNIEnv,_class: JClass,a: jint,b: jint,
) - jint {a b
}#[no_mangle]
pub unsafe extern C fn Java_pers_metaworm_RustJNI_getThisField(env: JNIEnv,this: JObject,name: JString,sig: JString,
) - jobject {let result env.get_field(this,env.get_string(name).unwrap().to_string_lossy(),env.get_string(sig).unwrap().to_string_lossy(),).unwrap();result.l().unwrap().into_inner()
} RustJNI.java package pers.metaworm;public class RustJNI {static {System.loadLibrary(rust_java_demo);}public static void main(String[] args) {init();System.out.println(test addInt: (addInt(1, 2) 3));RustJNI jni new RustJNI();System.out.println(test getThisField: (jni.getThisField(stringField, Ljava/lang/String;) jni.stringField));System.out.println(test success);}String stringField abc;static native void init();static native int addInt(int a, int b);native Object getThisField(String name, String sig);
} 其中addInt方法接收两个int参数并返回相加的结果getThisField是一个实例native方法它获取this对象指定的字段并返回 参数传递 从上一节的例子里可以看到jni函数的第一个参数总是JNIEnv很多交互操作都需要通过这个对象来进行 第二个参数是类对象(静态native方法)或this对象(实例native方法) 从第三个参数开始每一个参数对应Java的native方法所声明的参数 对于基础的参数类型可以直接用use jni::sys::*提供的j开头的系列类型来声明类型对照表 Java 类型Native 类型类型描述booleanjbooleanunsigned 8 bitsbytejbytesigned 8 bitscharjcharunsigned 16 bitsshortjshortsigned 16 bitsintjintsigned 32 bitslongjlongsigned 64 bitsfloatjfloat32 bitsdoublejdouble64 bitsvoidvoidnot applicable 对于引用类型(复合类型/对象类型)可以统一用jni::objects::JObject声明JObject是对jobject的rust封装带有生命周期参数对于String类型也可以用 JString 来声明JString是对JObject的一层简单封装 抛异常 前面的Java_pers_metaworm_RustJNI_getThisField函数里用了很多unwrap这在生产环境中是非常危险的万一传了一个不存在的字段名就直接crash了所以我们改进一下这个函数让他支持抛异常出错的时候能让Java捕获到 #[no_mangle]
pub unsafe extern C fn Java_pers_metaworm_RustJNI_getThisFieldSafely(env: JNIEnv,this: JObject,name: JString,sig: JString,
) - jobject {let result (|| {env.get_field(this,env.get_string(name)?.to_string_lossy(),env.get_string(sig)?.to_string_lossy(),)?.l()})();match result {Ok(res) res.into_inner(),Err(err) {env.exception_clear().expect(clear);env.throw_new(Ljava/lang/Exception;, format!({err:?})).expect(throw);std::ptr::null_mut()}}
} Java层的测试代码为 try {System.out.println(test getThisFieldSafely: (jni.getThisFieldSafely(stringField, Ljava/lang/String;) jni.stringField));jni.getThisFieldSafely(fieldNotExists, Ljava/lang/String;);} catch (Exception e) {System.out.println(test getThisFieldSafely: catched exception: e.toString());} 通过env.throw_new(Ljava/lang/Exception;, format!({err:?}))抛出了一个异常从JNI函数返回后Java就会捕获到这个异常 代码里可以看到在抛异常之前调用了env.exception_clear()来清除异常这是因为前面的get_field已经抛出一个异常了当env里已经有一个异常的时候后续再调用env的函数都会失败这个异常也会继续传递到上层的Java调用者所以其实这里没有这两句直接返回null的话Java也可以捕获到异常但我们通过throw_new可以自定义异常类型及异常消息 这其实不是一个典型的场景典型的场景应该是Rust里的某个调用返回了Error然后通过抛异常的形式传递到Java层比如除0错误 #[no_mangle]
pub unsafe extern C fn Java_pers_metaworm_RustJNI_divInt(env: JNIEnv,_class: JClass,a: jint,b: jint,
) - jint {if b 0 {env.throw_new(Ljava/lang/Exception;, divide zero).expect(throw);0} else {a / b}
} Rust调用Java 创建对象、调用方法、访问字段... 下面用一段代码展示如何在Rust中创建Java对象、调用方法、获取字段、处理异常等常见用法 #[allow(non_snake_case)]
fn call_java(env: JNIEnv) {match (|| {let File env.find_class(java/io/File)?;// 获取静态字段let separator env.get_static_field(File, separator, Ljava/lang/String;)?;let separator env.get_string(separator.l()?.into())?.to_string_lossy().to_string();println!(File.separator: {}, separator);assert_eq!(separator, format!({}, std::path::MAIN_SEPARATOR));// env.get_static_field_unchecked(class, field, ty)// 创建实例对象let file env.new_object(java/io/File,(Ljava/lang/String;)V,[JValue::Object(env.new_string()?.into())],)?;// 调用实例方法let abs env.call_method(file, getAbsolutePath, ()Ljava/lang/String;, [])?;let abs_path env.get_string(abs.l()?.into())?.to_string_lossy().to_string();println!(abs_path: {}, abs_path);jni::errors::Result::Ok(())})() {Ok(_) {}// 捕获异常Err(jni::errors::Error::JavaException) {let except env.exception_occurred().expect(exception_occurred);let err env.call_method(except, toString, ()Ljava/lang/String;, []).and_then(|e| Ok(env.get_string(e.l()?.into())?.to_string_lossy().to_string())).unwrap_or_default();env.exception_clear().expect(clear exception);println!(call java exception occurred: {err});}Err(err) {println!(call java error: {err:?});}}
}#[no_mangle]
pub unsafe extern C fn Java_pers_metaworm_RustJNI_callJava(env: JNIEnv) {println!(call java);call_java(env)
} 总结一下常用的函数具体用法可以参考JNIEnv的文档 创建对象 new_object创建字符串对象 new_string调用方法 call_method call_static_method获取字段 get_field get_static_field修改字段 set_field set_static_field 要注意的是调用方法、创建对象等需要传一个方法类型签名这是因为Java支持方法重载同一个类里一个名称的函数可能有多个所以需要通过类型签名来区分类型签名的规则可以参考官方文档 异常处理 call_java函数展示了如何在Rust中处理Java的异常 通过JNIEnv对象动态获取字段或者调用方法都会返回一个jni::errors::Result类型对应的Error类型为jni::errors::Error如果Error是jni::errors::Error::JavaException则表明在JVM执行过程中某个地方抛出了异常这种情况下就可以用exception_occurred函数来获取异常对象进行处理然后调用exception_clear来清除异常如果再返回到Java便可以继续执行 在非Java线程中调用Java 从Java中调用的Rust代码本身就处于一个Java线程中第一个参数为JNIEnv对象Rust代码用这个对象和Java进行交互 实际应用场景中可能需要从一个非Java线程或者说我们自己的线程中去调用Java的方法但我们的线程没有JNIEnv对象这时就需要调用JavaVM::attach_current_thread函数将当前线程附加到JVM上来获得一个JNIEnv #[no_mangle]
pub unsafe extern C fn Java_pers_metaworm_RustJNI_callJavaThread(env: JNIEnv) {let vm env.get_java_vm().expect(get jvm);std::thread::spawn(move || {println!(call java in another thread);let env vm.attach_current_thread().expect(attach);call_java(env);});
} attach_current_thread函数返回一个AttachGuard对象可以解引用为JNIEnv并且在作用域结束drop的时候自动调用detach_current_thread函数原始的AttachCurrentThreadJNI函数如果当前线程已经attach了则会抛异常jni crate里的JavaVM::attach_current_thread做了一层封装如果当前已经attach了则会返回之前attach的对象保证不会重复attach JavaVM对象通过JNIEnv::get_java_vm函数获取可以在初始化的时候将这个变量存起来给后续的其他线程使用 局部引用、全局引用与对象缓存 关于局部引用与全局引用的官方文档 Rust提供的native函数传过来的对象引用都是局部引用局部引用只在本次调用JNI调用范围内有效而且不能跨线程使用如果跨线程必须使用全局引用 可以通过JNIEnv::new_global_ref来获取JClass、JObject的全局引用这个函数返回一个GlobalRef对象可以通过GlobalRef::as_object转成JObject或者JClass等对象GlobalRef对象drop的时候会调用DeleteGlobalRef将JVM内部的引用删除 前面的代码从Rust调用Java方法都是通过名称加方法签名调用的这种方式写起来很舒服但运行效率肯定是非常低的因为每次都要通过名称去查找对应的方法 其实JNI原始的C接口是通过jobjectID、jclassID、jmethodID、jfieldID来和Java交互的只不过是jni crate给封装了一层比较友好的接口 如果我们对性能要求比较高则可以在初始化的时候获取一些JClass、JObject的全局引用缓存起来后面再转成JClass、JObject来使用千万不要对jmethodID、jfieldID获取全局引用因为这俩都是通过jclassID生成的其声明周期和jclassID对应的对象相同不是需要GC的对象如果对jmethodID获取全局引用然后调用会导致某些JVM Crash对于jmethodID、jfieldID则可以基于JClass、JObject的全局引用获取后面直接使用即可 获取到这些全局的ID之后就可以通过JNIEnv::call_method_unchecked系列函数来更高效地调用Java 我用Rust强大的宏实现了这个过程可以让我们直接在Rust中以声明的方式缓存的所需类及其方法ID #[allow(non_snake_case)]
pub mod cache {use anyhow::Context;use jni::errors::Result as JniResult;use jni::objects::*;use jni::JNIEnv;pub fn method_global_refa(env: JNIEnva,class: JClass,name: str,sig: str,) - JniResultJMethodIDa {let method env.get_method_id(class, name, sig)?.into_inner();Ok(JMethodID::from(method.cast()))}pub fn static_method_global_refa(env: JNIEnva,class: JClass,name: str,sig: str,) - ::jni::errors::ResultJStaticMethodIDa {let method env.get_static_method_id(class, name, sig)?.into_inner();Ok(JStaticMethodID::from(method.cast()))}macro_rules! gen_global_ref {(method_type) { JMethodIDstatic };(method_type static) { JStaticMethodIDstatic };(method_ref) { method_global_ref };(method_ref static) { static_method_global_ref };($(#[name $classname:literal]class $name:ident {$($method:ident : $($modify:ident)* $sig:literal,)*})*) {$(#[allow(non_snake_case)]pub struct $name {pub class: JClassstatic,$(pub $method: gen_global_ref!(method_type $($modify)*),)*}impl $name {pub fn from_env(env: JNIEnvstatic) - anyhow::ResultSelf {Self::from_class(env, env.find_class($classname)?)}pub fn from_class(env: JNIEnvstatic, class: JClass) - anyhow::ResultSelf {let cls env.new_global_ref(class)?;let class JClass::from(*cls.as_obj());core::mem::forget(cls);Ok(Self {class,$($method: gen_global_ref!(method_ref $($modify)*)(env, class, stringify!($method), $sig).context(stringify!($method))?,)*})}}// TODO: impl Drop)*pub struct CachedClasses {$(pub $name: $name,)*}impl CachedClasses {pub fn from_env(env: JNIEnvstatic) - anyhow::ResultSelf {Ok(Self {$($name: $name::from_env(env).context(stringify!($name))?,)*})}}unsafe impl Sync for CachedClasses {}unsafe impl Send for CachedClasses {}}}gen_global_ref! {#[name java/lang/Thread]class Thread {currentThread: static ()Ljava/lang/Thread;,getStackTrace: ()[Ljava/lang/StackTraceElement;,}#[name java/lang/StackTraceElement]class StackTraceElement {getLineNumber: ()I,toString: ()Ljava/lang/String;,}#[name java/io/File]class File {getAbsolutePath: ()Ljava/lang/String;,}}static mut CLASSES: OptionBoxCachedClasses None;pub unsafe fn init(env: JNIEnvstatic) - anyhow::ResultOptionBoxCachedClasses {Ok(CLASSES.replace(CachedClasses::from_env(env)?.into()))}pub fn get() - static CachedClasses {unsafe { CLASSES.as_ref().expect(Cached Java Classed not inited) }}
}
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/915041.shtml
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!