Rust库交叉编译以及在Android与iOS中使用

2023-05-16

请添加图片描述

本篇是关于交叉编译Rust库,生成Android和iOS的二进制文件(so与a文件),以及简单的集成使用。

1.环境

系统:macOS 13.0 M1 Pro,Windows 10

Python: 3.9.6
Rust: 1.66.1
NDK: 21.4.7075529

这里就不具体说明以上环境的安装配置了,有需要可以去对应官网查找或看文末参考链接。高版本ndk操作有所不同,我后面会说到。其他版本没有具体要求,大体一致即可。

总的来说,macOS和Windows在操作上没有太大的区别,主要是两者环境安装配置的不同,。本篇以macOS为例说明。

2.配置

Android

使用NDK 提供的 make_standalone_toolchain.py 脚本创建工具链。我们需要对我们想要编译的每个架构都这样做。

export ANDROID_HOME=实际Android sdk位置
export NDK_HOME=$ANDROID_HOME/ndk/21.4.7075529

python "$NDK_HOME\build\tools\make_standalone_toolchain.py" --api 30 --arch arm64 --install-dir NDK/arm64
python "$NDK_HOME\build\tools\make_standalone_toolchain.py" --api 30 --arch arm --install-dir NDK/arm
python "$NDK_HOME\build\tools\make_standalone_toolchain.py" --api 30 --arch x86 --install-dir NDK/x86

命令中的 --api 参数对应Android项目的targetSdk版本,同时需要注意当前NDK支持的最高版本,比如版本21最高30,版本25最高33。

创建一个新文件cargo-config.toml。该文件将告诉cargo在交叉编译期间在哪里寻找clang链接器。将以下内容添加到文件中:

# macos
[target.aarch64-linux-android]
ar = "/Users/weilu/NDK/arm64/bin/aarch64-linux-android-ar"
linker = "/Users/weilu/NDK/arm64/bin/aarch64-linux-android-clang"

# windows
[target.armv7-linux-androideabi]
ar = "D:\\NDK\\arm\\bin\\arm-linux-androideabi-ar.exe"
linker = "D:\\NDK\\arm\\bin\\arm-linux-androideabi-clang.cmd"

#[target.i686-linux-android]
#ar = "xxx/bin/i686-linux-android-ar"
#linker = "xxx/bin/i686-linux-android-clang"

上面是我macos和windows上文件目录路径,你们需要替换为自己设备上的目录路径。如果你不需要x86,可以不用添加target.i686-linux-android配置。

然后执行命令cp cargo-config.toml ~/.cargo/config将此配置文件复制到我们的.cargo目录中。所以这里你也可以直接创建config文件手动复制的cargo的安装根目录中。


在执行一开始的命令时你会看到如下警告:

WARNING:__main__:make_standalone_toolchain.py is no longer necessary. The
$NDK/toolchains/llvm/prebuilt/darwin-x86_64/bin directory contains target-specific scripts that perform
the same task.

其实就是说$NDK/toolchains/llvm/prebuilt/darwin-x86_64/bin目录下已经内置了,所以你也可以直接使用。举个例子:

[target.aarch64-linux-android]
ar = "/Users/weilu/android-sdk-macosx/ndk/21.4.7075529/toolchains/llvm/prebuilt/darwin-x86_64/bin/aarch64-linux-android-ar"
linker = "/Users/weilu/android-sdk-macosx/ndk/21.4.7075529/toolchains/llvm/prebuilt/darwin-x86_64/bin/aarch64-linux-android30-clang"

注意aarch64-linux-android30-clang文件中的数字是你需要的api版本。

最后是安装交叉编译组件:

rustup target add aarch64-linux-android armv7-linux-androideabi i686-linux-android

按需安装,如果不用x86可以去除i686-linux-android。使用rustup target list可以查看安装结果。


补充

一开始提到高版本ndk操作有所不同,这里我用ndk(版本25.1.8937393)说明一下:

首先是没有了后缀linux-android--ar文件,需要替换为llvm-ar

[target.aarch64-linux-android]
ar = "/Users/weilu/android-sdk-macosx/ndk/25.1.8937393/toolchains/llvm/prebuilt/darwin-x86_64/bin/llvm-ar"
linker = "/Users/weilu/android-sdk-macosx/ndk/25.1.8937393/toolchains/llvm/prebuilt/darwin-x86_64/bin/aarch64-linux-android30-clang"

然后打包时会报错:

note: ld: error: unable to find library -lgcc
          clang-14: error: linker command failed with exit code 1 (use -v to see invocation)

原因是高版本将libgcc文件改名为libunwind。所以我们添加软链接,或者复制libunwind重命名为libgcc

ln -s /Users/weilu/android-sdk-macosx/ndk/25.1.8937393/toolchains/llvm/prebuilt/darwin-x86_64/lib64/clang/14.0.6/lib/linux/aarch64/libunwind.a /Users/weilu/android-sdk-macosx/ndk/25.1.8937393/toolchains/llvm/prebuilt/darwin-x86_64/lib64/clang/14.0.6/lib/linux/aarch64/libgcc.a

ln -s /Users/weilu/android-sdk-macosx/ndk/25.1.8937393/toolchains/llvm/prebuilt/darwin-x86_64/lib64/clang/14.0.6/lib/linux/arm/libunwind.a /Users/weilu/android-sdk-macosx/ndk/25.1.8937393/toolchains/llvm/prebuilt/darwin-x86_64/lib64/clang/14.0.6/lib/linux/arm/libgcc.a

iOS

iOS配置相对简单,安装Xcode,然后执行xcode-select --install安装命令行工具。

安装交叉编译组件:

rustup target add aarch64-apple-ios armv7-apple-ios armv7s-apple-ios x86_64-apple-ios i386-apple-ios

一样,按需安装。比如我只需要64位,所以实际只安装了aarch64-apple-ios

最后安装lipo,它是一个命令就可编译出iOS目前支持的5个CPU架构静态库,且自动合并成一个universal静态库。

cargo install cargo-lipo

3.例子

首先创建一个项目:

cargo new --lib rust_demo

使用vs code打开项目,推荐安装rust-analyzerBetter TOMLCodeLLDB等插件。项目结构如下图:

请添加图片描述

打开Cargo.toml文件,配置如下:

[package]
name = "rust_demo"
version = "0.1.0"
edition = "2021"

# 仅Android
[target.'cfg(target_os="android")'.dependencies]
jni = { version = "0.20.0", default-features = false }

[lib]
name = "rust_demo"
# iOS and Android.
crate-type = ["staticlib", "cdylib"]

[dependencies]
  • 添加jni依赖,因为这个只是Android使用,所以可以添加此限制条件。
  • staticlib静态库(.a)和cdylib动态库(.so)

lib.rs输入以下代码:

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()
}
  • 注解#[no_mangle],告诉Rust编译器不要 mangle 这个函数的名称,确保我们的函数名导出时不变。
  • extern告诉Rust编译器,此函数将从Rust外部调用。

这个方法很简单,就是传入什么字符串,前面拼接一个"Hello "。

到这里,iOS就可以直接打包使用了。Android还需要添加一个JNI方法:

#[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 fn Java_com_weilu_demo_RustGreetings_greeting(env: JNIEnv, _: 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()
    }
}
  • #[allow(non_snake_case)]:告诉编译器忽略java这类驼峰命名警告。
  • Java_com_weilu_demo_RustGreetings_greeting指的是在Android项目中调用的包名类名方法名,这个同C++一样。

4.打包

Android

打包命令:

cargo build --target aarch64-linux-android --release
cargo build --target armv7-linux-androideabi --release
cargo build --target i686-linux-android --release

请添加图片描述

so文件位置在项目下的target/xxx-linux-android/release/目录。


也可以在Android 项目中使用rust-android-gradle插件实现打包。

根目录build.gradle配置:

dependencies {
	classpath 'org.mozilla.rust-android-gradle:plugin:0.9.3'
}

项目modulebuild.gradle配置:

apply plugin: 'org.mozilla.rust-android-gradle.rust-android'

cargo {
    module  = "../rust_demo" // Or whatever directory contains your Cargo.toml
    libname = "rust_demo"   // Or whatever matches Cargo.toml's [package] name.
    targets = ["arm64", "arm"]  // See bellow for a longer list of options
    apiLevel = 31
    profile = 'release'
}

执行打包命令:

./gradlew cargoBuild

so文件位置在项目下的build/rustJniLibs目录。

iOS

打包命令:

cargo lipo --release
# 或
cargo lipo --targets aarch64-apple-ios --release # 指定平台

请添加图片描述

静态库位置在项目下的target/universal/release/目录。


打包后librust_demo.so文件4.4M,librust_demo.a文件16.7M。这个大小说实话有点大了,所以我们在Cargo.toml添加如下优化配置:

[profile.release]
lto = true
opt-level = 'z'
strip = true
codegen-units = 1

重新编译librust_demo.so文件247K,librust_demo.a文件6.5M。这个大小还是可以接受的。

5.使用

Android

Android端代码如下,注意放到对应的包名下。

package com.weilu.demo;

public class RustGreetings {

	static {
        System.loadLibrary("rust_demo");
    }

    private static native String greeting(final String pattern);

    public String sayHello(String to) {
        return greeting(to);
    }
}

librust_demo.so(64位)放到src/main/jniLibs/arm64-v8a目录下。

调用代码验证:

RustGreetings g = new RustGreetings();
Log.d("rust_demo", g.sayHello("world"));

iOS

Frameworks,Libraries,and Embedded Content下添加librust_demo.alibresolv.tbd文件。
请添加图片描述
创建greetings.h文件,代码如下:

#include <stdint.h>
// 方法名与rust一致
const char* rust_greeting(const char* to);

Swift 项目需要桥接,创建Greetings-Bridging-Header.h,代码如下:

#ifndef Greetings_Bridging_Header_h
#define Greetings_Bridging_Header_h

#import "greetings.h"

#endif /* Greetings_Bridging_Header_h */

打开Build Settings 选项卡, 将 Objective-C Bridging Header设置为 Greetings-Bridging-Header.h路径。

请添加图片描述
然后在 Build SettingsLibrary Search Paths 添加librust_demo.a路径。

请添加图片描述

添加RustGreetings.swift文件,写入如下代码:

class RustGreetings {
    func sayHello(to: String) -> String {
        let result = rust_greeting(to)
        let swift_result = String(cString: result!)
        return swift_result
    }
}

调用代码验证:

let rustGreetings = RustGreetings()
print("\(rustGreetings.sayHello(to: "world"))")

至此,本篇结束。下一篇会基于本篇内容开发一款小工具。

参考

  • Building and Deploying a Rust library on iOS
  • Building and Deploying a Rust library on Android
  • 创建第一个Rust JNI项目
  • Support Android ndk versions r23-beta3 and up
  • Rust 编译后二进制大小和常用优化方式
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Rust库交叉编译以及在Android与iOS中使用 的相关文章

随机推荐

  • Kotlin开发中的一些Tips(二)

    接着上一篇 xff0c 最近又整理了一些 1 作用域函数选择 目前有let run with apply 和 also五个作用域函数 官方文档有张表来说明它们之间的区别 xff1a 总结一下有几点区别 xff1a apply和also返回上
  • Jetpack Compose 从入门到入门(一)

    Jetpack Compose 是用于构建原生 Android 界面的新工具包 它使用更少的代码 强大的工具和直观的 Kotlin API xff0c 可以帮助您简化并加快 Android 界面开发 xff0c 打造生动而精彩的应用 它可让
  • Jetpack Compose 从入门到入门(二)

    开始布局部分 这部分我个人感觉没有必要每个组件 属性都详细说到 xff0c 否则篇幅会很长 建立起Compose中的组件与 Android Views的一个对应关系就够了 具体还是需要在实际的使用中去熟悉 1 Column 子元素按竖直顺序
  • Jetpack Compose 从入门到入门(三)

    本篇开始介绍Jetpack Compose 中的修饰符Modifier 修饰符可以用来执行以下操作 xff1a 更改可组合项的大小 布局 行为和外观 添加信息 xff0c 如无障碍标签 处理用户输入 添加高级互动 xff0c 如使元素可点击
  • 【操作系统】Linux系统中直接优化提升CPU性能(已解决)

    文章目录 问题 xff1a 服务器CPU没有调用最高性能 xff0c 导致跑算法的时候处理速度慢一 BIOS方法二 终端直接设置CPU调节器方法1 查看当前CPU调节器2 安装各种依赖库3 最后安装cpufrequtis工具包并设置CPU调
  • Jetpack Compose 从入门到入门(四)

    本篇开始介绍Jetpack Compose 中常用的组件 有一部分之前的文章中也出现过 xff0c 今天详细说明一下 1 Text 日常最常用的应该就是显示文字 xff0c 所以有必要说一下Text控件 首先源码如下 xff1a span
  • Jetpack Compose 从入门到入门(五)

    应用中的状态是指可以随时间变化的任何值 这是一个非常宽泛的定义 xff0c 从 Room 数据库到类的变量 xff0c 全部涵盖在内 由于Compose是声明式UI xff0c 会根据状态变化来更新UI xff0c 因此状态的处理至关重要
  • Jetpack Compose 从入门到入门(六)

    本篇说说Compose中的Canvas 1 Canvas span class token annotation builtin 64 Composable span span class token keyword fun span sp
  • Jetpack Compose 从入门到入门(七)

    本篇进入Compose 动画部分 1 动画预览 在本系列第一篇中我们提到过 xff0c 64 Preview可以帮我们实现UI的预览功能 xff0c 简单的交互和播放动画 在Android Studio Bumblebee xff08 大黄
  • Android 12 变更及适配攻略

    这几个月有点忙 xff0c 一年一篇的适配文章来的有点晚了 但其实也还好 xff0c 因为我们项目也是下半年才适配 我这边也是提前调研踩坑 xff0c 评估一下工作量 这个时间点也完全跟得上Google Play的审核要求 xff08 11
  • Jetpack Compose 从入门到入门(八)

    接着上一篇的动画部分 xff0c 本篇主要是自定义动画与Animatable AnimationSpec 上一篇中 xff0c 出现了多次animationSpec属性 xff0c 它是用来自定义动画规范的 例如 xff1a span cl
  • Jetpack Compose 从入门到入门(九)

    本篇是Compose的手势部分 点击 添加clickable修饰符就可以轻松实现元素的点击 此外它还提供无障碍功能 xff0c 并在点按时显示水波纹效果 span class token annotation builtin 64 Comp
  • 记参加 2022 Google开发者大会

    前几天有幸参加了2022年Google 开发者大会 Google Developer Summit xff0c 上一次参加Google开发者大会还是2019年 这期间因为众所周知的原因 xff0c 开发者大会都改为了线上举办 和上次相比可以
  • Jetpack Compose 从入门到入门(十)

    本篇介绍如何将Jetpack Compose 添加到已有应用中 xff0c 毕竟大多数情况都是在现有项目中使用 Jetpack Compose 旨在配合既有的基于 View 的界面构造方式一起使用 如果您要构建新应用 xff0c 最好的选择
  • Flutter状态管理之Riverpod 2.0

    两年前分享过一篇Flutter状态管理之Riverpod xff0c 当时riverpod的版本还是0 8 0 xff08 后来文章更新到0 14版本 xff09 当时提到过有一些不足之处 xff1a 毕竟诞生不久 xff0c 它还不能保证
  • Python:元组和字典简述

    目录 1 列表的方法2 for循环遍历列表2 1 语法2 2 range 函数 3 元组3 1 元组的基本概念3 2 元组的创建3 3 元组的解包3 3 1 号在解包中的用法 4 字典4 1 字典的基本概念4 2 字典的使用4 2 1 字典
  • 七种常见软件开发模型

    目录 瀑布模型 xff08 面向文档的软件开发模型 xff09 演化模型 螺旋模型 增量模型 构件组装模型 统一过程 xff08 up xff09 xff08 迭代的软件过程 xff0c 以架构为中心 xff09 敏捷开发模型 瀑布模型 x
  • IP安全策略:只允许指定IP连接远程桌面,限制IP登录

    一 xff0c 新建IP安全策略 WIN 43 R打开运行对话框 xff0c 输入gpedit msc进入组策略编辑器 依次打开 本地计算机 策略 计算机配置 Windows设置 安全设置 IP安全策略 在 本地计算机上 在右面的空白处右击
  • 2022年终总结

    不知不觉就到了年末 xff0c 感叹时间过的真快 我自己坚持写了七年多的博客 xff0c 但这其实是我第一次去写年终总结 也不知道怎么写 xff0c 就简单聊聊 写博客的初衷就是个人收获 xff0c 学习的记录 xff0c 分享出来如果能帮
  • Rust库交叉编译以及在Android与iOS中使用

    本篇是关于交叉编译Rust库 xff0c 生成Android和iOS的二进制文件 xff08 so与a文件 xff09 xff0c 以及简单的集成使用 1 环境 系统 xff1a macOS 13 0 M1 Pro xff0c Window