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

本篇主要是介绍如何使用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类型说明:JStringjstring的生命周期表现形式。在使用jni-rs编写JNI代码时,建议使用JString类型,因为它提供了更好的类型安全性和方便性,而不必担心内存分配和释放问题。当需要将JString类型转换为jstring类型时,可以使用JString::into_raw()方法。例如上面的例子,可以使用JString作为返回值,那么最后一行可以直接使用output

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
}

参考

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,以下是一个简单的示例C/C++代码,可以使用libjpeg库实现RGB565图像数据的压缩。请注意,此示例仅供参考,您需要根据实际需求进行修改和优化。 ```c #include <stdio.h> #include <stdlib.h> #include <jpeglib.h> typedef unsigned char BYTE; typedef unsigned short WORD; typedef unsigned int DWORD; // 定义RGB565像素结构体 typedef struct { BYTE blue; // 蓝色分量 BYTE green; // 绿色分量 BYTE red; // 红色分量 } RGB565Pixel; // 将RGB565像素数据转换为RGB24像素数据 void RGB565ToRGB24(RGB565Pixel* srcPixels, BYTE* dstPixels, int width, int height) { int i, j; for (i = 0; i < height; i++) { for (j = 0; j < width; j++) { RGB565Pixel* srcPixel = &srcPixels[i * width + j]; BYTE* dstPixel = &dstPixels[(i * width + j) * 3]; dstPixel[0] = (srcPixel->red << 3) | (srcPixel->red >> 2); // R dstPixel[1] = (srcPixel->green << 2) | (srcPixel->green >> 4); // G dstPixel[2] = (srcPixel->blue << 3) | (srcPixel->blue >> 2); // B } } } // 压缩RGB565图像数据为JPEG格式数据 int compressRGB565ToJPEG(BYTE* srcPixels, int width, int height, BYTE** dstData, DWORD* dstSize, int quality) { struct jpeg_compress_struct cinfo; struct jpeg_error_mgr jerr; JSAMPROW row_pointer[1]; int row_stride; // 分配JPEG压缩器对象并设置错误处理器 cinfo.err = jpeg_std_error(&jerr); jpeg_create_compress(&cinfo); // 设置输出数据缓冲区和大小 *dstData = NULL; *dstSize = 0; // 设置JPEG压缩参数 cinfo.image_width = width; cinfo.image_height = height; cinfo.input_components = 3; cinfo.in_color_space = JCS_RGB; jpeg_set_defaults(&cinfo); jpeg_set_quality(&cinfo, quality, TRUE); // 分配输出数据缓冲区 jpeg_mem_dest(&cinfo, dstData, dstSize); // 开始压缩过程 jpeg_start_compress(&cinfo, TRUE); // 将RGB565图像数据转换为RGB24图像数据 BYTE* rgb24Pixels = (BYTE*)malloc(width * height * 3); RGB565Pixel* rgb565Pixels = (RGB565Pixel*)srcPixels; RGB565ToRGB24(rgb565Pixels, rgb24Pixels, width, height); // 逐行压缩图像数据 row_stride = width * 3; while (cinfo.next_scanline < cinfo.image_height) { row_pointer[0] = &rgb24Pixels[cinfo.next_scanline * row_stride]; jpeg_write_scanlines(&cinfo, row_pointer, 1); } // 结束压缩过程 jpeg_finish_compress(&cinfo); // 释放内存 free(rgb24Pixels); jpeg_destroy_compress(&cinfo); return 0; } ``` 在Java中调用此函数,可以使用JNI机制,例如: ```java public class JpegCompressor { static { System.loadLibrary("jpegcompressor"); } // 压缩RGB565图像数据为JPEG格式数据 public static native int compressRGB565ToJPEG(byte[] srcPixels, int width, int height, byte[] dstData, int quality); } ``` 希望这个示例对您有所帮助!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值