
本篇是关于交叉编译Rust库,生成Android和iOS的二进制文件(so与a文件),以及简单的集成使用。
系统:macOS 13.0 M1 Pro,Windows 10
Python: 3.9.6
Rust: 1.66.1
NDK: 21.4.7075529
这里就不具体说明以上环境的安装配置了,有需要可以去对应官网查找或看文末参考链接。高版本ndk操作有所不同,我后面会说到。其他版本没有具体要求,大体一致即可。
总的来说,macOS和Windows在操作上没有太大的区别,主要是两者环境安装配置的不同,。本篇以macOS为例说明。
使用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配置相对简单,安装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
首先创建一个项目:
cargo new --lib rust_demo
使用vs code打开项目,推荐安装rust-analyzer、Better TOML,CodeLLDB等插件。项目结构如下图:

打开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++一样。打包命令:
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目录。
打包命令:
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。这个大小还是可以接受的。
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"));
Frameworks,Libraries,and Embedded Content下添加librust_demo.a与libresolv.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 Settings 中 Library 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"))")
至此,本篇结束。下一篇会基于本篇内容开发一款小工具。
我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div
我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看rubyzip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d
类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc
很好奇,就使用rubyonrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提
假设我做了一个模块如下:m=Module.newdoclassCendend三个问题:除了对m的引用之外,还有什么方法可以访问C和m中的其他内容?我可以在创建匿名模块后为其命名吗(就像我输入“module...”一样)?如何在使用完匿名模块后将其删除,使其定义的常量不再存在? 最佳答案 三个答案:是的,使用ObjectSpace.此代码使c引用你的类(class)C不引用m:c=nilObjectSpace.each_object{|obj|c=objif(Class===objandobj.name=~/::C$/)}当然这取决于
我正在尝试使用ruby和Savon来使用网络服务。测试服务为http://www.webservicex.net/WS/WSDetails.aspx?WSID=9&CATID=2require'rubygems'require'savon'client=Savon::Client.new"http://www.webservicex.net/stockquote.asmx?WSDL"client.get_quotedo|soap|soap.body={:symbol=>"AAPL"}end返回SOAP异常。检查soap信封,在我看来soap请求没有正确的命名空间。任何人都可以建议我
关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。
我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t
我想将html转换为纯文本。不过,我不想只删除标签,我想智能地保留尽可能多的格式。为插入换行符标签,检测段落并格式化它们等。输入非常简单,通常是格式良好的html(不是整个文档,只是一堆内容,通常没有anchor或图像)。我可以将几个正则表达式放在一起,让我达到80%,但我认为可能有一些现有的解决方案更智能。 最佳答案 首先,不要尝试为此使用正则表达式。很有可能你会想出一个脆弱/脆弱的解决方案,它会随着HTML的变化而崩溃,或者很难管理和维护。您可以使用Nokogiri快速解析HTML并提取文本:require'nokogiri'h
我想为Heroku构建一个Rails3应用程序。他们使用Postgres作为他们的数据库,所以我通过MacPorts安装了postgres9.0。现在我需要一个postgresgem并且共识是出于性能原因你想要pggem。但是我对我得到的错误感到非常困惑当我尝试在rvm下通过geminstall安装pg时。我已经非常明确地指定了所有postgres目录的位置可以找到但仍然无法完成安装:$envARCHFLAGS='-archx86_64'geminstallpg--\--with-pg-config=/opt/local/var/db/postgresql90/defaultdb/po