|
| 1 | +# 第 35 条:优先使用bindgen而不是手动FFI映射 |
| 2 | + |
| 3 | +[第 34 条]讨论了从 Rust 程序调用 C 代码的机制,描述了 C 结构和函数的声明需要有一个等效的 Rust 声明,以允许它们通过 FFI 使用。C 和 Rust 的声明需要保持同步,并且[第 34 条]还警告说工具链不会帮助解决这个问题 —— 不匹配将会被默默忽略,隐藏以后会出现的问题。 |
| 4 | + |
| 5 | +让两件事情完全同步听起来像是自动化的好目标,Rust 项目为此提供了正确的工具:bindgen。bindgen 的主要功能是解析 C 头文件并生成相应的 Rust 声明。 |
| 6 | +以[第 34 条]中的一些 C 声明为例: |
| 7 | + |
| 8 | +```c |
| 9 | +/* File lib.h */ |
| 10 | +#include <stdint.h> |
| 11 | + |
| 12 | +typedef struct { |
| 13 | + uint8_t byte; |
| 14 | + uint32_t integer; |
| 15 | +} FfiStruct; |
| 16 | + |
| 17 | +int add(int x, int y); |
| 18 | +uint32_t add32(uint32_t x, uint32_t y); |
| 19 | +``` |
| 20 | +
|
| 21 | +`bindgen` 工具可以手动调用(或通过 `build.rs` [构建脚本]调用)以创建相应的 Rust 文件: |
| 22 | +
|
| 23 | +```bash |
| 24 | +% bindgen --no-layout-tests \ |
| 25 | + --allowlist-function="add.*" \ |
| 26 | + --allowlist-type=FfiStruct \ |
| 27 | + -o src/generated.rs \ |
| 28 | + lib.h |
| 29 | +``` |
| 30 | + |
| 31 | +生成的 Rust 代码与[第 34 条]中手工编写的声明是完全相同的: |
| 32 | + |
| 33 | +```rust |
| 34 | +/* automatically generated by rust-bindgen 0.59.2 */ |
| 35 | + |
| 36 | +#[repr(C)] |
| 37 | +#[derive(Debug, Copy, Clone)] |
| 38 | +pub struct FfiStruct { |
| 39 | + pub byte: u8, |
| 40 | + pub integer: u32, |
| 41 | +} |
| 42 | +extern "C" { |
| 43 | + pub fn add( |
| 44 | + x: ::std::os::raw::c_int, |
| 45 | + y: ::std::os::raw::c_int, |
| 46 | + ) -> ::std::os::raw::c_int; |
| 47 | +} |
| 48 | +extern "C" { |
| 49 | + pub fn add32(x: u32, y: u32) -> u32; |
| 50 | +} |
| 51 | +``` |
| 52 | + |
| 53 | +并且可以通过源码级别的 [`include!` 宏]引入到 Rust 代码中: |
| 54 | + |
| 55 | +```rust |
| 56 | +// Include the auto-generated Rust declarations. |
| 57 | +include!("generated.rs"); |
| 58 | +``` |
| 59 | + |
| 60 | +对于任何非最简单的 FFI 声明,请使用 bindgen 为 C 代码生成 Rust 绑定 —— 在这个领域,机器制作的大规模生产代码绝对优于手工精制的声明。如果 C 函数定义发生变化,C 编译器会在 C 声明不再与 C 定义匹配时发出抱怨,但不会有东西抱怨手工编写的 Rust 声明不再与 C 声明匹配;从 C 声明自动生成 Rust 声明可以确保两者保持同步。 |
| 61 | + |
| 62 | +这也意味着 bindgen 步骤是理想的候选者,可以包含在 CI 系统([第 32 条])中;如果生成的代码包含在源码控制中,CI 系统可以在新生成的文件与检入的版本不匹配时报错。 |
| 63 | + |
| 64 | +当您处理具有大量 API 的现有 C 代码库时,bindgen 工具才能真正发挥其作用。为一个庞大的 `lib_api.h` 头文件创建 Rust 等价物是手动且乏味的,因此容易出错 —— 并且如前所述,许多不匹配错误的类别不会被工具链检测到。bindgen 还拥有一系列选项,允许针对 API 的特定子集(比如,之前展示的 -- `allowlist-function` 和 `--allowlist-type` 选项)。 |
| 65 | + |
| 66 | +这也允许采用分层方法在 Rust 中暴露现有的 C 库;包装某个 xyzzy 库的一个常见约定是拥有以下内容: |
| 67 | +* 一个仅包含 bindgen 生成的代码的 `xyzzy-sys crate` —— 其使用必然是不安全的。 |
| 68 | +* 一个 `xyzzy crate`,它封装了不安全代码,并提供对底层功能的安全 Rust 访问。 |
| 69 | + |
| 70 | +这将在一个层中集中不安全代码,并允许程序的其他部分遵循[第 16 条]的建议。 |
| 71 | + |
| 72 | +### 超越C语言 |
| 73 | + |
| 74 | +`bindgen` 工具能够处理一些 `C++` 结构,但只是有限的一部分,并且方式有限。为了更好的(尽管仍然有限)集成,可以考虑使用 `cxx crate` 进行 `C++/Rust` 交互操作。`cxx` 不是从 `C++` 声明生成 `Rust` 代码,而是采用从公共模式自动生成 `Rust` 和 `C++` 代码的方法,允许更紧密的集成。 |
| 75 | + |
| 76 | +#### 注释 |
| 77 | +[^1]: 示例还使用了 `--no-layout-tests` 选项以保持输出简单;默认情况下,生成的代码将包含 `#[test]` 代码,以检查结构体确实正确布局。 |
| 78 | + |
| 79 | + |
| 80 | + |
| 81 | +<!-- 参考链接 --> |
| 82 | +[第 16 条]: ../chapter_3/item16-unsafe.md |
| 83 | +[第 32 条]: https://www.lurklurk.org/effective-rust/ci.html |
| 84 | +[第 34 条]: [item5-casts.md](https://www.lurklurk.org/effective-rust/ffi.html) |
| 85 | +[构建脚本]: https://doc.rust-lang.org/cargo/reference/build-scripts.html |
| 86 | +[`include!` 宏]: https://doc.rust-lang.org/std/macro.include.html |
0 commit comments