Skip to main content

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 的)进行. 文章中提到他是参考另外这个文章的.

开发机器上的配置:

  1. 添加必要的跨平台编译目标支持:

    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
  2. 安装 cargo lipo: cargo install cargo-lipo, 用于帮忙编译 iOS 库.目前不需要使用这个子命令了, 因为 lipo 命令 + 脚本直接就可以办到.

  3. 安装 cbindgen, 用于生成头文件: cargo install cbindgen. 但不推荐无脑使用, 因为生成的头文件大部分时候都需要手动修改.

  4. 创建 lib 工程: cargo new my-rust-library --lib

  5. 设置 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 的取值以及产物类型的说明, 详见官方文档.

  6. 设置 release 打包配置:

    [profile.release]
    lto = "fat"
    opt-level = 3
    codegen-units = 1
  7. 编写 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));
    }
  8. Swift 侧使用时就可以像下面这样:

    let result = rust_hello("world")
    let swift_result = String(cString: result!)
    rust_hello_free(UnsafeMutablePointer(mutating: result))
    print(swift_result)
  9. 生成头文件:

    # 安装 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);
  10. 写一个我们自己的脚本直接调用系统提供的 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
  11. 在 Xcode 中使用生成的库:

    • 将 includes 和 libs 放到工程目录中
    • 添加 header search path
    • 添加 lib search path
    • 在 build phase 中添加 .a 文件的链接
    • 生成 bridge header 并引入 lib 的 header
    • 在 swift 中调用, 调用时非常方便

Rust 和 C 之间对接的一些细节

  1. 环境变量以及 cargo.toml 中的值可以通过代码读取, 详见官方文档, 读取时类似 const VERSION: &str = env!("CARGO_PKG_VERSION"); 使用 env 宏进行读取.

参考

  1. rust 的 ffi 文档: https://doc.rust-lang.org/nomicon/ffi.html
  2. rust 和其他语言互操: https://github.com/crackcomm/rust-lang-interop
  3. rust bindgen: https://github.com/rust-lang/rust-bindgen, 用于在已有 C 库的基础上, 将 header 文件中的相关内容转换为 rust 对应代码.
  4. ffi 的例子: https://github.com/alexcrichton/rust-ffi-examples
  5. build script: https://doc.rust-lang.org/cargo/reference/build-scripts.html
  6. 在 rust 中调用 c/c++: https://docs.rust-embedded.org/book/interoperability/c-with-rust.html
  7. 在 c/c++ 中调用 rust: https://docs.rust-embedded.org/book/interoperability/c-with-rust.html
  8. 一个比较好的 ffi 简介: https://doc.rust-lang.org/1.9.0/book/ffi.html
  9. ffi 文章: https://medium.com/dwelo-r-d/using-c-libraries-in-rust-13961948c72a