如何通过JNI从Rust调用Java方法?
我有一个Java库,它有一个类如何通过JNI从Rust调用Java方法?,rust,java-native-interface,Rust,Java Native Interface,我有一个Java库,它有一个类com.purplefrog.batikeExperiment.ToPixels,它有一个方法static void renderToPixelsShape3(int-width,int-height,byte[]rgbs)。调用Java方法并访问新填充的rgbs数组需要什么代码 我打算从Rustmain()函数调用ToPixels.renderToPixelsShape3,因此Rust代码必须构建JNI环境。下面是一个简单的单文件项目,演示如何使用JNI机箱: [
com.purplefrog.batikeExperiment.ToPixels
,它有一个方法static void renderToPixelsShape3(int-width,int-height,byte[]rgbs)
。调用Java方法并访问新填充的rgbs
数组需要什么代码
我打算从Rust
main()
函数调用ToPixels.renderToPixelsShape3
,因此Rust代码必须构建JNI环境。下面是一个简单的单文件项目,演示如何使用JNI机箱:
[dependencies.jni]
version = "0.12.3"
features = ["invocation", "default"]
Java端
package org.example.mcve.standalone;
公共级Mcve{
静止的{
load(“/Users/svetlin/CLionProjects/mcve/target/debug/libmcve.dylib”);
}
公共静态void main(字符串[]args)引发异常{
dostuffinantive();
}
公共静态本机void dostuffinative();
公共静态无效回调(){
System.out.println(“从JNI调用”);
}
}
load
,它需要一个绝对路径。或者,您可以使用loadLibrary
,它只需要库的名称,但另一方面要求它位于特定位置cd src/main/java/org/example/mcve/standalone/
javac-hmcve.java
因此,您应该得到一个如下所示的文件
/*不要编辑此文件-它是机器生成的*/
#包括
/*类组织的标题\u示例\u mcve\u独立\u mcve*/
#ifndef包括组织示例独立示例
#定义(包含)(组织)(示例)(独立)(示例)
#ifdef_uucplusplus
外部“C”{
#恩迪夫
/*
*类别:组织\u示例\u mcve\u独立\u mcve
*方法:剂量法
*签字:()五
*/
JNIEXPORT void JNICALL Java_org_示例_mcve_standalone_mcve_dostuffinanative
(JNIEnv*,jclass);
#ifdef_uucplusplus
}
#恩迪夫
#恩迪夫
锈面
既然我们知道了所需的方法签名,我们就可以创建我们的Rust库了!首先使用板条箱创建Cargo.toml\u type=“cdylib”
:
注意,我们使用了生成的头文件中丑陋的方法名和签名。否则JVM将无法找到我们的方法
首先,我们加载所需的类。在本例中,这并不是必需的,因为我们传递了与名为\u class
的参数相同的类。然后,我们使用作为参数接收的env
调用所需的java方法
第一个参数是目标类
第二个-目标方法名
第三个-描述参数类型和返回值:(参数)返回类型
。在我们的例子中,您可以找到更多关于奇特语法和神秘字母的信息,我们没有任何参数,返回类型是V
,意思是VOID
第四个-包含实际参数的数组。由于该方法不需要任何值,因此我们传递一个空数组
现在构建Rust库,然后运行Java应用程序。因此,您必须在从JNI调用的终端中看到
在Rust中从main()
调用Java
首先,您必须生成一个JVM实例。您必须在jni板条箱上使用“调用”功能:
[dependencies.jni]
version = "0.12.3"
features = ["invocation", "default"]
您可能希望使用.option()
自定义jvm设置:
fn main(){
让jvm_args=InitArgsBuilder::new()
.version(JNIVersion::V8)
.option(“-Xcheck:jni”)
.build()
.unwrap();
让jvm=JavaVM::new(jvm_args).unwrap();
让guard=jvm.attach_current_thread().unwrap();
让system=guard.find_类(“java/lang/system”).unwrap();
让print_stream=guard.find_类(“java/io/PrintStream”).unwrap();
放出
.get_static_字段(系统,“out”,“Ljava/io/PrintStream;”)
.unwrap();
如果让JValue::Object(out)=out{
让message=guard.new_字符串(“Hello World”).unwrap();
警卫
.call_方法(
出来
“println”,
“(Ljava/lang/String;)V”,
&[JValue::Object(message.into())],
)
.unwrap();
}
}
一切都是一样的,只是我们现在使用AttachGuard
来调用Java方法,而不是传递的JNIEnv
对象
这里棘手的部分是在启动Rust应用程序之前正确设置LD\u LIBRARY\u PATH
环境变量,否则它将无法找到libjvm.so。就我而言,它是:
export-LD\u-LIBRARY\u-PATH=/usr/lib/jvm/java-1.11.0-openjdk-amd64/lib/server/
但在你的系统中,路径可能不同以Svetlin Zarev的答案为起点,我已经设法对其进行了扩展,并找到了如何回答其余问题的方法。我不认为这是一个明确的答案,因为我预料还有缺点,因为我所做的一切都是用石头敲击,直到它起作用为止。
Cargo.toml是:
[package]
name = "rust_call_jni"
version = "0.1.0"
authors = ["Robert Forsman <git@thoth.purplefrog.com>"]
edition = "2018"
[dependencies.jni]
version="0.12.3"
features=["invocation"]
我不能绝对肯定我是否正确地进行了阵列复制,但它似乎没有爆炸。更有经验的Rust/Java程序员可能会发现一些错误(并留下评论)
为了结束这个毛球,让我们将字节写入一个文件,这样我们可以在GIMP中查看图像:
{
use std::fs::File;
use std::path::Path;
use std::io::Write;
let mut f = File::create(Path::new("/tmp/x.ppm")).expect("why can't I create the image file?");
f.write_all(format!("P6\n{} {} 255\n", width, height).as_bytes()).expect("failed to write image header");
let tmp:&[u8] =unsafe { &*(rgbs3.as_slice() as *const _ as *const [u8])};
f.write_all( tmp).expect("failed to write image payload");
println!("wrote /tmp/x.ppm");
}
return Ok(());
}
请告诉我有更好的方法将Vec
写入文件(因为这是谷歌搜索结果中显示的解决方案,但诉诸不安全的块让我很难过)
我省略了henious_classpath()
的定义,因为这只是类路径的大约30个jar的列表。我想知道一个妈妈
let width = 400;
let height = 400;
let rgbs = env.new_byte_array(width*height*3)?;
let rgbs2:JObject = JObject::from(rgbs);
let result = je.call_static_method(cls, "renderToPixelsShape3", "(II[B)V", &[
JValue::from(width),
JValue::from(height),
JValue::from(rgbs2),
])?;
println!("{:?}", result);
let blen = env.get_array_length(rgbs).unwrap() as usize;
let mut rgbs3:Vec<i8> = vec![0; blen];
println!("byte array length = {}", blen);
env.get_byte_array_region(rgbs, 0, &mut rgbs3)?;
{
use std::fs::File;
use std::path::Path;
use std::io::Write;
let mut f = File::create(Path::new("/tmp/x.ppm")).expect("why can't I create the image file?");
f.write_all(format!("P6\n{} {} 255\n", width, height).as_bytes()).expect("failed to write image header");
let tmp:&[u8] =unsafe { &*(rgbs3.as_slice() as *const _ as *const [u8])};
f.write_all( tmp).expect("failed to write image payload");
println!("wrote /tmp/x.ppm");
}
return Ok(());
}
use std::convert::TryFrom;
use j4rs::{Instance, InvocationArg, Jvm, JvmBuilder};
fn main() -> Result<(), J4RsError> {
// Create a Jvm
let jvm = JvmBuilder::new().build()?;
// Create the values for the byte array
let rgbs: Vec<InvocationArg> = [0i8; 400 * 400 * 3]
.iter()
.map(|r| InvocationArg::try_from(r).unwrap()
.into_primitive().unwrap())
.collect();
// Create a Java array from the above values
let byte_arr = jvm.create_java_array("byte", &rgbs)?;
// Invoke the static method
jvm.invoke_static(
"com.purplefrog.batikExperiment.ToPixels",
"renderToPixelsShape3",
&[
InvocationArg::try_from(33_i32)?.into_primitive()?,
InvocationArg::try_from(333_i32)?.into_primitive()?,
InvocationArg::try_from(byte_arr)?
])?;
Ok(())
}