赞
踩
提到JNI
,大家都会想到C
,C++
.不过如今rust
又给我们增加了一个选项,借助rust
的jni
库(https://github.com/jni-rs/jni-rs
),我们可以很方便的使Android
与rust
交互.从本章起,我们将逐步地了解使用rust
实现一些经典的jni
方法.
在命令行输入命令:
cargo new --lib rust_jni_demo
命令执行结果:
Created library `rust_jni_demo` package
这样我们就成功的创建了一个名为rust_jni_demo
的rust
项目.
txs:rust_jni_demo/ (master*) $ tree .
.
├── Cargo.toml
└── src
└── lib.rs
1 directory, 2 files
可以看到目录里有一个src/lib.rs
,这是我们接下来要遍写代码的地方.而Cargo.toml
文件则是我们将要配置项目及依赖的地方.
vs code
配合rust
插件,或者使用CLion
配合rust
插件都可以,甚至使用vim
都是可以的.如图:在Cargo.toml
文件的[dependencies]
标签下,导入jni
依赖
jni = "0.20.0"
在Cargo.toml
中添加如下内容
[lib]
#C规范动态库
crate_type = ["cdylib"]
至此,Cargo.toml
文件的内容为:
[package]
name = "rust_jni_demo"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
jni = "0.20.0"
[lib]
#C规范动态库
crate_type = ["cdylib"]
JNI
方法,返回值为String
pub extern "system" fn Java_com_jni_rust_RustNative_getStringFromRust(env: JNIEnv, _: JClass) -> jstring
可以看到,方法名Java_com_jni_rust_RustNative_getStringFromRust
,命名格式和标准的C++
声明的方法名格式相同.另外请注意,extern 的使用无需 unsafe,我经常看到一些文章里面使用unsafe
的方式声明JNI
方法,方法名如pub unsafe extern "system" fn...
.再次注意,unsafe目前是非必要的.
#[no_mangle]
在方法上方添加#[no_mangle]
注解,来告诉 Rust 编译器不要 mangle 此函数的名称.
Mangling 发生于当编译器将我们指定的函数名修改为不同的名称时,这会增加用于其他编译过程的额外信息,不过会使其名称更难以阅读。每一个编程语言的编译器都会以稍微不同的方式 mangle 函数名,所以为了使 Rust 函数能在其他语言中指定,必须禁用 Rust 编译器的 name mangling。
Android
函数jstring
对应java
里面的String
,所以我们在Android
端定义一个方法public static native String getStringFromRust();
方法位于package com.jni.rust
文件夹下,RustNative
类.
rust
端返回一个jstring
let output = env.new_string("hi bro from rust").unwrap();
output.into_raw()
通过文档可看到env.new_string
方法的具体描述
从
rust string
创建一个java string
对象
其返回值为JString
,即一个jstring
的生命周期表示.
所以我们调用output.into_raw()
将JString
转为JNI
所需要的jstring
.
C++
再实现一遍相同功能,代码如下:extern "C"
JNIEXPORT jstring JNICALL
Java_com_jni_rust_CNative_getStringFromCpp(JNIEnv *env, jclass clazz) {
std::string bro = "hi bro from cpp";
return env->NewStringUTF(bro.c_str());
}
.so
cargo build --target aarch64-linux-android --release
$ cargo build --target aarch64-linux-android --release Compiling proc-macro2 v1.0.47 Compiling quote v1.0.21 Compiling unicode-ident v1.0.5 Compiling syn v1.0.105 Compiling memchr v2.5.0 Compiling same-file v1.0.6 Compiling thiserror v1.0.37 Compiling log v0.4.17 Compiling cfg-if v1.0.0 Compiling bytes v1.3.0 Compiling cesu8 v1.1.0 Compiling jni-sys v0.3.0 Compiling walkdir v2.3.2 Compiling jni v0.20.0 Compiling combine v4.6.6 Compiling thiserror-impl v1.0.37 Compiling rust_jni_demo v0.1.0 (/home/txs/Center/project/rustProject/rust_jni_demo) Finished release [optimized] target(s) in 6.60s
生成的.so
在/target/aarch64-linux-android/release
文件夹下,本项目.so
文件名为librust_jni_demo.so
.
so
文件大小在/target/aarch64-linux-android/release
文件夹下,查看.so
文件大小:
$ ls -alh
总用量 4.2M
drwxrwxr-x 7 txs txs 4.0K 12月 6 15:30 .
drwxrwxr-x 3 txs txs 4.0K 12月 6 15:30 ..
drwxrwxr-x 6 txs txs 4.0K 12月 6 15:30 build
-rw-rw-r-- 1 txs txs 0 12月 6 15:30 .cargo-lock
drwxrwxr-x 2 txs txs 4.0K 12月 6 15:30 deps
drwxrwxr-x 2 txs txs 4.0K 12月 6 15:30 examples
drwxrwxr-x 16 txs txs 4.0K 12月 6 15:30 .fingerprint
drwxrwxr-x 2 txs txs 4.0K 12月 6 15:30 incremental
-rw-rw-r-- 1 txs txs 171 12月 6 15:30 librust_jni_demo.d
-rwxrwxr-x 2 txs txs 4.2M 12月 6 15:30 librust_jni_demo.so
可以看到,librust_jni_demo.so
文件大小居然有4.2M
,远远超出了我们的预期.其实把这个so
放到Android
项目里面再打包之后会被优化到200k
左右的,不过我们也可以在rust
项目里先对我们的so
文件大小进行优化.
so
文件大小编辑Cargo.toml
文件,添加如下内容:
[profile.release]
lto = true
opt-level = 'z'
strip = true
codegen-units = 1
#panic = 'abort'
具体配置详情请参考官方文档. 如果配置上panic = 'abort'
是可以进一步优化文件大小的.但是会导致panic
后看不到调用栈信息.所以我还是习惯于不进行panic = 'abort'
这一项优化.
另外其他优化手段,如重新编译libstd
、声明#![no_std]
不使用标准库,都可以优化so
文件大小.后续文章也会为大家分享一些优化std
的小知识.
so
文件大小 $ ls -alh
总用量 272K
drwxrwxr-x 7 txs txs 4.0K 12月 6 15:49 .
drwxrwxr-x 3 txs txs 4.0K 12月 6 15:30 ..
drwxrwxr-x 10 txs txs 4.0K 12月 6 15:48 build
-rw-rw-r-- 1 txs txs 0 12月 6 15:30 .cargo-lock
drwxrwxr-x 2 txs txs 4.0K 12月 6 15:49 deps
drwxrwxr-x 2 txs txs 4.0K 12月 6 15:30 examples
drwxrwxr-x 29 txs txs 4.0K 12月 6 15:48 .fingerprint
drwxrwxr-x 2 txs txs 4.0K 12月 6 15:30 incremental
-rw-rw-r-- 1 txs txs 171 12月 6 15:30 librust_jni_demo.d
-rwxrwxr-x 2 txs txs 237K 12月 6 15:49 librust_jni_demo.so
可以看到, librust_jni_demo.so
优化到了237k
rust
项目打包生成的so
文件,放到Android
项目中.RustNative
,路径为com.jni.rust
,用以对应Java_com_jni_rust_RustNative_getStringFromRust
函数路径.同时定义一个native
方法public static native String getStringFromRust();
RustNative
类代码为:package com.jni.rust;
public class RustNative {
static {
System.loadLibrary("rust_jni_demo");
}
public static native String getStringFromRust();
}
android
项目中,我又用C++
实现了一个返回string
类型数据的函数,用来对比生成的so
文件大小.详细代码将放在github
.release
包.rust
生成的so
和C++
生成的so
文件apk
,查看so
文件$ ls -lh
总用量 460K
-rw-r--r-- 1 txs txs 217K 1月 1 1981 libcppnative.so
-rw-r--r-- 1 txs txs 237K 1月 1 1981 librust_jni_demo.so
其中libcppnative.so
就是C++
代码打出的so
文件.可以看到,rust
打包生成的so
文件较C++
打包生成的so
文件大了20k
.目前还可以接受.
so
文件代码将 libcppnative.so
拖入IDA
中查看
导出函数如图
函数有点多,找到我们的JNI
方法,看一下伪代码(需要F5插件)
JNI
方法伪代码,如图:
可以看到逻辑还算能看.
再将librust_jni_demo.so
拖入IDA
中查看
导出函数如图
导出函数看起来比较清晰
-再看看具体代码,代码有点多就不放图片了
_int64 __fastcall Java_com_jni_rust_RustNative_getStringFromRust(__int64 a1) { const char *v1; // x20 __int64 v3; // x0 unsigned __int64 v4; // x28 void **v5; // x22 __int64 v6; // x23 __int64 v7; // x1 __int64 v8; // x1 __int64 v9; // x8 unsigned __int64 v10; // x27 char *v11; // x0 __int64 v12; // x1 unsigned int v13; // w9 unsigned int v14; // w28 __int64 v15; // x0 __int64 v16; // x1 __int64 v17; // x0 __int64 v18; // x22 void *v19; // x21 void *v20; // x0 const char *v21; // x1 const char *v22; // x23 __int64 v23; // x0 __int64 v24; // x1 __int64 (__fastcall *v25)(__int64, __int64); // x9 __int64 v26; // x0 unsigned __int8 (__fastcall *v27)(__int64); // x8 __int64 v28; // x20 __int64 v30; // x8 __int64 v31; // x9 char *v32; // x8 __int64 v33; // x0 char v34; // w19 __int64 v35; // x19 size_t v36; // w0 size_t v37; // w1 const char *v38; // [xsp+8h] [xbp-C8h] __int128 v39[2]; // [xsp+10h] [xbp-C0h] BYREF _DWORD v40[2]; // [xsp+30h] [xbp-A0h] __int64 v41; // [xsp+38h] [xbp-98h] BYREF const char *v42; // [xsp+40h] [xbp-90h] void *v43; // [xsp+48h] [xbp-88h] __int128 v44; // [xsp+50h] [xbp-80h] __int128 v45; // [xsp+60h] [xbp-70h] v1 = "hi bro from rust"; if ( sub_F878(0LL, "hi bro from rust", 16LL) == 1 ) { LABEL_2: v3 = sub_E158(8LL); v4 = 0LL; v5 = &off_3A880; v6 = 24LL; v42 = (const char *)v7; v43 = 0LL; v41 = v3; v38 = "assertion failed: w <= 4assertion failed: i + w <= bytes.len()assertion failed: 0xD800 <= surrogate && surroga" "te <= 0xDFFFEoiCharacterBoundaryUnexpectedParse..BorrowMutError"; while ( 1 ) { while ( 1 ) { if ( v4 > 0xF ) { v18 = v41; v1 = v42; v19 = v43; if ( v41 ) goto LABEL_25; goto LABEL_24; } v8 = (unsigned __int8)aHiBroFromRust[v4]; if ( aHiBroFromRust[v4] ) break; sub_E240(&v41, 192LL); sub_E240(&v41, 128LL); LABEL_8: ++v4; } if ( (v8 & 0x80) == 0 ) { sub_E240(&v41, v8); goto LABEL_8; } v9 = byte_4144[v8]; if ( (unsigned int)v9 >= 5 ) goto LABEL_41; v10 = v4 + v9; if ( v4 + v9 > 0x10 ) { v32 = "assertion failed: i + w <= bytes.len()assertion failed: 0xD800 <= surrogate && surrogate <= 0xDFFFEoiCharacterBoundaryUnexpectedParse..BorrowMutError"; v6 = 38LL; v5 = &off_3A898; LABEL_40: v38 = v32; LABEL_41: v33 = sub_F1A0(v38, v6, v5); goto LABEL_49; } if ( (_DWORD)v9 == 4 ) { v11 = (char *)sub_E28C(v4, v10, &off_3A8C8); if ( !v12 ) goto LABEL_39; v13 = (unsigned __int8)*v11; if ( (*v11 & 0x80000000) != 0 ) { if ( v13 <= 0xDF ) { v14 = v11[1] & 0x3F | ((v13 & 0x1F) << 6); } else if ( v13 < 0xF0 ) { v14 = v11[2] & 0x3F | ((v11[1] & 0x3F) << 6) & 0xFFF | ((v13 & 0x1F) << 12); } else { v14 = v11[3] & 0x3F | ((v11[2] & 0x3F | ((v11[1] & 0x3F) << 6) & 0xFFF) << 6) & 0xFFE3FFFF | ((v13 & 7) << 18); if ( v14 == 1114112 ) { LABEL_39: v32 = "called `Option::unwrap()` on a `None` valueinternal error: entered unreachable code/rustc/897e37553b" "ba8b42751c67658967889d11ecd120/library/alloc/src/collections/btree/navigate.rs/rustc/897e37553bba8b4" "2751c67658967889d11ecd120/library/alloc/src/slice.rs/cargo/registry/src/github.com-1ecc6299db9ec823/" "gimli-0.25.0/src/read/line.rs"; v6 = 43LL; v5 = &off_3A8E0; goto LABEL_40; } } } else { v14 = (unsigned __int8)*v11; } sub_E2B4(((v14 + 16711680) >> 10) | 0xFFFFD800); sub_35DA8(); v17 = sub_E2B4(v14 & 0x3FF | 0xFFFFDC00); LOWORD(v40[0]) = v17; BYTE2(v40[0]) = BYTE2(v17); sub_35DA8(); v4 = v10; } else { v15 = sub_E28C(v4, v4 + v9, &off_3A8B0); sub_E1D0(&v41, v15, v15 + v16); v4 = v10; } } } v30 = 0LL; v19 = &word_10; while ( v30 != 16 ) { v31 = (unsigned __int8)aHiBroFromRust[v30]; if ( (v31 & 0xC0) != 128 && byte_4144[v31] > 3u ) goto LABEL_2; ++v30; } LABEL_24: v20 = (void *)sub_E158(v19); v22 = v21; v18 = (__int64)v20; memcpy(v20, v1, (size_t)v19); v1 = v22; LABEL_25: v41 = v18; v42 = v1; v43 = v19; v23 = sub_DBF8(&v41); *(_QWORD *)&v39[0] = v23; *((_QWORD *)&v39[0] + 1) = v24; if ( !a1 ) { v34 = 8; v28 = 6LL; v19 = &unk_4DFD; goto LABEL_48; } if ( !*(_QWORD *)a1 ) goto LABEL_42; v25 = *(__int64 (__fastcall **)(__int64, __int64))(*(_QWORD *)a1 + 1336LL); if ( !v25 ) { v34 = 6; v28 = 12LL; v19 = &unk_4E18; goto LABEL_48; } v26 = v25(a1, v23); if ( !*(_QWORD *)a1 ) { LABEL_42: v34 = 8; v28 = 7LL; v19 = &unk_4E03; goto LABEL_48; } v27 = *(unsigned __int8 (__fastcall **)(__int64))(*(_QWORD *)a1 + 1824LL); if ( v27 ) { v28 = v26; if ( v27(a1) == 1 ) { v34 = 5; } else { if ( v28 ) { sub_13BC0(v39); return v28; } v34 = 7; v28 = 19LL; v19 = &unk_4E24; } } else { v34 = 6; v28 = 14LL; v19 = &unk_4E0A; } LABEL_48: sub_13BC0(v39); LOBYTE(v41) = v34; v42 = (const char *)v19; v43 = (void *)v28; v44 = v39[0]; v45 = v39[1]; *(_DWORD *)((char *)&v41 + 1) = v40[0]; HIDWORD(v41) = *(_DWORD *)((char *)v40 + 3); v33 = sub_FF4C(&unk_5537, 43LL, &v41, &off_3A808, &off_3A828); LABEL_49: __break(1u); v35 = v33; sub_14284(v39); v36 = sub_37A54(v35); __break(1u); return sub_D4DC(v36, v37); }
可以看到相比于C++
JNI
函数的伪代码,rust
代码看起来混乱的多.或许IDA
后续升级可以解决?
想在rust
层打印日志?println!()
信息在Android
里面不显示的.现推荐两个rust android logger
库.
android_logger
,强大的rust android logger
库, github地址:https://github.com/Nercury/android_logger-rs
.(包体也会比较大).android_logger_lite
,超级轻量级的rust android logger
库,包体<1k
,专为Android
项目打造.github地址:https://github.com/tangxuesong6/android_logger_lite
使用Rust
进行JNI
开发的整体流程和原本的C++
开发流程是非常相似的.而且在目前不使用三方库文件的情况下生成的so
文件大小相差不多.但是Rust
项目无法在Android studio
中Debug
调试却是一大硬伤.
Android
项目地址:https://github.com/tangxuesong6/Android_Rust_JNI_Demo
rust
项目地址:https://github.com/tangxuesong6/Rust_JNI_Demo
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。