通过 JNI 实现 Java 与 Rust 的 Channel 消息传递

做纯粹的自己。“你要搞清楚自己人生的剧本——不是父母的续集,不是子女的前传,更不是朋友的外篇。对待生命你不妨再大胆一点,因为你好歹要失去它。如果这世上真有奇迹,那只是努力的另一个名字”。

一、crossbeam_channel

参考 crossbeam_channel - Rust

crossbeam_channel 是一个多生产者多消费者通道,用于消息传递,它是std::sync::mpsc的替代品,具有更多的功能和更好的性能。

二、Channel 类型

通道可以使用两个函数创建:

  1. bounded 函数创建一个容量有限的信道,即一个信道一次可以容纳的消息数量是有限制的。
  2. unbounded 函数创建一个容量无界的信道,即它一次可以容纳任意数量的消息。

这两个函数都返回一个发送方 Sender 和一个接收方 Receiver,它们代表通道的相反两端。

创建一个有界 Channel:

use crossbeam_channel::bounded;// Create a channel that can hold at most 5 messages at a time.
let (s, r) = bounded(5);// Can send only 5 messages without blocking.
for i in 0..5 {s.send(i).unwrap();
}// Another call to `send` would block because the channel is full.
// s.send(5).unwrap();

创建一个无界 Channel:

use crossbeam_channel::unbounded;// Create an unbounded channel.
let (s, r) = unbounded();// Can send any number of messages into the channel without blocking.
for i in 0..1000 {s.send(i).unwrap();
}

三、通过 JNI 使用 Channel

Java 端可以通过 JNI 调用 getSender 获取发送端指针,调用 sendMessage 发送消息到 Rust 中的处理线程,由 Rust 负责处理核心逻辑。

1、新建一个 Rust 库项目

cargo new rust_jni_channel_test --lib

添加依赖包, 

# Cargo.toml[dependencies]
jni = "0.21.1"
lazy_static = "1.5.0"
crossbeam-channel = "0.5.13"
#log = "0.4"
#env_logger = "0.11"[lib]
crate_type = ["cdylib"]

实现 JNI 模块函数, 

// lib.rs#[macro_use]
extern crate lazy_static;use jni::objects::{JClass, JObject};
use jni::sys::{jlong, jobject};
use jni::JNIEnv;
use crossbeam_channel::{unbounded, Sender, Receiver};
use std::thread;lazy_static! {static ref SENDER: Sender<String> = {let (sender, receiver) = unbounded();// Spawn a thread to handle the receiverthread::spawn(move || {for message in receiver.iter() {println!("Received message: {}", message);}});sender};
}#[no_mangle]
pub extern "system" fn Java_com_yushanma_MyResultHandler_getSender(_env: JNIEnv,_class: JClass,
) -> jlong {let sender_ptr = Box::into_raw(Box::new(SENDER.clone())) as jlong;sender_ptr
}#[no_mangle]
pub extern "system" fn Java_com_yushanma_MyResultHandler_sendMessage(mut env: JNIEnv,_class: JClass,sender_ptr: jlong,message: JObject,
) {let sender = unsafe { &*(sender_ptr as *mut Sender<String>) };let message: String = env.get_string(&message.into()).expect("Couldn't get java string!").into();match sender.send(message) {Ok(_) => println!("Message sent successfully"),Err(e) => eprintln!("Failed to send message: {:?}", e),}
}

以上代码主要做了三件事情: 

1、定义静态变量

  • 使用lazy_static!宏定义一个静态变量SENDER,它是一个Sender<String>类型的通道发送端。
  • 创建一个无界通道(unbounded channel),返回一个发送端和接收端。
  • 启动一个新线程,在该线程中不断从接收端读取消息并打印出来。
  • 返回发送端sender

2、实现JNI函数:获取发送端:Java_com_yushanma_MyResultHandler_getSender

  • 这个函数名遵循JNI命名规则,表示这是一个供Java调用的本地方法。
  • 函数参数包括JNI环境指针_env和Java类_class
  • 将静态变量SENDER克隆后转换为原始指针,再将其转换为jlong类型返回给Java。

 3、实现JNI函数:发送消息Java_com_yushanma_MyResultHandler_sendMessage

  • 这个函数同样遵循JNI命名规则。
  • 参数包括JNI环境指针env、Java类_class、发送端指针sender_ptr和Java字符串对象message
  • sender_ptr转换回Sender<String>类型的引用。
  • 从Java字符串对象中提取出Rust字符串。
  • 尝试通过发送端发送消息,如果成功则打印成功信息,否则打印错误信息。

2、新建一个 Maven 项目

package com.yushanma;import java.io.IOException;/*** @version: V1.0* @author: 余衫马* @description: 主函数* @data: 2024-11-22 19:24**/public class MyResultHandler {private static native long getSender();private static native void sendMessage(long senderPtr, String message);// 用于加载包含本地方法实现的本地库。static {System.loadLibrary("rust_jni_channel_test");}public static void main(String[] args) throws IOException {long senderPtr = getSender();new Thread(() -> {for (int i = 0; i < 100; i++) {sendMessage(senderPtr, String.format("Hello from Java! COUNT * %d", i + 1));}}).start();new Thread(() -> {for (int i = 100; i < 200; i++) {sendMessage(senderPtr, String.format("Hello from Java! COUNT * %d", i + 1));}}).start();System.in.read();}
}

 我们声明了两个本地方法getSendersendMessage,它们分别用于获取发送端指针和发送消息,对应 Rust 库中封装的两个 Native 方法。然后在 main 函数中,启动两个新线程,每个线程发送100条消息到Rust端,并且使用System.in.read()阻塞主线程,以防止程序过早退出。

3、添加外部 libs 与 VM 启动参数

-Djava.library.path=D:\JWorkSpace\ChannelDeomo\libs

 通过 VM 参数指定外部库路径,否则会直接使用默认路径,会报错找不到 dll 文件。

4、运行效果

四、封装

1、新建 Callback 类

package com.yushanma.callback;import java.time.LocalDateTime;/*** @version: V1.0* @author: 余衫马* @description: 回调类* @data: 2024-11-22 19:30**/
public class MyCallback {// 用于加载包含本地方法实现的本地库。static {System.loadLibrary("rust_jni_channel_test");}/*** Sender 指针*/private final long senderPtr;/*** 获取 Sender 指针** @return*/private static native long getSender();/*** 发送信息** @param senderPtr Sender 指针* @param message   信息内容*/private static native void sendMessage(long senderPtr, String message);/*** 默认构造方法*/public MyCallback() {senderPtr = getSender();}/*** 回调方法*/public void callback(String out) {sendMessage(senderPtr, String.format("%s callback data: %s, senderPtr %d", LocalDateTime.now(), out, senderPtr));}}
package com.yushanma;import com.yushanma.callback.MyCallback;import java.io.IOException;
import java.util.UUID;/*** @version: V1.0* @author: 余衫马* @description: 主函数* @data: 2024-11-22 19:24**/public class MyResultHandler {public static void main(String[] args) throws IOException {new Thread(() -> {MyCallback myCallback = new MyCallback();for (int i = 0; i < 10; i++) {myCallback.callback(UUID.randomUUID().toString());}}).start();new Thread(() -> {MyCallback myCallback = new MyCallback();for (int i = 0; i < 10; i++) {myCallback.callback(UUID.randomUUID().toString());}}).start();System.in.read();}
}

2、Rust 修改命名路径

#[macro_use]
extern crate lazy_static;use jni::objects::{JClass, JObject};
use jni::sys::{jlong, jobject};
use jni::JNIEnv;
use crossbeam_channel::{unbounded, Sender, Receiver};
use std::thread;lazy_static! {static ref SENDER: Sender<String> = {let (sender, receiver) = unbounded();// Spawn a thread to handle the receiverthread::spawn(move || {for message in receiver.iter() {println!("Received message: {}", message);}});sender};
}#[no_mangle]
pub extern "system" fn Java_com_yushanma_callback_MyCallback_getSender(_env: JNIEnv,_class: JClass,
) -> jlong {let sender_ptr = Box::into_raw(Box::new(SENDER.clone())) as jlong;sender_ptr
}#[no_mangle]
pub extern "system" fn Java_com_yushanma_callback_MyCallback_sendMessage(mut env: JNIEnv,_class: JClass,sender_ptr: jlong,message: JObject,
) {let sender = unsafe { &*(sender_ptr as *mut Sender<String>) };let message: String = env.get_string(&message.into()).expect("Couldn't get java string!").into();match sender.send(message) {Ok(_) => println!("Message sent successfully"),Err(e) => eprintln!("Failed to send message: {:?}", e),}
}

路径要保持一致,否则调用本地方法时会报错找不到函数。

3、运行效果

五、内存释放

在 Java 中,JVM(Java Virtual Machine)管理着内存的分配和释放。对于纯 Java 对象,JVM 的垃圾回收机制会自动处理对象的生命周期。然而,当涉及到与本地代码(如 Rust 或 C/C++)交互时,需要特别注意资源的管理。

在这个示例中,MyCallback 是一个通过 JNI(Java Native Interface)或类似技术与 Rust 代码交互的类。senderPtr 是一个指向本地(Rust)资源的指针。当 MyCallback 实例 lib 被垃圾回收时,JVM 并不会自动知道如何释放 senderPtr 所指向的本地资源。这意味着我们需要手动管理这些资源,以避免内存泄漏。

1、显式释放资源

在 MyCallback 类中提供一个方法,用于显式释放本地资源。

package com.yushanma.callback;import java.time.LocalDateTime;/*** @version: V1.0* @author: 余衫马* @description: 回调类* @data: 2024-11-22 19:30**/
public class MyCallback {// 用于加载包含本地方法实现的本地库。static {System.loadLibrary("rust_jni_channel_test");}/*** Sender 指针*/private long senderPtr;/*** 获取 Sender 指针** @return*/private static native long getSender();/*** 发送信息** @param senderPtr Sender 指针* @param message   信息内容*/private static native void sendMessage(long senderPtr, String message);/*** 释放资源* @param senderPtr Sender 指针*/private static native void releaseSender(long senderPtr);/*** 默认构造方法*/public MyCallback() {senderPtr = getSender();}/*** 回调方法*/public void callback(String out) {sendMessage(senderPtr, String.format("%s callback data: %s, senderPtr %d", LocalDateTime.now(), out, senderPtr));}/*** 释放资源*/public void release(){releaseSender(senderPtr);senderPtr = 0;}/*** 重载 Object 类的 finalize 方法* 不推荐依赖 finalize 方法,因为它的执行时间是不确定的,但作为最后的防线,可以在 finalize 方法中释放资源。* @throws Throwable*/@Overrideprotected void finalize() throws Throwable {try {if (senderPtr != 0) {releaseSender(senderPtr);}} finally {super.finalize();}}
}

2、使用 AutoCloseable 接口

package com.yushanma.callback;import java.time.LocalDateTime;/*** @version: V1.0* @author: 余衫马* @description: 回调类* @data: 2024-11-22 19:30**/
public class MyCallback implements AutoCloseable  {// 用于加载包含本地方法实现的本地库。static {System.loadLibrary("rust_jni_channel_test");}/*** Sender 指针*/private long senderPtr;/*** 获取 Sender 指针** @return*/private static native long getSender();/*** 发送信息** @param senderPtr Sender 指针* @param message   信息内容*/private static native void sendMessage(long senderPtr, String message);/*** 释放资源* @param senderPtr Sender 指针*/private static native void releaseSender(long senderPtr);/*** 默认构造方法*/public MyCallback() {senderPtr = getSender();}/*** 回调方法*/public void callback(String out) {sendMessage(senderPtr, String.format("%s callback data: %s, senderPtr %d", LocalDateTime.now(), out, senderPtr));}//    /**
//     * 释放资源
//     */
//    public void release(){
//        releaseSender(senderPtr);
//        senderPtr = 0;
//    }
//
//    /**
//     * 重载 Object 类的 finalize 方法
//     * 不推荐依赖 finalize 方法,因为它的执行时间是不确定的,但作为最后的防线,可以在 finalize 方法中释放资源。
//     * @throws Throwable
//     */
//    @Override
//    protected void finalize() throws Throwable {
//        try {
//            if (senderPtr != 0) {
//                releaseSender(senderPtr);
//            }
//        } finally {
//            super.finalize();
//        }
//    }/*** 释放资源*/@Overridepublic void close() {if (senderPtr != 0) {releaseSender(senderPtr);senderPtr = 0;}}}
try (MyCallback myCallback = new MyCallback()) {for (int i = 0; i < 10; i++) {myCallback.callback(UUID.randomUUID().toString());}
} // 自动调用 myCallback.close() 方法

3、Rust 释放内存逻辑

添加一个新的 JNI 函数来释放 Sender,并确保在释放时不会发生内存泄漏或其他问题。

#[macro_use]
extern crate lazy_static;use jni::objects::{JClass, JObject};
use jni::sys::{jlong, jobject};
use jni::JNIEnv;
use crossbeam_channel::{unbounded, Sender, Receiver};
use std::thread;lazy_static! {static ref SENDER: Sender<String> = {let (sender, receiver) = unbounded();// Spawn a thread to handle the receiverthread::spawn(move || {for message in receiver.iter() {println!("Received message: {}", message);}});sender};
}#[no_mangle]
pub extern "system" fn Java_com_yushanma_callback_MyCallback_getSender(_env: JNIEnv,_class: JClass,
) -> jlong {let sender_ptr = Box::into_raw(Box::new(SENDER.clone())) as jlong;sender_ptr
}#[no_mangle]
pub extern "system" fn Java_com_yushanma_callback_MyCallback_sendMessage(mut env: JNIEnv,_class: JClass,sender_ptr: jlong,message: JObject,
) {let sender = unsafe { &*(sender_ptr as *mut Sender<String>) };let message: String = env.get_string(&message.into()).expect("Couldn't get java string!").into();match sender.send(message) {Ok(_) => println!("Message sent successfully"),Err(e) => eprintln!("Failed to send message: {:?}", e),}
}#[no_mangle]
pub extern "system" fn Java_com_yushanma_callback_MyCallback_releaseSender(_env: JNIEnv,_class: JClass,sender_ptr: jlong,
) {if sender_ptr != 0 {unsafe {// Convert the raw pointer back to a Box and drop itlet _ = Box::from_raw(sender_ptr as *mut Sender<String>);}println!("Sender released");}
}

释放内存的关键在这里:

let _ = Box::from_raw(sender_ptr as *mut Sender<String>);

这行代码的作用是将一个原始指针(raw pointer)转换为一个 Box,从而恢复对堆上分配的内存的所有权。在 Rust 中,Box 是一个智能指针类型,用于管理堆上的内存。具体来说,这行代码执行了以下操作:

  1. 类型转换sender_ptr as *mut Sender<String> 将 sender_ptr 转换为一个可变的原始指针,指向 Sender<String> 类型的数据。
  2. 从原始指针创建 BoxBox::from_raw 接受一个原始指针,并返回一个 Box,从而接管该指针所指向的内存的所有权。

完整的解释如下:

  • sender_ptr 是一个原始指针,通常是通过某种方式获得的,例如通过 Box::into_raw 方法将一个 Box 转换为原始指针。
  • as *mut Sender<String> 是一个类型转换,将 sender_ptr 转换为一个指向 Sender<String> 的可变原始指针。
  • Box::from_raw(sender_ptr as *mut Sender<String>) 使用这个原始指针创建一个 Box<Sender<String>>,这样 Rust 的所有权系统就可以正确地管理这块内存。

需要注意的是,使用 Box::from_raw 时要确保:

  • 原始指针确实指向有效的堆内存。
  • 该内存之前是通过 Box 分配的。
  • 在调用 Box::from_raw 后,不再使用原始指针,因为 Box 会负责释放这块内存。

4、运行效果

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/62362.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

CSS笔记(一)炉石传说卡牌设计1

目标 我要通过html实现一张炉石传说的卡牌设计 问题 其中必须就要考虑到各个元素的摆放&#xff0c;形状的调整来达到满意的效果。通过这个联系来熟悉一下CSS的基本操作。 1️⃣ 基本概念 在CSS里面有行元素&#xff0c;块元素&#xff0c;内联元素&#xff0c;常见的行元…

GAMES101:现代计算机图形学入门-笔记-09

久违的101图形学回归咯 今天的话题应该是比较轻松的&#xff1a;聊一聊在渲染中比较先进的topics Advanced Light Transport 首先是介绍一系列比较先进的光线传播方法&#xff0c;有无偏的如BDPT&#xff08;双向路径追踪&#xff09;&#xff0c;MLT&#xff08;梅特罗波利斯…

Oracle 数据库 IDENTITY 列

IDENTITY列是Oracle数据库12c推出的新特性。之所以叫IDENTITY列&#xff0c;是由于其支持ANSI SQL 关键字 IDENTITY&#xff0c;其内部实现还是使用SEQUENCE。 不过推出这个新语法也是应该的&#xff0c;毕竟MyQL已经有 AUTO_INCREMENT列&#xff0c;而SQL Server也已经有IDENT…

前端学习笔记之文件下载(1.0)

因为要用到这样一个场景&#xff0c;需要下载系统的使用教程&#xff0c;所以在前端项目中就提供了一个能够下载系统教程的一个按钮&#xff0c;供使用者进行下载。 所以就试着写一下这个功能&#xff0c;以一个demo的形式进行演示&#xff0c;在学习的过程中也发现了中文路径…

【阅读记录-章节4】Build a Large Language Model (From Scratch)

文章目录 4. Implementing a GPT model from scratch to generate text4.1 Coding an LLM architecture4.1.1 配置小型 GPT-2 模型4.1.2 DummyGPTModel代码示例4.1.3 准备输入数据并初始化 GPT 模型4.1.4 初始化并运行 GPT 模型 4.2 Normalizing activations with layer normal…

Python PDF转JPG图片小工具

Python PDF转JPG图片小工具 1.简介 将单个pdf装换成jpg格式图片 Tip: 1、软件窗口默认最前端&#xff0c;不支持调整窗口大小&#xff1b; 2、可通过按钮选择PDF文件&#xff0c;也可以直接拖拽文件到窗口&#xff1b; 3、转换质量有5个档位&#xff0c;&#xff08;0.25&a…

使用SOAtest进行功能回归测试

持续集成是将所有开发人员的工作副本合并到共享的主线上。这个过程使软件开发对开发人员来说更容易访问、更快、风险更小。 阅读这篇文章&#xff0c;让我们了解如何配置Parasoft SOAtest作为持续集成过程的一部分&#xff0c;来执行功能测试和回归测试。我们将介绍如何使用主…

ais_server 学习笔记

ais_server 学习笔记 一前序二、ais init1、时序图如下2. 初始化一共分为以下几个重要步骤&#xff1a;2.1.1、在ais_server中启动main函数&#xff0c;然后创建AisEngine&#xff0c;接着初始化AisEngine2.1.2、解析/var/camera_config.xml 文件&#xff0c;获取相关配置参数。…

L1G3000 任务-浦语提示词工程

基础任务 (完成此任务即完成闯关) 背景问题&#xff1a;近期相关研究指出&#xff0c;在处理特定文本分析任务时&#xff0c;语言模型的表现有时会遇到挑战&#xff0c;例如在分析单词内部的具体字母数量时可能会出现错误。任务要求&#xff1a;利用对提示词的精确设计&#xf…

Unity之一键创建自定义Package包

内容将会持续更新&#xff0c;有错误的地方欢迎指正&#xff0c;谢谢! Unity之一键创建自定义Package包 TechX 坚持将创新的科技带给世界&#xff01; 拥有更好的学习体验 —— 不断努力&#xff0c;不断进步&#xff0c;不断探索 TechX —— 心探索、心进取&#xff01; …

python的Flask框架使用

python的Flask框架使用 python环境搭建conda安装python自带的虚拟环境&#xff1a;venv python环境搭建 官网地址 点击downloads 选择你需要的版本&#xff0c;我这里使用的3.12.6 选择Windows installer (64-bit) 选择自定义安装&#xff0c;勾选以管理员权限安装&#xff0…

网络原理(一)—— http

什么是 http http 是一个应用层协议&#xff0c;全称为“超文本传输协议”。 http 自 1991 年诞生&#xff0c;目前已经发展为最主流使用的一种应用层协议。 HTTP 往往基于传输层的 TCP 协议实现的&#xff0c;例如 http1.0&#xff0c;http1.0&#xff0c;http2.0 http3 是…

103.【C语言】数据结构之建堆的时间复杂度分析

1.向下调整的时间复杂度 推导 设树高为h 发现如下规律 按最坏的情况考虑(即调整次数最多) 第1层,有个节点,最多向上调整h-1次 第2层,有个节点,最多向上调整h-2次 第3层,有个节点,最多向上调整h-3次 第4层,有个节点,最多向上调整h-4次 ... 第h-1层,有个节点,最多向上调整1次 第…

用Python爬虫“偷窥”1688商品详情:一场数据的奇妙冒险

引言&#xff1a;数据的宝藏 在这个信息爆炸的时代&#xff0c;数据就像是一座座等待挖掘的宝藏。而对于我们这些电商界的探险家来说&#xff0c;1688上的商品详情就是那些闪闪发光的金子。今天&#xff0c;我们将化身为数据的海盗&#xff0c;用Python这把锋利的剑&#xff0…

Python基础学习-12匿名函数lambda和map、filter

目录 1、匿名函数&#xff1a; lambda 2、Lambda的参数类型 3、map、 filter 4、本节总结 1、匿名函数&#xff1a; lambda 1&#xff09;语法&#xff1a; lambda arg1, arg2, …, argN : expression using arg 2&#xff09; lambda是一个表达式&#xff0c;而不是一个语…

【JavaEE初阶 — 网络编程】TCP流套接字编程

TCP流套接字编程 1. TCP &#xff06; UDP 的区别 TCP 的核心特点是面向字节流&#xff0c;读写数据的基本单位是字节 byte 2 API介绍 2.1 ServerSocket 定义 ServerSocket 是创建 TCP 服务端 Socket 的API。 构造方法 方法签名 方法说明 ServerS…

idea新建springboot web项目

idea新建springboot web项目 写在前面开始项目结构定义依赖初始化创建完成修复配置文件内容乱码修改配置文件名称更新配置文件内容为yml格式 配置项目启动项启动项目 写在前面 以下操作以IntelliJ IDEA 2022.3.3版本为例&#xff0c;其他版本应该大体相似。 开始 项目结构定义…

Docker 容器网络创建网桥链接

一、网络:默认情况下,所有的容器都以bridge方式链接到docker的一个虚拟网桥上; 注意:“172.17.0.0/16”中的“/16”表示子网掩码的长度为16位,它表示子网掩码中有16个连续的1,后面跟着16个连续的0。用于区分IP地址中的网络部分和主机部分; 二、为何自定义网络? 加入自…

智能产品综合开发 - 温湿度检测

1 实训选题目的 本次实训选择的题目是“温湿度检测系统”&#xff0c;旨在提升我们对日常生活中实际应用场景的观察力、问题描述能力、分析力和产品设计能力。通过本项目&#xff0c;我们将能够将所学的人工智能专业知识进行综合应用&#xff0c;包括但不限于Linux操作系统、Py…

人工智能如何改变你的生活?

在我们所处的这个快节奏的世界里&#xff0c;科技融入日常生活已然成为司空见惯的事&#xff0c;并且切实成为了我们生活的一部分。在这场科技变革中&#xff0c;最具变革性的角色之一便是人工智能&#xff08;AI&#xff09;。从我们清晨醒来直至夜晚入睡&#xff0c;人工智能…