Rust macOS 平台实践
本文主要探索如何使用 rust 和 c 之间进行互操
macOS APP 对接杂记
在 Swift Library 中嵌入 C 库
详见这篇文章, 同时里面还说了如何制作 Podspec, 非常有价值!
这篇文章 也有整体步骤, 非常好!
Rust 中如何链接 Cocoa Framework?
#[cfg(target_os = "macos")]
#[link(name = "Cocoa", kind = "framework")]
fn some_mac_function() {
}
打包 Rust 并在 Swift Framework 中使用
github 上有一个例子用于生成各个平台的库: https://github.com/chrisballinger/rust-framework-template
参考这篇文章(虽然是 iOS 的)进行. 文章中提到他是参考另外这个文章的.
开发机器上的配置:
-
添加必要的跨平台编译目标支持:
rustup target add aarch64-apple-ios
rustup target add aarch64-apple-darwin
rustup target add x86_64-apple-darwin
// 如果要面向 iOS 模拟器(一般都要):
rustup target add x86_64-apple-ios -
安装 cargo lipo:目前不需要使用这个子命令了, 因为 lipo 命令 + 脚本直接就可以办到.cargo install cargo-lipo
, 用于帮忙编译 iOS 库. -
安装 cbindgen, 用于生成头文件:
cargo install cbindgen
. 但不推荐无脑使用, 因为生成的头文件大部分时候都需要手动修改. -
创建 lib 工程:
cargo new my-rust-library --lib
-
设置 lib 的产物类型: 关于 toml 字段说明详见官方文档.
[lib]
name = "my_rust_library" # The name of the target.
crate-type = ["staticlib", "cdylib"]
path = "src/lib.rs" # The source file of the target.
test = true # Is tested by default.
doctest = true # Documentation examples are tested by default.
bench = true # Is benchmarked by default.
doc = true # Is documented by default.
proc-macro = false # Set to `true` for a proc-macro library.
harness = true # Use libtest harness.
edition = "2021" # The edition of the target.更多关于
crate-type
的取值以及产物类型的说明, 详见官方文档. -
设置 release 打包配置:
[profile.release]
lto = "fat"
opt-level = 3
codegen-units = 1 -
编写 lib 代码:
use std::ffi::{CStr, CString};
use std::os::raw::c_char;
#[no_mangle]
/// # Safety
///
/// not
pub unsafe extern "C" fn rust_hello(to: *const c_char) -> *mut c_char {
let c_str = 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]
/// # Safety
///
/// not
pub unsafe extern "C" fn rust_hello_free(s: *mut c_char) {
if s.is_null() {
return;
}
drop(CString::from_raw(s));
} -
Swift 侧使用时就可以像下面这样:
let result = rust_hello("world")
let swift_result = String(cString: result!)
rust_hello_free(UnsafeMutablePointer(mutating: result))
print(swift_result) -
生成头文件:
# 安装 cbindgen
cargo install cbindgen
# 在 rust 项目根目录下创建头文件目录
mkdir includes
# 生成头文件
cbindgen src/lib.rs -l c > includes/lib_headers.h生成的头文件如下所示:
#include <stdarg.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
/**
* # Safety
*
* not
*/
char *rust_hello(const char *to);
/**
* # Safety
*
* not
*/
void rust_hello_free(char *s); -
写一个我们自己的脚本直接调用系统提供的
lipo
命令打包(不用cargo-lipo
):#!/bin/bash
# 创建用于存放最终产物的目录
mkdir libs
# 编译 x86(其中 target 类型可以通过 rustup target list 查看)
cargo build --release --lib --target x86_64-apple-darwin
# 编译 arm
cargo build --release --lib --target aarch64-apple-darwin
# 清理老的 build, 这里假设是想提供静态库, 如果是动态的, 则对应移除
rm -rf libs/*.a libs/*.dylib
# 将 release 打包的两个架构产物通过 lipo 合并为一个
lipo -create -output libs/my-rust-library.a \
target/x86_64-apple-darwin/release/libmy_rust_library.a \
target/aarch64-apple-darwin/release/libmy_rust_library.a -
在 Xcode 中使用生成的库:
- 将 includes 和 libs 放到工程目录中
- 添加 header search path
- 添加 lib search path
- 在 build phase 中添加 .a 文件的链接
- 生成 bridge header 并引入 lib 的 header
- 在 swift 中调用, 调用时非常方便
Rust 和 C 之间对接的一些细节
- 环境变量以及 cargo.toml 中的值可以通过代码读取, 详见官方文档, 读取时类似
const VERSION: &str = env!("CARGO_PKG_VERSION");
使用 env 宏进行读取.
参考
- rust 的 ffi 文档: https://doc.rust-lang.org/nomicon/ffi.html
- rust 和其他语言互操: https://github.com/crackcomm/rust-lang-interop
- rust bindgen: https://github.com/rust-lang/rust-bindgen, 用于在已有 C 库的基础上, 将 header 文件中的相关内容转换为 rust 对应代码.
- ffi 的例子: https://github.com/alexcrichton/rust-ffi-examples
- build script: https://doc.rust-lang.org/cargo/reference/build-scripts.html
- 在 rust 中调用 c/c++: https://docs.rust-embedded.org/book/interoperability/c-with-rust.html
- 在 c/c++ 中调用 rust: https://docs.rust-embedded.org/book/interoperability/c-with-rust.html
- 一个比较好的 ffi 简介: https://doc.rust-lang.org/1.9.0/book/ffi.html
- ffi 文章: https://medium.com/dwelo-r-d/using-c-libraries-in-rust-13961948c72a