-
Notifications
You must be signed in to change notification settings - Fork 61
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
2 changed files
with
87 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
# 第 35 条:优先使用bindgen而不是手动FFI映射 | ||
|
||
[第 34 条]讨论了从 Rust 程序调用 C 代码的机制,描述了 C 结构和函数的声明需要有一个等效的 Rust 声明,以允许它们通过 FFI 使用。C 和 Rust 的声明需要保持同步,并且[第 34 条]还警告说工具链不会帮助解决这个问题 —— 不匹配将会被默默忽略,隐藏以后会出现的问题。 | ||
|
||
让两件事情完全同步听起来像是自动化的好目标,Rust 项目为此提供了正确的工具:bindgen。bindgen 的主要功能是解析 C 头文件并生成相应的 Rust 声明。 | ||
以[第 34 条]中的一些 C 声明为例: | ||
|
||
```c | ||
/* File lib.h */ | ||
#include <stdint.h> | ||
|
||
typedef struct { | ||
uint8_t byte; | ||
uint32_t integer; | ||
} FfiStruct; | ||
|
||
int add(int x, int y); | ||
uint32_t add32(uint32_t x, uint32_t y); | ||
``` | ||
`bindgen` 工具可以手动调用(或通过 `build.rs` [构建脚本]调用)以创建相应的 Rust 文件: | ||
```bash | ||
% bindgen --no-layout-tests \ | ||
--allowlist-function="add.*" \ | ||
--allowlist-type=FfiStruct \ | ||
-o src/generated.rs \ | ||
lib.h | ||
``` | ||
|
||
生成的 Rust 代码与[第 34 条]中手工编写的声明是完全相同的: | ||
|
||
```rust | ||
/* automatically generated by rust-bindgen 0.59.2 */ | ||
|
||
#[repr(C)] | ||
#[derive(Debug, Copy, Clone)] | ||
pub struct FfiStruct { | ||
pub byte: u8, | ||
pub integer: u32, | ||
} | ||
extern "C" { | ||
pub fn add( | ||
x: ::std::os::raw::c_int, | ||
y: ::std::os::raw::c_int, | ||
) -> ::std::os::raw::c_int; | ||
} | ||
extern "C" { | ||
pub fn add32(x: u32, y: u32) -> u32; | ||
} | ||
``` | ||
|
||
并且可以通过源码级别的 [`include!` 宏]引入到 Rust 代码中: | ||
|
||
```rust | ||
// Include the auto-generated Rust declarations. | ||
include!("generated.rs"); | ||
``` | ||
|
||
对于任何非最简单的 FFI 声明,请使用 bindgen 为 C 代码生成 Rust 绑定 —— 在这个领域,机器制作的大规模生产代码绝对优于手工精制的声明。如果 C 函数定义发生变化,C 编译器会在 C 声明不再与 C 定义匹配时发出抱怨,但不会有东西抱怨手工编写的 Rust 声明不再与 C 声明匹配;从 C 声明自动生成 Rust 声明可以确保两者保持同步。 | ||
|
||
这也意味着 bindgen 步骤是理想的候选者,可以包含在 CI 系统([第 32 条])中;如果生成的代码包含在源码控制中,CI 系统可以在新生成的文件与检入的版本不匹配时报错。 | ||
|
||
当您处理具有大量 API 的现有 C 代码库时,bindgen 工具才能真正发挥其作用。为一个庞大的 `lib_api.h` 头文件创建 Rust 等价物是手动且乏味的,因此容易出错 —— 并且如前所述,许多不匹配错误的类别不会被工具链检测到。bindgen 还拥有一系列选项,允许针对 API 的特定子集(比如,之前展示的 -- `allowlist-function` 和 `--allowlist-type` 选项)。 | ||
|
||
这也允许采用分层方法在 Rust 中暴露现有的 C 库;包装某个 xyzzy 库的一个常见约定是拥有以下内容: | ||
* 一个仅包含 bindgen 生成的代码的 `xyzzy-sys crate` —— 其使用必然是不安全的。 | ||
* 一个 `xyzzy crate`,它封装了不安全代码,并提供对底层功能的安全 Rust 访问。 | ||
|
||
这将在一个层中集中不安全代码,并允许程序的其他部分遵循[第 16 条]的建议。 | ||
|
||
### 超越C语言 | ||
|
||
`bindgen` 工具能够处理一些 `C++` 结构,但只是有限的一部分,并且方式有限。为了更好的(尽管仍然有限)集成,可以考虑使用 `cxx crate` 进行 `C++/Rust` 交互操作。`cxx` 不是从 `C++` 声明生成 `Rust` 代码,而是采用从公共模式自动生成 `Rust` 和 `C++` 代码的方法,允许更紧密的集成。 | ||
|
||
#### 注释 | ||
[^1]: 示例还使用了 `--no-layout-tests` 选项以保持输出简单;默认情况下,生成的代码将包含 `#[test]` 代码,以检查结构体确实正确布局。 | ||
|
||
|
||
|
||
<!-- 参考链接 --> | ||
[第 16 条]: ../chapter_3/item16-unsafe.md | ||
[第 32 条]: https://www.lurklurk.org/effective-rust/ci.html | ||
[第 34 条]: [item5-casts.md](https://www.lurklurk.org/effective-rust/ffi.html) | ||
[构建脚本]: https://doc.rust-lang.org/cargo/reference/build-scripts.html | ||
[`include!` 宏]: https://doc.rust-lang.org/std/macro.include.html |