
0x01 Introduction
Rust語(yǔ)言突然很火,其運(yùn)行速度堪比C++,有傳言說(shuō)可能會(huì)成為C/C++的替代品。但是這不是今天討論的話題。還是來(lái)看下Rust強(qiáng)大的交叉編譯吧,用Rust構(gòu)建so庫(kù)。網(wǎng)絡(luò)上大都是在Linux系統(tǒng)下操作,包括官方文檔也都是以Linux系統(tǒng)為例說(shuō)明的。今天我們就來(lái)詳細(xì)的講一下如何在Windows下使用Rust構(gòu)建so庫(kù),以及Rust構(gòu)建的so文件與傳統(tǒng)的C/C++有什么區(qū)別呢?
0x02 Tools
主要工具:
- Android Studio——編寫(xiě)Android Demo的工具
- IDEA(VS Code也可以)——編寫(xiě)Rust語(yǔ)言的工具
- Python——執(zhí)行生成工具鏈的 python腳本
其它工具(非必須):
- IDA——反編譯so文件
0x03 Environment
- 安裝Rust語(yǔ)言環(huán)境
- 安裝Python環(huán)境
- 安裝Android SDK和Android NDK環(huán)境
0x04 Code
新建項(xiàng)目
IDEA安裝Rust插件,新建項(xiàng)目。新建項(xiàng)目的項(xiàng)目名稱(chēng)如果存在多個(gè)單詞建議使用下劃線分隔,比如rust_jni_android,防止后面編譯報(bào)warning。
添加依賴(lài)
在Cargo.toml中添加JNI依賴(lài),并聲明lib.rs的crate_type為cdylib。告知編譯器要編譯成庫(kù)。這樣將會(huì)構(gòu)建出動(dòng)態(tài)庫(kù) (.so, .dylib 或 .dll 文件,取決你的操作系統(tǒng)類(lèi)型)。
[dependencies]
# 添加 jni 依賴(lài),并指定版本 0.17.0 截至目前是最新的版本
jni = { version = "0.17.0", default-features = false }
[lib]
crate_type = ["cdylib"]
編寫(xiě)代碼
coding...
#![cfg(target_os = "android")]
#![allow(non_snake_case)]
use std::ffi::{CString, CStr};
use jni::JNIEnv;
use jni::objects::{JObject, JString};
use jni::sys::{jstring};
#[no_mangle]
pub unsafe extern fn Java_com_example_logproject_NativeMethodTest_hello(env: JNIEnv, _: JObject, j_recipient: JString) -> jstring {
let recipient = CString::from(
CStr::from_ptr(
env.get_string(j_recipient).unwrap().as_ptr()
)
);
let output = env.new_string("Hello ".to_owned() + recipient.to_str().unwrap()).unwrap();
output.into_inner()
}
#[no_mangle]
pub unsafe extern fn Java_com_example_logproject_NativeMethodTest_init(env: JNIEnv, jclass: JObject)
-> jstring {
// 將某個(gè)setEnable方法設(shè)置為true
let clazz = env.find_class("com/example/logproject/SDK").unwrap();
env.call_static_method(clazz, "setEnable", "(Z)V", &[JValue::from(true)]);
let str = "i'm a so by rust!";
let str = env.new_string(str.to_owned()).unwrap();
str.into_inner()
}
0x04 Building
準(zhǔn)備工具鏈
用Python編譯工具鏈。如果Android NDK安裝的是默認(rèn)路徑,在任意位置新建一個(gè)文件夾(建議不要帶有中文路徑),然后使用CMD或者PowerShell執(zhí)行下面的代碼。如果NDK的位置不是默認(rèn)安裝位置,務(wù)必更換NDK_HOME的地址。
注:--api 29中的29是Android SDK中下載的API版本。我這里項(xiàng)目中用的androidx,也下載過(guò)API 28的SDK,依然可以正常運(yùn)行。
$NDK_HOME="$env:LOCALAPPDATA\Android\Sdk\ndk-bundle"
mkdir NDK
python "$NDK_HOME\build\tools\make_standalone_toolchain.py" --api 28 --arch arm64 --install-dir NDK/arm64
python "$NDK_HOME\build\tools\make_standalone_toolchain.py" --api 28 --arch arm --install-dir NDK/arm
python "$NDK_HOME\build\tools\make_standalone_toolchain.py" --api 28 --arch x86 --install-dir NDK/x86
生成過(guò)程大約需要2-3分鐘,下面是生成結(jié)果。

創(chuàng)建Cargo配置文件
進(jìn)入到Cargo的安裝目錄,查看目錄下是否有config文件,沒(méi)有就創(chuàng)建。默認(rèn)應(yīng)該是沒(méi)有config文件的。
注:config文件不需要擴(kuò)展名。

在config文件中添加以下內(nèi)容,其中ar和linker中的路徑請(qǐng)修改為上面創(chuàng)建工具鏈的路徑
[target.aarch64-linux-android]
ar = "E:\\rust_jni\\NDK\\arm64\\bin\\aarch64-linux-android-ar.exe"
linker = "E:\\rust_jni\\NDK\\arm64\\bin\\aarch64-linux-android-clang.cmd"
[target.armv7-linux-androideabi]
ar = "E:\\rust_jni\\NDK\\arm\\bin\\arm-linux-androideabi-ar.exe"
linker = "E:\\rust_jni\\NDK\\arm\\bin\\arm-linux-androideabi-clang.cmd"
[target.i686-linux-android]
ar = "E:\\rust_jni\\NDK\\x86\\bin\\i686-linux-android-ar.exe"
linker = "E:\\rust_jni\\NDK\\x86\\bin\\i686-linux-android-clang.cmd"

安裝交叉編譯組件
在CMD或者PowerShell執(zhí)行下面的代碼,來(lái)支持各平臺(tái)的編譯,依然需要等待幾分鐘。
其中,各平臺(tái)與Android NDK filter的映射關(guān)系如下:
aarch64-linux-android 對(duì)應(yīng) arm64-v8a
armv7-linux-androideabi 對(duì)應(yīng) armeabi-v7a
i686-linux-android 對(duì)應(yīng) x86
rustup target add aarch64-linux-android armv7-linux-androideabi i686-linux-android
生成so庫(kù)
三個(gè)平臺(tái)需要以下三條命令分別生成,這里我僅測(cè)試過(guò)生成arm64-v8a和armeabi-v7a的so庫(kù)。
cargo build --target aarch64-linux-android --release
cargo build --target armv7-linux-androideabi --release
cargo build --target i686-linux-android --release
以armeabi-v7a為例,打開(kāi)在IDEA中剛才編寫(xiě)的代碼,在控制臺(tái)中輸入編譯命令,回車(chē)。

so庫(kù)在哪里呢?展開(kāi)target目錄,我這里編譯了兩次,所以存在兩個(gè)編譯文件

然后在展開(kāi)armv7-linux-androideabi-release-deps-librust_jni_android,

然后拷到android的項(xiàng)目目錄就行了。

0x05 Finally
所有的工作都已經(jīng)完成了,生成的so也可以使用。
但是我發(fā)現(xiàn)幾個(gè)問(wèn)題:
- 生成的so文件在Android 5.1的系統(tǒng)可能會(huì)崩潰,Android 10正常。也有可能是我5.1手機(jī)的問(wèn)題。我需要再進(jìn)一步驗(yàn)證。
- 調(diào)試so文件比較復(fù)雜,我還沒(méi)找到好的調(diào)試方法,如果有好的方法,大家可以分享下。
關(guān)于源碼,我會(huì)在寫(xiě)完第二節(jié)放出。
參考資料:
rust-jni:https://docs.rs/jni/0.17.0/jni/
Building and Deploying aRust library on Android:https://mozilla.github.io/firefox-browser-architecture/experiments/2017-09-21-rust-on-android.html