使用jni-rs实现Rust与Android代码互相调用

news2024/10/4 19:26:03

本篇主要是介绍如何使用jni-rs。有关jni-rs内容基于版本0.20.0,新版本写法有所不同。

入门用法

在Rust库交叉编译以及在Android与iOS中使用中我简单说明了jni-rs及demo代码,现在接着补充一些详细内容。

首先贴上之前的示例代码:

use std::os::raw::{c_char};
use std::ffi::{CString, CStr};

#[no_mangle]
pub extern fn rust_greeting(to: *const c_char) -> *mut c_char {
    let c_str = unsafe { CStr::from_ptr(to) };
    let recipient = match c_str.to_str() {
        Err(_) => "there",
        Ok(string) => string,
    };

    CString::new("Hello ".to_owned() + recipient).unwrap().into_raw()
}

#[cfg(target_os="android")]
#[allow(non_snake_case)]
pub mod android {
    extern crate jni;

    use super::*;
    use self::jni::JNIEnv;
    use self::jni::objects::{JClass, JString};
    use self::jni::sys::{jstring};

    #[no_mangle]
    pub unsafe extern "C" fn Java_com_weilu_demo_RustGreetings_greeting(
        env: JNIEnv,
        _class: JClass,
        java_pattern: JString
    ) -> jstring {
        // Our Java companion code might pass-in "world" as a string, hence the name.
        let world = rust_greeting(env.get_string(java_pattern).expect("invalid pattern string").as_ptr());
        // Retake pointer so that we can use it below and allow memory to be freed when it goes out of scope.
        let world_ptr = CString::from_raw(world);
        let output = env.new_string(world_ptr.to_str().unwrap()).expect("Couldn't create java string!");

        output.into_raw()
    }
}
  • 方法的前两个参数JNIEnvJClass是必须的。
  • 其中JNIEnv是JVM接口,用来调用JVM方法。比如例子中的get_stringnew_string方法。
  • JClass是静态方法的类。不会被使用,但仍需要有一个参数槽。
  • JStringjstring类型说明:在使用jni-rs编写JNI代码时,建议使用JString类型作为参数类型,因为它提供了更好的类型安全性和方便性,而不必担心内存分配和释放问题。而jstring类型通常用作返回类型,因为在与Java虚拟机进行交互时,必须使用jstring类型。当需要将JString类型转换为jstring类型时,可以使用JString::into_raw()方法。

jni-rs中的类型与Java中的类型对应如下:

jni-rsJava
jbooleanboolean
jbytebyte
jcharchar
jshortshort
jintint
jlonglong
jfloatfloat
jdoubledouble
jstring、JStringString
jobject、JObjectObject
jclass、JClassObject
jarray数组
jbooleanArrayboolean[]
jbyteArraybyte[]
jcharArraychar[]
jshortArrayshort[]
jintArrayint[]
jlongArraylong[]
jfloatArrayfloat[]
jdoubleArraydouble[]
jobjectArrayObject[]

上面类型基本与C/C++写法一致,需要注意的是有几个首字母大写形式的类型。


另外补充一点,上面的例子中存在一个Rust方法rust_greeting。为了调用它,我们用到了as_ptr方法将JavaStr转为c_char类型。然后返回c_char结果再转为JString,最后调用into_raw转换为jstring类型返回。之所以这么麻烦,因为我们这里考虑到iOS直接调用rust_greeting的情况。如果不考虑iOS,只是用于Android端,那么可以简化为:

    #[no_mangle]
    pub unsafe extern "C" fn Java_com_weilu_demo_RustGreetings_greeting(
        env: JNIEnv,
        _class: JClass,
        java_pattern: JString
    ) -> jstring {
        let world: String = env.get_string(java_pattern).expect("invalid pattern string").into();
        let output = env.new_string(format!("Hello, {}!", world)).expect("Couldn't create java string!");
        output.into_raw()
    }

进阶用法

上面主要是Android调用Rust代码。下面来看如何在Rust中调用Android的代码。

首先我们需要了解JNI字段描述符,JNI字段描述符是一种对函数返回值和参数的编码。

描述符类型例子
Vvoid-
Iint(I)V 表示 void function(int param)
Bbtye(B)V 表示 void function(btye param)
Cchar-
Ddouble-
Ffloat-
Jlong-
Sshort-
Zboolean-
[element_type一维数组([I)V 表示 void function(int[] param)
L classname ;Ljava/lang/String; 表示String
  • 注意对象类型,以L开头,以;结尾。
  • 如果是二维数组,就是两个[符号。比如[[I就表示int[][]

1.回调

我们先实现Android端,添加一个传入接口CallBack的方法:

public class RustGreetings {
	...

    private static native void greetingCallBack(final String pattern, CallBack callBack);

    public void sayHelloCallBack(String to, CallBack callBack) {
        greetingCallBack(to, callBack);
    }
}

public interface CallBack {
    void result(String str);
}

对应的Rust部分代码如下:

#[no_mangle]
pub unsafe extern "C" fn Java_com_weilu_demo_RustGreetings_greetingCallBack(
    env: JNIEnv,
    _class: JClass,
    java_pattern: JString,
    callback: JObject
) {
    let world = rust_greeting(env.get_string(java_pattern).expect("invalid pattern string").as_ptr());
    let world_ptr = CString::from_raw(world);
    let output = env.new_string(world_ptr.to_str().unwrap()).expect("Couldn't create java string!");
    // 延时1s
    thread::sleep(Duration::from_millis(1000));
    env.call_method(callback, "result", "(Ljava/lang/String;)V", &[JValue::from(output)]).unwrap();
}
  • CallBack对应的类型是JObject
  • call_method是Rust调用Android方法的函数。
  • result是调用方法名,(Ljava/lang/String;)V括号内是调用方法的参数类型String,V是方法返回值。
  • &[JValue::from(output)]是传入的参数。没有参数就写&[]

调用代码测试一下:

val rust = RustGreetings()
System.out.println(rust.sayHello("World"))
rust.sayHelloCallBack("Rust") { str ->
    System.out.println(str)
}

调用结果:
在这里插入图片描述

2.指针

有时我们需要将Rust中产生的数据供多个方法使用。一种方法是使用全局变量,比如之前文中提到的lazy_static,我们将结构体保存在Map中,使用时从中获取。另一种方法我们可以将数据的指针返给Android端,后面使用时,可以再传给Rust端使用。下面我们介绍一下第二种方法。

这里我就直接用jni-rs的demo来说明。

public class RustGreetings {

    ...

    private static native long counterNew(HelloWorld callback);
    
    private static native void counterIncrement(long counterPtr);
    
    private static native void counterDestroy(long counterPtr);
}

public class HelloWorld {

    public void counterCallback(int count) {
        System.out.println("counterCallback: count = " + count);
    }
}

添加了三个方法,counterNew用来传入回调对象。counterIncrement是计数器的自增方法。counterDestroy来释放计数器。

对应Rust代码如下:

    struct Counter {
        count: i32,
        callback: GlobalRef,
    }
    
    impl Counter {
        pub fn new(callback: GlobalRef) -> Counter {
            Counter {
                count: 0,
                callback: callback,
            }
        }
    
        pub fn increment(&mut self, env: JNIEnv) {
            self.count = self.count + 1;
            env.call_method(
                &self.callback,
                "counterCallback",
                "(I)V",
                &[self.count.into()],
            )
            .unwrap();
        }
    }
    
    #[no_mangle]
    pub unsafe extern "C" fn Java_com_weilu_demo_RustGreetings_counterNew(
        env: JNIEnv,
        _class: JClass,
        callback: JObject,
    ) -> jlong {
        let global_ref = env.new_global_ref(callback).unwrap();
        let counter = Counter::new(global_ref);
    
        Box::into_raw(Box::new(counter)) as jlong
    }
    
    #[no_mangle]
    pub unsafe extern "C" fn Java_com_weilu_demo_RustGreetings_counterIncrement(
        env: JNIEnv,
        _class: JClass,
        counter_ptr: jlong,
    ) {
        let counter = &mut *(counter_ptr as *mut Counter);
    
        counter.increment(env);
    }
    
    #[no_mangle]
    pub unsafe extern "C" fn Java_com_weilu_demo_RustGreetings_counterDestroy(
        _env: JNIEnv,
        _class: JClass,
        counter_ptr: jlong,
    ) {
        let _boxed_counter = Box::from_raw(counter_ptr as *mut Counter);
    }

  • 代码通过将计数器结构体Counter的指针返回给Android端,实现了计数自增功能。
  • 指针我们通过Box生成,它的作用是将你的数据存储在堆上,然后在栈中保留一个智能指针指向堆上的数据。
  • 调用from_raw函数后,释放分配的内存.

调用部分代码:

long counterPtr = counterNew(new HelloWorld());

for (int i = 0; i < 5; i++) {
    counterIncrement(counterPtr);
}

counterDestroy(counterPtr);

结果如下:
在这里插入图片描述

3.单例

Android端示例代码如下:

package com.weilu.demo;

import android.util.Log;

public class Singleton {
    private Singleton() {}

    public static Singleton getInstance() {
        return Singleton.SingletonHolder.INSTANCE;
    }
    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }

    public void sayHello(){
        Log.d("RustDemo","Hello!");
    }
}

一个简单的单例,里面有一个sayHello方法。

然后对应的Rust部分代码如下:

 #[no_mangle]
pub unsafe extern "C" fn Java_com_weilu_demo_RustGreetings_sayHello(
    env: JNIEnv,
    _class: JClass
) {
    let class = env.find_class("com/weilu/demo/Singleton").expect("can't find class Singleton");

    let instance_method_id = env.get_static_method_id(class, "getInstance", "()Lcom/weilu/demo/Singleton;")
        .expect("can't find method Singleton.getInstance");

    let instance = env.call_static_method_unchecked(class, instance_method_id, ReturnType::Object, &[])
        .expect("can't call method getInstance");

    let instance_obj = JObject::from(instance.l().unwrap());

    let say_hello = env.get_method_id(class, "sayHello", "()V").expect("can't call method sayHello");

    env.call_method_unchecked(instance_obj, say_hello, ReturnType::Primitive(Void), &[]).unwrap();
}
  • find_class,顾名思义查找Class。这里是获取Singleton类。
  • get_static_method_id,获取静态方法id。这里是获取getInstance方法。
  • call_static_method_unchecked,调用静态方法。这里是调用getInstance方法,获取Singleton单例。
  • get_method_id,获取方法id。这里是获取sayHello方法。
  • call_method_unchecked,调用方法。这里是调用sayHello方法。
  • ReturnType::ObjectReturnType::Primitive(Void)是方法的返回类型,对应代码中的Singletonvoid

同理,可以使用上述这一套调用任何存在的系统方法,例如调用Java中的 System.currentTimeMillis(); 方法获取时间戳:

#[no_mangle]
pub unsafe extern "C" fn Java_com_weilu_demo_RustGreetings_currentTimeMillis(
    env: JNIEnv,
    _class: JClass
) -> jlong {
    let system = env.find_class("java/lang/System").unwrap();
    let result = env.call_static_method(system, "currentTimeMillis", "()J", &[]).unwrap();
    let time = result.j().unwrap();
    time as jlong
}

参考

  • JNI字段描述符
  • Android Rust JNI系列教程(三) Rust与Android互相调用

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

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

相关文章

嘉靖王朝最大的一出闹剧和惨剧——大礼仪之争

大礼仪之争 大礼议是指发生在正德十六年&#xff08;1521年&#xff09;到嘉靖三年&#xff08;1524年&#xff09;间的一场皇统问题上的政治争论。 原因是明世宗以地方藩王入主皇位&#xff0c;为其改换父母的问题所引起&#xff0c;是明朝历史第二次小宗入大宗的事件。 “…

罗丹明荧光染料标记叶酸,FA-PEG-RB,叶酸-聚乙二醇-罗丹明;Folic acid-PEG-RB

FA-PEG-RB,叶酸-聚乙二醇-罗丹明 中文名称&#xff1a;叶酸-聚乙二醇-罗丹明 英文名称&#xff1a;FA-PEG-RB, Folic acid-PEG-RB 性状&#xff1a;粉红色固体或液体&#xff0c;取决于分子量 溶剂&#xff1a;溶于水和DMSO、DMF等常规性有机溶剂 保存条件&#xff1a;-2…

【Python】【进阶篇】二十一、Python爬虫的多线程爬虫

目录二十一、Python爬虫的多线程爬虫21.1 多线程使用流程21.2 Queue队列模型21.3 多线程爬虫案例1) 案例分析​2) 完整程序二十一、Python爬虫的多线程爬虫 网络爬虫程序是一种 IO 密集型程序&#xff0c;程序中涉及了很多网络 和 本地磁盘的 IO 操作&#xff0c;这会消耗大量…

自绘 MFC 控件 CComboBox

运行效果: 第一步:在窗口中拖拽一个CComboBox控件,设置如下属性: 类型,设置为:下拉列表包含字符串,设置为:True所有者描述,设置为:Variable 注意: 包含字符串,不设置为True,则使用GetLBText等函数无法获取到Item的text;所有者描述,设置为No,不执行DrawItem、M…

实战大数据项目

存储日志数据集&#xff08;HDFS&#xff09; 数据仓库构建&#xff08;Hive&#xff09; 数据分区表构建 数据预处理 &#xff08;Spark计算引擎&#xff09;-使用Zeppelin进行写SQL 订单指标分析 Sqoop数据导出到传统数据库&#xff08;Mysql&#xff09; Superset数据…

Vue2_02_指令

模板语法 — Vue.js (vuejs.org) 指令 (Directives) 是带有 v- 前缀的特殊 attribute 参数 一些指令能够接收一个“参数”&#xff0c;在指令名称之后以冒号表示 <a v-bind:href"url">...</a> 动态参数 可以用方括号括起来的 JavaScript 表达式作为一…

企业消费管理迈向数字化,助力员工满意度提升,解决行政·财务·采购等部门痛点 | 爱分析调研

调研&#xff1a;李进宝 撰写&#xff1a;李进宝 某市场巡视人员&#xff1a;每次出差都要垫钱&#xff0c;每月还只能报销一次&#xff0c;这不是自费上班吗&#xff1b; 某软件研发人员&#xff1a;我们每天都要加班到很晚&#xff0c;公司提供晚餐&#xff0c;但高油高盐&…

ERP系统应用场景,API接口接入

ERP订单管理系统能为企业带来什么 1、ERP订单管理系统可以对工厂物料采购的时间进行提示&#xff0c;根据产品目前的库存情况来进行确定&#xff0c;并比较物料采购时所需要支付的费用是多少&#xff0c;什么时候采购价格相对更加便宜。 2、在产品的生产方面&#xff0c;涉及…

Linux远程连接虚拟机超时,且ip地址找不到问题解决

ip地址虚拟机自动更改&#xff1a; 原因&#xff1a;Linux没有正常关机 解决&#xff1a;从虚拟机在自己电脑上的文件地址中bin目录下&#xff0c;前面几个以.lck的文件全部删除 Linux远程连接虚拟机超时&#xff1a; 原因可能跟上面是一样的&#xff0c;IP地址自动修改之后自…

华为 ADS 2.0 发布,城区智驾之战「白热化」

作者 | 马波编辑 | 德新虽然上海车展还未正式拉开帷幕&#xff0c;但今天的华为却通过一系列新品的发布为今年的汽车盛会进行了预热。就在今天上午&#xff0c;华为车BU正式发布了一系列新品&#xff0c;同时也对部分现有产品进行了升级。其内容之多、升级的幅度之大&#xf…

Maven项目中的依赖出现版本冲突,最终发现是对Dependency Scope理解有误

再来个文章目录 文章目录背景疑问排查过程问题存在的原因总结示例依赖版本说明本文记录一下遇到maven依赖版本冲突后的排查过程说明以及问题原因说明 下面还有投票&#xff0c;帮忙投个票&#x1f44d; 背景 最近加入了 Apache Dubbo 开源社区&#xff0c;成为了一名Dubbo Con…

【K8S系列】深入解析Pod对象(一)

目录 序言 1.问题引入 1.1 问题描述 2 问题解答 2.1 pod 属性 2.1.1 NodeSelector 2.1.2 HostAliases 2.1.3 shareProcessNamespace 2.1.4 NodeName 2.1.5 其他pod属性 2.2 容器属性 2.2.1 ImagePullPolicy 2.2.2 Lifecycle 3 总结 4. 投票 序言 任何一件事情&am…

Zabbix代理服务器

Zabbix代理服务器一、部署 zabbix 代理服务器1、设置 zabbix 的下载源&#xff0c;按照 zabbix-proxy2、安装zabbix所需数据库3、添加数据库用户&#xff0c;以及 zabbix 所需的数据库信息4、导入数据库信息5、修改配置文件6、配置 agent 使用 proxy二、设置 zabbix-snmp 监控1…

dubbo2.7升级到dubbo3--dubbo2.7升级到dubbo3系列

最近在做老系统升级(springboot2dubbo2.7.1zookeepernacos-config)&#xff0c;去掉zookeeper的注册中心&#xff0c;替换成nacos2.1版本&#xff08;阿里云已经不支持1.X版本了&#xff09;-对应的需要升级springboot和dubbo3。最终升级完成了&#xff0c;其中遇到的诸多问题&…

Unity VFX -- (1)概览

视觉特效&#xff08;Visual Effects&#xff0c;VFX&#xff09;在实时3D项目中能够带来惊艳的效果&#xff0c;其范围很广&#xff0c;比如水花四溅、迷雾、火焰、爆炸效果等。 如果没有VFX&#xff0c;整个环境会让人感到非常呆板无聊。这些特效会让环境生动起来&#xff0c…

nodejs微服务:RPC与GRPC框架

RPC RPC(Remote Procedure Call Protocol)&#xff0c;是远程过程调用的缩写通俗的说就是调用远处的一个函数&#xff0c;与之相对应的是本地函数调用 本地函数调用&#xff1a;参数&#xff0c;返回值&#xff0c;代码段都在本地的一个进程空间内远程函数调用&#xff1a;远程…

【pan-sharpening 攻击:目标检测】

Adversarial pan-sharpening attacks for object detection in remote sensing &#xff08;对抗性泛锐化攻击在遥感目标检测中的应用&#xff09; 全色锐化是遥感系统中最常用的技术之一&#xff0c;其目的是将纹理丰富的PAN图像和多光谱MS图像融合&#xff0c;以获得纹理丰…

docker上面安装mysql

一、docker安装mysql 新建配置 /data/mysql3306/conf/my.cnf(新建logs,data,conf/my.cnf 后面要用) 详情&#xff1a; [mysql] #设置mysql客户端默认字符集 default-character-setUTF8MB4[mysqld] #设置3306端口 port3306#允许最大连接数 max_connections200#允许连接失败的次…

Spring事务(3)-TransactionInterceptor实际事务执行

Spring事务&#xff08;2&#xff09;-EnableTransactionManagement实现源码解析 中介绍了Spring事务开启和代理的实现&#xff0c;现在了解实际事务执行TransactionInterceptor。 TransactionInterceptor TransactionInterceptor类图 MethodInterceptor&#xff1a;AOP代理后…

vue 高德地图设置鼠标样式

高德地图JS API 2.0 设置鼠标样式在线示例 首先&#xff0c;在 index.html 中引入图标&#xff1a; <link rel"stylesheet" href"https://at.alicdn.com/t/font_873139_0v65kqy674.css" >封装工具文件 utils/map.js &#xff1a; export default …