#bindings #framework #safety #ffi #no-std

no-std safer-ffi

在 Rust 中编写更安全的 FFI 代码,而无需使用不安全的代码污染您的代码

33 个版本

0.1.12 2024 年 8 月 1 日
0.1.11 2024 年 7 月 31 日
0.1.9-rc12024 年 6 月 27 日
0.1.6 2024 年 3 月 29 日
0.0.5 2020 年 6 月 10 日

#52 in Rust 模式

Download history 3707/week @ 2024-05-04 4561/week @ 2024-05-11 4969/week @ 2024-05-18 4932/week @ 2024-05-25 4960/week @ 2024-06-01 4282/week @ 2024-06-08 3928/week @ 2024-06-15 5300/week @ 2024-06-22 5912/week @ 2024-06-29 5483/week @ 2024-07-06 5535/week @ 2024-07-13 5396/week @ 2024-07-20 5850/week @ 2024-07-27 5668/week @ 2024-08-03 11163/week @ 2024-08-10 13651/week @ 2024-08-17

37,457 每月下载量
33 个crate(15 个直接使用) 中使用

MIT 许可证

400KB
11K SLoC

safer-ffi-banner

CI guide docs-rs crates-io repository

什么是 safer_ffi

safer_ffi 是一个框架,帮助您编写外部的函数接口 (FFI),在不需要在 Rust 代码中使用不安全的代码块的同时,使函数更容易阅读和维护。

📚 阅读用户指南 📚

先决条件

最低支持的 Rust 版本:1.66.1

快速入门

点击隐藏

小型自包含演示

您可以尝试使用存储库中嵌入的 examples/point 示例

git clone https://github.com/getditto/safer_ffi && cd safer_ffi
(cd examples/point && make)

否则,要开始使用 ::safer_ffi,请按照以下步骤操作

Crate 布局

步骤 1:Cargo.toml

按照如下修改您的 Cargo.toml

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

[lib]
crate-type = [
    "staticlib",  # Ensure it gets compiled as a (static) C library
  # "cdylib",     # If you want a shared/dynamic C library (advanced)
    "lib",        # For `generate-headers` and other downstream rust dependents
                  # such as integration `tests/`, doctests, and `examples/`
]

[[bin]]
name = "generate-headers"
required-features = ["headers"]  # Do not build unless generating headers.

[dependencies]
# Use `cargo add` or `cargo search` to find the latest values of x.y.z.
# For instance:
#   cargo add safer-ffi
safer-ffi.version = "x.y.z"
safer-ffi.features = [] # you may add some later on.

[features]
# If you want to generate the headers, use a feature-gate
# to opt into doing so:
headers = ["safer-ffi/headers"]
  • 其中 "x.y.z" 应替换为最新发布的版本,您可以通过运行 cargo search safer-ffi 来找到。

  • 有关更多信息,请参阅 有关 Cargo.toml 的专用章节

步骤 2:src/lib.rs

然后,要将 Rust 函数导出为 FFI,添加如下所示的 #[derive_ReprC]#[ffi_export] 属性

use ::safer_ffi::prelude::*;

/// A `struct` usable from both Rust and C
#[derive_ReprC]
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct Point {
    x: f64,
    y: f64,
}

/* Export a Rust function to the C world. */
/// Returns the middle point of `[a, b]`.
#[ffi_export]
fn mid_point(a: &Point, b: &Point) -> Point {
    Point {
        x: (a.x + b.x) / 2.,
        y: (a.y + b.y) / 2.,
    }
}

/// Pretty-prints a point using Rust's formatting logic.
#[ffi_export]
fn print_point(point: &Point) {
    println!("{:?}", point);
}

// The following function is only necessary for the header generation.
#[cfg(feature = "headers")] // c.f. the `Cargo.toml` section
pub fn generate_headers() -> ::std::io::Result<()> {
    ::safer_ffi::headers::builder()
        .to_file("rust_points.h")?
        .generate()
}

步骤 3:src/bin/generate-headers.rs

fn main() -> ::std::io::Result<()> {
    ::crate_name::generate_headers()
}

编译和头文件生成

# Compile the C library (in `target/{debug,release}/libcrate_name.ext`)
cargo build # --release

# Generate the C header
cargo run --features headers --bin generate-headers
生成的C头文件(rust_points.h
/*! \file */
/*******************************************
 *                                         *
 *  File auto-generated by `::safer_ffi`.  *
 *                                         *
 *  Do not manually edit this file.        *
 *                                         *
 *******************************************/

#ifndef __RUST_CRATE_NAME__
#define __RUST_CRATE_NAME__
#ifdef __cplusplus
extern "C" {
#endif


#include <stddef.h>
#include <stdint.h>

/** \brief
 *  A `struct` usable from both Rust and C
 */
typedef struct Point {
    /** <No documentation available> */
    double x;

    /** <No documentation available> */
    double y;
} Point_t;

/** \brief
 *  Returns the middle point of `[a, b]`.
 */
Point_t
mid_point (
    Point_t const * a,
    Point_t const * b);

/** \brief
 *  Pretty-prints a point using Rust's formatting logic.
 */
void
print_point (
    Point_t const * point);


#ifdef __cplusplus
} /* extern \"C\" */
#endif

#endif /* __RUST_CRATE_NAME__ */

从C中测试它

以下是一个基本示例,展示如何调用我们导出的Rust函数

main.c

#include <stdlib.h>

#include "rust_points.h"

int
main (int argc, char const * const argv[])
{
    Point_t a = { .x = 84, .y = 45 };
    Point_t b = { .x = 0, .y = 39 };
    Point_t m = mid_point(&a, &b);
    print_point(&m);
    return EXIT_SUCCESS;
}

编译命令

cc -o main{,.c} -L target/debug -l crate_name -l{pthread,dl,m}

# Now feel free to run the compiled binary
./main
  • 有关额外-l…标志的说明。

    这些标志取决于所使用的Rust标准库版本以及编译所使用的系统。为了可靠地知道应该使用哪些标志,应该查询rustc

    简单命令

    rustc --crate-type=staticlib --print=native-static-libs -</dev/null
    

    这将输出到stderr,大致如下

    note: Link against the following native artifacts when linking against this static library. The order and any duplication can be significant on some platforms.
    
    note: native-static-libs: -lSystem -lresolv -lc -lm -liconv
    

    使用类似sed -nE 's/^note: native-static-libs: (.*)/\1/p'的命令可以方便地提取这些标志

    rustc --crate-type=staticlib --print=native-static-libs -</dev/null \
        2>&1 | sed -nE 's/^note: native-static-libs: (.*)/\1/p'
    

    理想情况下,您不会在真空状态下查询此信息(例如,使用/dev/null文件作为上面的输入Rust代码),而是将其应用于您正在编译的实际代码

    cargo rustc -q -- --print=native-static-libs \
        2>&1 | sed -nE 's/^note: native-static-libs: (.*)/\1/p'
    

    如果您真的想进一步完善,可以使用JSON格式的编译器输出(例如,这可以避免重定向stderr)。但这时您需要使用JSON解析器,如jq

    RUST_STDLIB_DEPS=$(set -eo pipefail && \
        cargo rustc \
            --message-format=json \
            -- --print=native-static-libs \
        | jq -r '
            select (.reason == "compiler-message")
            | .message.message
        ' | sed -nE 's/^native-static-libs: (.*)/\1/p' \
    )
    

    然后使用

    cc -o main{,.c} -L target/debug -l crate_name ${RUST_STDLIB_DEPS}
    

它将输出

Point { x: 42.0, y: 42.0 }

🚀🚀

开发

测试

safer-ffi 包含三个不同的测试套件,可以运行。

# In the project root:
cargo test

# FFI tests

make -C ffi_tests

# JavaScript tests

make -C js_tests

# Running the JS tests also gives you instructions for running browser tests.
# Run this command in the `js_tests` directory, open a browser and navigate to
# https://127.0.0.1:13337/
wasm-pack build --target web && python3 -m http.server 13337

依赖项

~1.6–4.5MB
~77K SLoC