rust库交叉编译以及在android与ios中使用(代码片段)

唯鹿 唯鹿     2023-02-05     618

关键词:

本篇是关于交叉编译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/目录。

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"))")

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

参考

在android与ios中使用lldb调试rust程序(代码片段)

...通过println!()打印的日志信息在Xcode中可以显示,但是AndroidStudio里不显示。所以Android可以使用android_logger实现日志输出。但是开发中仅使用打印日志的方式进行调试还是不够的,我们还需要debug调式。所以有了本篇的内容... 查看详情

rust交叉编译和不依赖glic之类的编译(内嵌这些依赖库)(代码片段)

通过命令查看支持哪些OS和CPU架构rustc--printtarget-list|pr-tw100--columns3通过ldd命令可以查看编译出来的程序是否依赖动态链接库:1.先普通编译,比如cargobuild--release(没有--release则是编译在debug目录)2.通过ldd命令查看:lddtarget/release/... 查看详情

为 ARM 交叉编译 Rust 程序时的 ALSA 链接

】为ARM交叉编译Rust程序时的ALSA链接【英文标题】:ALSAlinkingwhencross-compilingRustprogramforARM【发布时间】:2019-11-2400:38:33【问题描述】:我正在尝试交叉编译一个简单的Rust程序,以便在安装了libasound-dev库的Docker容器内使用wavycrate在... 查看详情

定义表示不透明 C 结构的 Rust 类型的交叉编译安全方法,其大小在编译时已知

】定义表示不透明C结构的Rust类型的交叉编译安全方法,其大小在编译时已知【英文标题】:Cross-compile-safewayofdefiningaRusttyperepresentinganopaqueCstructwhosesizeisknownatcompile-time【发布时间】:2021-07-0622:56:31【问题描述】:我需要定义一个... 查看详情

rust交叉编译配置:windows上编译linux可执行程序(代码片段)

rust交叉编译配置:windows上编译linux可执行程序简述交叉编译大概指在在一种计算机环境中运行的编译程序,能编译出在另外一种环境下运行的代码.本次,我们配置的是在windows上编译出在linux上运行的rust可执行程序.我们在安装rust... 查看详情

rust交叉编译树莓派程序(代码片段)

rust交叉编译树莓派程序使用rust写树莓派程序时,如果直接在树莓派上进行编译,速度非常慢,如果是zero那更加是慢到受不了。因此最好是能通过开发机编译完后,直接放到树莓派上运行。由于开发机上的cpu架构、操作系统和目... 查看详情

rust交叉编译树莓派程序(代码片段)

rust交叉编译树莓派程序使用rust写树莓派程序时,如果直接在树莓派上进行编译,速度非常慢,如果是zero那更加是慢到受不了。因此最好是能通过开发机编译完后,直接放到树莓派上运行。由于开发机上的cpu架构、操作系统和目... 查看详情

rust学习内存安全探秘:变量的所有权引用与借用

...型系统和所有权模型保证了内存安全和线程安全,让您在编译期就能够消除各种各样的错误。•生产力-Rust拥有出色的文档、友好的编译器和清晰的错误提示信息,还集成了一流的工具——包管理器和构建工具,智能地自动补全... 查看详情

cmake交叉编译android库(代码片段)

CMAKE交叉编译Android库不管是在Windows,还是Ubuntu平台,cmake-G"UnixMakefiles"-DCMAKE_TOOLCHAIN_FILE=F:/Download/android-ndk-r21-windows-x86_64/android-ndk-r21/build/cmake/android.toolchain. 查看详情

cmake交叉编译android库(代码片段)

CMAKE交叉编译Android库不管是在Windows,还是Ubuntu平台,cmake-G"UnixMakefiles"-DCMAKE_TOOLCHAIN_FILE=F:/Download/android-ndk-r21-windows-x86_64/android-ndk-r21/build/cmake/android.toolchain. 查看详情

交叉编译适用于 iOS 的 Jansson C 库

】交叉编译适用于iOS的JanssonC库【英文标题】:cross-compileJanssonClibraryforiOS【发布时间】:2014-04-2523:30:28【问题描述】:我正在尝试从我的机​​器(x86_64)交叉编译iOS(armv7)的Jansson。我了解到,使用xcode5,苹果已删除llvm-gcc,因此无... 查看详情

交叉编译到静态库 (libgcrypt) 以在 iOS 上使用

】交叉编译到静态库(libgcrypt)以在iOS上使用【英文标题】:Cross-compiletostaticlib(libgcrypt)foruseoniOS【发布时间】:2015-01-0421:11:54【问题描述】:我从https://www.gnupg.org/download/index.html下载了最新的libgcrypt和libgpg-error库。我已经使用./confi... 查看详情

如何将库添加到交叉编译工具链?

】如何将库添加到交叉编译工具链?【英文标题】:Howtoaddlibrarytocross-compiletoolchain?【发布时间】:2015-12-0210:28:09【问题描述】:我正在使用Netbeans与arm-linux-gnueabihf-raspbian交叉编译树莓派的C++代码。编译工作正常,我可以在raspberr... 查看详情

linux应用开发-libjpeg库交叉编译与使用(代码片段)

1.前言在开发板上如果想要显示jpeg格式的图片,必须用到libjpeg库,不可能自己去编写jpg的解码代码。libjpeg是一个完全用C语言编写的库,包含了被广泛使用的JPEG解码、JPEG编码和其他的JPEG功能的实现。这个库由独立JPE... 查看详情

java示例代码_在Java/Android模块库中使两个相关的包相互独立

java示例代码_在Java/Android模块库中使两个相关的包相互独立 查看详情

qtcreator的安装与qt交叉编译的配置

QtCreator的安装到Qt官网下载QtCreator https://www.qt.io/download-open-source/其它旧版本点击Achieve连接下载或登录http://download.qt.io/下载下载前记得查看自己的linux系统情况uname-a我的是1.i386适用于intel和AMD所有32位的cpu.以及via采用X86架构的... 查看详情

交叉编译时如何使用外部库?(代码片段)

...一些代码。我正在使用gcc-linaro-armhf工具链。我能够在pi上交叉编译并运行一些独立的程序。现在,我想将我的代码链接到外部库,如ncurses。我怎样才能做到这一点。我应该只将我的程序与主机上的现有ncurseslib链接,然后在ARM上... 查看详情

如何在 ARM 交叉编译时选择要链接的静态库?

】如何在ARM交叉编译时选择要链接的静态库?【英文标题】:HowcanIselectastaticlibrarytobelinkedwhileARMcrosscompiling?【发布时间】:2014-08-2806:26:00【问题描述】:我在Ubuntu(arm-linux-gnueabi-gcc)中有一个ARM交叉编译器,默认架构是ARMv7。但是... 查看详情