cbindgen 完全指南:Rust 库的 C/C++ 绑定自动生成与配置精要
cbindgen
是一个用于为 Rust 库生成 C/C++11 头文件的工具,这些库公开了一个公共的 C API。
虽然你可以手工完成这项工作,但这并不是一个特别好的时间利用方式。与基于你实际 Rust 代码的机器生成的头文件相比,手工编写的头文件更有可能出错。cbindgen 的开发者还与 Rust 的开发者紧密合作,以确保我们生成的头文件反映了 Rust 的类型布局和 ABI 的实际保证。
C++ 头文件的好处在于,我们可以使用运算符重载、构造函数、枚举类和模板,使 API 更加符合人体工程学,更像 Rust。C 头文件的好处在于,你可以更有信心地确信,与你互操作的任何人都能够处理它们。使用 cbindgen,_你不需要选择_!你只需告诉它同时发出两者即可。
使用 cbindgen 有两种方式:作为一个独立的程序,或者作为一个库(可能是在你的 build.rs 中)。实际上并没有太大的区别,因为 cbindgen 是一个简单的 Rust 库,没有有趣的依赖。
作为一个程序使用意味着构建你的软件的人需要安装它。在库中使用意味着人们可能需要更频繁地构建 cbindgen(例如,每次他们更新 Rust 编译器时)。
值得注意的是,cbindgen 的开发主要是临时的,随着支持维护者使用情况的功能被添加。这意味着 cbindgen 可能会随机地无法支持某些特定情况,因为还没有人投入努力来处理它。如果你遇到这样的情况,请提交一个问题[1]。尽管我们都有其他的工作,你可能也需要自己来做实现工作 :)
快速开始
要安装 cbindgen,你只需要运行
cargo install --force cbindgen
(--force 只是确保如果 cbindgen 已经安装,它会更新到最新版本)
或者使用 Homebrew,运行
brew install cbindgen
要使用 cbindgen,你需要两样东西:
一个配置(cbindgen.toml,可以开始时为空) 一个带有公共 C API 的 Rust 库
然后你只需要运行它:
cbindgen --config cbindgen.toml --crate my_rust_library --output my_header.h
这将生成一个 C++ 的头文件。对于 C,添加 --lang c
开关。
查看 cbindgen --help
了解更多选项。
在这里阅读完整的用户文档![2]
在这里获取模板 cbindgen.toml。[3]
示例
我们目前没有一个精心定制的示例应用程序,但 测试[4] 中包含了许多有趣的功能示例。
你还可能对浏览使用 cbindgen 的生产项目感兴趣:
milksnake[5] webrender[6] (生成的头文件[7]) stylo[8] (生成的头文件[9]) maturin[10] tquic[11] (生成的头文件[12])
如果你正在使用 cbindgen
并且希望被添加到这个列表中,请提交一个拉取请求!
发布
cbindgen 没有固定的发布日程,请提交一个问题请求发布,如果主干中有一些你需要发布的固定内容。@emilio 可以提高效果。
完整文档
cbindgen 用户指南
cbindgen 为 Rust 库生成 C/C++11 头文件,这些库公开了一个公共的 C API。
虽然你可以手工完成这项工作,但这并不是一个特别好的时间利用方式。与基于你实际 Rust 代码的机器生成的头文件相比,手工编写的头文件更有可能出错。cbindgen 的开发者还与 Rust 的开发者紧密合作,以确保我们生成的头文件反映了 Rust 的类型布局和 ABI 的实际保证。
C++ 头文件的好处在于,我们可以使用运算符重载、构造函数、枚举类和模板,使 API 更加符合人体工程学,更像 Rust。C 头文件的好处在于,你可以更有信心地确信,与你互操作的任何人都能够处理它们。使用 cbindgen,_你不需要选择_!你只需告诉它同时发出两者即可。
使用 cbindgen 有两种方式:作为一个独立的程序,或者作为一个库(可能是在你的 build.rs 中)。实际上并没有太大的区别,因为 cbindgen 是一个简单的 Rust 库,没有有趣的依赖。
作为一个程序使用意味着构建你的软件的人需要安装它。在库中使用意味着人们可能需要更频繁地构建 cbindgen(例如,每次他们更新 Rust 编译器时)。
值得注意的是,cbindgen 的开发主要是临时的,随着支持维护者使用情况的功能被添加。这意味着 cbindgen 可能会随机地无法支持某些特定情况,因为还没有人投入努力来处理它。[如果你遇到这样的情况,请提交一个问题][file-it]。尽管我们都有其他的工作,你可能也需要自己来做实现工作 :)
快速开始
要安装 cbindgen,你只需要运行
cargo install --force cbindgen
(--force 只是确保如果 cbindgen 已经安装,它会更新到最新版本)
要使用 cbindgen,你需要两样东西:
一个配置(cbindgen.toml,可以开始时为空) 一个带有公共 C API 的 Rust 库
然后你只需要运行它:
cbindgen --config cbindgen.toml --crate my_rust_library --output my_header.h
这将生成一个 C++ 的头文件。对于 C,添加 --lang c
开关。
cbindgen
还支持生成 Cython[13] 绑定,使用 --lang cython
即可。
查看 cbindgen --help
了解更多选项。
在这里获取模板 cbindgen.toml[14]。
build.rs
如果你想不使用 cbindgen 作为应用程序,这里有一个示例 build.rs 脚本:
extern crate cbindgen;
use std::env;
fn main() {
let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
cbindgen::Builder::new()
.with_crate(crate_dir)
.generate()
.expect("Unable to generate bindings")
.write_to_file("bindings.h");
}
你可以使用 `Builder`[15] 接口添加配置选项。
当你积极地进行代码开发时,你可能不希望 cbindgen 失败整个构建。而不是期望头文件生成的结果,你可以[忽略语法错误][section-ignore-parse-errors],让 rustc 或你的代码分析来提出问题:
// ...
.generate()
.map_or_else(
|error| match error {
cbindgen::Error::ParseSyntaxError { .. } => {}
e => panic!("{:?}", e),
},
|bindings| {
bindings.write_to_file("target/include/bindings.h");
},
);
}
确保在 Cargo.toml 中添加以下部分:
[build-dependencies]
cbindgen = "0.24.0"
如果你想使用带有 cbindgen.toml
的 build.rs
脚本,请考虑使用 [cbindgen::generate()
][section-generate] 代替。
编写你的 C API
cbindgen 有一个简单但有效的策略。它遍历你的 crate,寻找:
#[no_mangle] pub extern fn
(“函数”)#[no_mangle] pub static
(“全局变量”)pub const
(“常量”)
并生成一个声明这些项目的头文件。但是为了声明这些项目,它还需要能够描述出现在它们签名中的类型的布局和 ABI。因此,它也会通过你的 crate(以及可选地它的依赖项)来尝试找到你公共 API 中使用的所有类型的定义。
🚨 注意:cbindgen 的一个主要限制是它不了解 Rust 的模块系统或命名空间。这意味着,如果 cbindgen 看到它需要
MyType
的定义,并且你的项目中存在两个具有类型名称MyType
的项目,它将不知道该怎么办。目前,如果发生这种情况,cbindgen 的行为是未指定的。然而,如果它们有不同的 cfgs[[section-cfgs]],这可能没问题。
如果确定一个类型具有保证的布局,将在头文件中发出完整的定义。如果类型没有保证的布局,只会发出一个前向声明。如果类型旨在不透明地通过引用传递,这可能没问题。
示例
🚧 🏗
有一些经过策划和清晰的示例将非常有用,但我们还没有任何示例。
README 有一些有用的链接[16]。
支持的类型
Rust 中的大多数事情默认没有保证的布局。在大多数情况下,这是好的,因为它允许在类型布局不是那么有趣的情况下进行优化。然而,这对我们的目的来说是有问题的。幸运的是,Rust 允许我们使用 repr
属性选择进入保证的布局。
你可以阅读 Rust 的参考[reference]来了解所有不同的 repr 属性,但这里是一个快速摘要:
#[repr(C)]
:给这个 struct/union/enum 与 C 相同的布局和 ABI#[repr(u8, u16, ... 等)]
:给这个枚举与给定整数类型相同的布局和 ABI#[repr(transparent)]
:给这个单字段 struct 与它的字段相同的 ABI(对于 newtype 整数但保持整数 ABI 很有用)
cbindgen 支持 #[repr(align(N))]
和 #[repr(packed)]
属性,但目前不支持 #[repr(packed(N))]
。
cbindgen 还支持在非 C-like 枚举(带有字段的枚举)上使用 repr(C)
/repr(u8)
。这给出了一个 C 兼容的标记联合布局,如 [RFC 2195][really-tagged-unions] 所定义。repr(C)
将给出一个更简单的布局,可能更直观,而 repr(u8)
将产生一个更紧凑的布局。
如果你确保每件事都有一个保证的 repr,那么 cbindgen 将为以下内容生成定义:
结构体(命名风格或元组风格) 枚举(无字段或带有字段) 联合体 类型 [T; n]
(数组总是有保证的 C 兼容布局)&T
,&mut T
,*const T
,*mut T
,Option<&T>
,Option<&mut T>
(都有相同的指针 ABI)fn()
(作为一个实际的函数指针)bitflags! { ... }
(如果启用了 macro_expansion.bitflags)
结构体、枚举、联合体和类型别名可以是泛型的,尽管在某些配置下某些泛型替换可能无法解析。在 C 模式下,泛型通过单态化和 mangling 来解决,而在 C++ 模式下,泛型使用模板来解决。cbindgen 无法支持泛型函数,因为它们实际上没有单一定义的符号。
cbindgen 遗憾的是永远无法支持匿名元组 (A, B, ...)
,因为没有办法保证它们的布局。你必须使用元组结构体。
cbindgen 也无法支持宽指针,如 &dyn Trait
或 &[T]
,因为它们的布局和 ABI 没有保证。在切片的情况下,你至少可以把它们分解成一个指针和长度,并使用 slice::from_raw_parts
来重建它们。
如果 cbindgen 确定一个类型是零大小的,它将擦除对该类型的所有引用(所以该类型的字段根本就不会发出)。如果该类型作为函数参数出现,这将不起作用,因为 C、C++ 和 Rust 对于一个类型意味着什么是空的都有不同定义。
不要使用 [u64; 0]
的技巧来过度对齐一个结构体,我们不支持这个。
cbindgen 包含以下硬编码的映射(再次完全忽略命名空间,只是按类型的名称进行查找):
标准库类型
bool => bool char => uint32_t u8 => uint8_t u16 => uint16_t u32 => uint32_t u64 => uint64_t usize => uintptr_t i8 => int8_t i16 => int16_t i32 => int32_t i64 => int64_t isize => intptr_t f32 => float f64 => double VaList => va_list RawFd => int PhantomData => _蒸发_,只能作为类型的字段出现 PhantomPinned => _蒸发_,只能作为类型的字段出现 () => _蒸发_,只能作为类型的字段出现 MaybeUninit , ManuallyDrop , 和 Pin => T
libc 类型
c_void => void c_char => char c_schar => signed char c_uchar => unsigned char c_float => float c_double => double c_short => short c_int => int c_long => long c_longlong => long long c_ushort => unsigned short c_uint => unsigned int c_ulong => unsigned long c_ulonglong => unsigned long long
stdint 类型
uint8_t => uint8_t uint16_t => uint16_t uint32_t => uint32_t uint64_t => uint64_t uintptr_t => uintptr_t size_t => size_t int8_t => int8_t int16_t => int16_t int32_t => int32_t int64_t => int64_t intptr_t => intptr_t ssize_t => ssize_t ptrdiff_t => ptrdiff_t
配置你的头文件
cbindgen 支持几种不同的选项来配置你的头文件输出,包括目标语言、样式、名称混淆、前缀、包含和定义。
定义和 Cfgs
当 cbindgen 遍历你的 crate 时,它会记录下它找到的所有 cfgs,这些 cfgs 出现在每个项目的路径上。如果它发现多个声明具有相同名称但有不同的 cfgs,它将尝试发出它找到的每个版本,这些版本被包装在对应于这些 cfgs 的定义中。通过这种方式,可以正确地支持平台特定的 API 或表示。
然而,cbindgen 没有办法知道你如何想要将这些 cfgs 映射到定义。你将需要使用你的 cbindgen.toml 中的 [defines]
部分来指定所有不同的映射。它本地理解 any() 和 all() 等概念,所以你只需要告诉它如何转换像 target_os = "freebsd"
或 feature = "serde"
这样的基础概念。
请注意,因为 cbindgen 只解析你的 crate 的源代码,你基本上不需要担心你的 crate 特性或你正在针对的平台。每个可能的配置都应该对解析器可见。我们的原始映射也应该完全平台无关(i32 在任何目标上都是 int32_t)。
虽然 crate 中的模块形成一个具有唯一定义路径到每个项目的树,因此具有那些项目的唯一定义 cfgs,但依赖项不是这样的。如果你以多种方式依赖一个 crate,并且这些方式产生不同的 cfgs,那么任何一个 cfgs 将被任意选择用于在该 crate 中找到的任何类型。
注解
虽然输出配置主要是通过 cbindgen.toml 完成的,但在某些情况下你需要手动覆盖你的全局设置。在这些情况下,你可以添加内联注释到你的类型,这些注释是以 cbindgen:
开头的文档注释。这里是一个使用注释重命名结构体字段并启用重载 operator==
的示例:
/// cbindgen:field-names=[x, y]
/// cbindgen:derive-eq
#[repr(C)]
pub struct Point(pub f32, pub f32);
一个注释可以是一个布尔值、字符串(没有引号)或字符串列表。如果只提供了注释的名称,假设 =true
。注释解析器目前相当简单,没有转义的能力,所以不要尝试使用任何带有 =
、,
、[
或 ]
的字符串。
大多数注释只是 cbindgen.toml 中相同设置的本地覆盖,但有一些是独特的,因为它们在全局上下文中没有意义。支持的注释集如下:
忽略注释
cbindgen 会自动忽略它找到的任何 #[test]
或 #[cfg(test)]
项目。你可以使用 ignore
注解属性手动忽略其他内容:
pub mod my_interesting_mod;
/// cbindgen:ignore
pub mod my_uninteresting_mod; // This won't be scanned by cbindgen.
无导出注释
cbindgen 通常会发出它找到的所有项目,正如解析和导出配置部分所指示的。这个注释将使 cbindgen 在输出中跳过此项目,同时仍然意识到它。这对于 a) 抑制 "Can't find" 错误和 b) 为在不同头文件中的类型发出 struct my_struct
(而不是裸的 my_struct
)很有用。
这个注释没有等效的配置 - 相比之下,导出排除配置会导致 cbindgen 根本不会意识到这个项目。
注意,cbindgen 仍然会遍历 no-export
structs,如果它们是 repr(C)
,以发出字段中出现的类型。如果需要,你将需要在你的配置中手动排除这些类型。
/// cbindgen:no-export
#[repr(C)]
pub struct Foo { .. }; // This won't be emitted by cbindgen in the header
#[repr(C)]
fn bar() -> Foo { .. } // Will be emitted as `struct foo bar();`
结构体注释
field-names=[field1, field2, ...] -- 设置输出结构体中所有字段的名称。这些名称将逐字输出,并且不符合重命名条件。
其余的只是 cbindgen.toml 中找到的相同选项的本地覆盖:
rename-all=RenameRule derive-constructor derive-eq derive-neq derive-lt derive-lte derive-gt derive-gte {eq,neq,lt,lte,gt,gte}-attributes: 采用单一标识符,将被发出在自动生成的 operator==
/operator!=
/ 等(如果有)的签名之前。这个想法是将这个用于注释运算符与属性,例如:
/// cbindgen:eq-attributes=MY_ATTRIBUTES
#[repr(C)]
pub struct Foo { .. }
将生成类似的东西:
MY_ATTRIBUTES bool operator==(const Foo& other) const {
...
}
结合类似的东西:
#define MY_ATTRIBUTES [[nodiscard]]
例如。
枚举注释
enum-trailing-values=[variant1, variant2, ...] -- 在枚举定义的末尾添加以下无字段的枚举变体。这些变体名称将应用枚举的重命名规则。
警告:如果这些值中的任何一个曾经传递到 Rust,行为将是未定义的。Rust 不知道它们,并将假设它们不可能发生。
其余的只是 cbindgen.toml 中找到的相同选项的本地覆盖:
rename-all=RenameRule add-sentinel derive-helper-methods derive-const-casts derive-mut-casts derive-tagged-enum-destructor derive-tagged-enum-copy-constructor enum-class prefix-with-name private-default-tagged-enum-constructor {destructor,copy-constructor,copy-assignment}-attributes: 见结构体属性的描述,这些对相应的生成代码做同样的事情。
枚举变体注释
这些适用于标记和未标记的枚举变体。
variant-{constructor,const-cast,mut-cast,is}-attributes: 见结构体属性的描述。这些对相应的函数做同样的事情。
TODO: 我们应该允许使用每个变体的注释来覆盖 derive-{const,mut}-casts
,帮助方法等,可能。
联合注释
field-names=[field1, field2, ...] -- 设置输出联合体中所有字段的名称。这些名称将逐字输出,并且不符合重命名条件。
其余的只是 cbindgen.toml 中找到的相同选项的本地覆盖:
rename-all=RenameRule
函数注释
所有函数属性只是 cbindgen.toml 中找到的相同选项的本地覆盖:
rename-all=RenameRule prefix postfix ptrs-as-arrays=[[ptr_name1; array_length1], [ptr_name2; array_length2], ...] -- 将函数的指针参数表示为数组。下面是如何执行映射的:
arg: *const T --> const T arg[array_length]
arg: *mut T ---> T arg[array_length]
如果未指定 array_length
:
arg: *const T --> const T arg[]
arg: *mut T --> T arg[]
生成 Swift 绑定
除了在 C/C++ 头文件中解析函数名称,Swift 编译器还可以利用函数上的 swift_name
属性,为导入的函数和方法生成更符合习惯的名称。
这个属性通常在 Objective-C/C
/C++ 中通过 NS_SWIFT_NAME
和 CF_SWIFT_NAME
宏使用。
给定 cbindgen.toml 中的配置,cbindgen
可以通过猜测基于现有函数名称(以及类型,如果它是 impl
块中的方法)的适当方法签名,为你生成这些属性。
这由 cbindgen.toml 中的 swift_name_macro
选项控制。
cbindgen.toml
大多数配置通过你的 cbindgen.toml 文件进行。每个值都有默认值(通常是合理的),所以你可以从空的 cbindgen.toml 开始,调整它,直到你喜欢你得到的输出。
注意,这里定义的许多选项只适用于 C 或 C++ 中的一个。通常它是一个选项,指定我们是否应该尝试利用 C++ 类型系统中的一个特性或生成一个辅助方法。
# 输出绑定的语言
#
# 可能的值:"C", "C++", "Cython"
#
# 默认值:"C++"
language = "C"
# 包装头文件内容的选项:
# 在生成文件的开头输出的可选字符串
# 默认值:不发出任何内容
header = "/* 在生成文件开头放置的文本。可能是许可证。 */"
# 在生成文件末尾输出的可选字符串
# 默认值:不发出任何内容
trailer = "/* 在生成文件末尾放置的文本 */"
# 用作 include 守卫的可选名称
# 默认值:不发出 include 守卫
include_guard = "mozilla_wr_bindings_h"
# 是否添加 `#pragma once` 守卫
# 默认值:不发出 `#pragma once`
pragma_once = true
# 在生成文件的主要部分之间输出的可选字符串,作为手动编辑的警告
#
# 默认值:不发出任何内容
autogen_warning = "/* 警告,此文件由 cbindgen 自动生成。不要手动修改此文件。 */"
# 是否包含注释,说明用于生成文件的 cbindgen 版本
# 默认值:false
include_version = true
# 包装生成绑定的可选命名空间
# 默认值:不发出命名空间
namespace = "ffi"
# 包装生成绑定的可选命名空间列表
# 默认值:[]
namespaces = ["mozilla", "wr"]
# 作为使用 "using namespace" 声明的命名空间列表
# 默认值:[]
using_namespaces = ["mozilla", "wr"]
# 要 #include(尖括号中)的 sys 头文件列表
# 默认值:[]
sys_includes = ["stdio", "string"]
# 要 #include(引号中)的头文件列表
# 默认值:[]
includes = ["my_great_lib.h"]
# 是否抑制 cbindgen 的默认 C/C++ 标准导入。这些导入默认情况下是包含的,因为我们生成的头文件往往需要它们(例如,对于 uint32_t)。当前,生成的导入为:
#
# * 对于 C:<stdarg.h>, <stdbool.h>, <stdint.h>, <stdlib.h>, <uchar.h>
#
# * 对于 C++:<cstdarg>, <cstdint>, <cstdlib>, <new>, <cassert>(取决于配置)
#
# 默认值:false
no_includes = false
# 是否使 C 头文件与 C++ 兼容。
# 这将把生成的函数包装在 `extern "C"` 块中,例如
#
# #ifdef __cplusplus
# extern "C" {
# #endif // __cplusplus
#
# // 生成的函数。
#
# #ifdef __cplusplus
# } // extern "C"
# #endif // __cplusplus
#
# 如果语言不是 C,这个选项将不会有任何效果。
#
# 默认值:false
cpp_compat = false
# 在包含块之后添加的逐字字符串列表
after_includes = "#define VERSION 1"
# 代码样式选项
# 使用的花括号样式
#
# 可能的值:"SameLine", "NextLine"
#
# 默认值:"SameLine"
braces = "SameLine"
# 格式化行时使用的期望行长
# 默认值:100
line_length = 80
# 缩进时使用的空格数
# 默认值:2
tab_width = 3
# 从 Rust 中包含文档注释作为文档
documentation = true
# 生成文档的样式。
#
# 可能的值:
# * "c":/* 像这样 */
# * "c99":// 像这样
# * "c++":/// 像这样
# * "doxy":每行前面加上 * 的 C 风格
# * "auto":如果是语言是 "c++",则为 "c++",否则为 "doxy"
#
# 默认值:"auto"
documentation_style = "doxy"
# 输出每个项的文档数量。
#
# 可能的值:
# * "short":仅第一行。
# * "full":全部文档。
#
# 默认值:"full"
documentation_length = "short"
# 代码生成选项
# 生成 C 头文件时,用于结构体或枚举的声明样式。
#
# 可能的值:
# * "type":typedef struct { ... } MyType;
# * "tag":struct MyType { ... };
# * "both":typedef struct MyType { ... } MyType;
#
# 默认值:"both"
style = "both"
# 如果此选项为 true,则 `usize` 和 `isize` 将分别转换为 `size_t` 和 `ptrdiff_t`
# 而不是 `uintptr_t` 和 `intptr_t`。
usize_is_size_t = true
# 用于将 cfg 转换为 ifdefs 的替换列表。这里没有定义的 cfg
# 将被丢弃。
#
# 例如
# `#[cfg(target = "freebsd")] ...`
# 变为
# `#if defined(DEFINE_FREEBSD) ... #endif`
[defines]
"target_os = freebsd" = "DEFINE_FREEBSD"
"feature = serde" = "DEFINE_SERDE"
[export]
# 如果找到但似乎没有被公共 API 使用,总是包括在生成的绑定中的附加项目列表
#
# 默认值:[]
include = ["MyOrphanStruct", "MyGreatTypeRename"]
# 不包括在生成的绑定中的项目列表
#
# 默认值:[]
exclude = ["Bad"]
# 添加到每个项目名称之前的前缀
#
# 默认值:不添加前缀
prefix = "CAPI_"
# 我们将生成的项目类型。如果为空,则发出所有类型的项目。
#
# 可能的项目:(TODO:在此处详细解释这些)
# * "constants":
# * "globals":
# * "enums":
# * "structs":
# * "unions":
# * "typedefs":
# * "opaque":
# * "functions":
#
# 默认值:[]
item_types = ["enums", "structs", "opaque", "functions"]
# 应用 export.rename 中的规则是否阻止 export.prefix 应用。
#
# 例如,给定此 toml:
#
# [export]
# prefix = "capi_"
# [export.rename]
# "MyType" = "my_cool_type"
#
# 你将得到以下结果:
#
# renaming_overrides_prefixing = true:
# "MyType" => "my_cool_type"
#
# renaming_overrides_prefixing = false:
# "MyType" => "capi_my_cool_type"
#
# 默认值:false
renaming_overrides_prefixing = true
# 应用于项目名称的名称转换表(左侧变为右侧)
[export.rename]
"MyType" = "my_cool_type"
"my_function" = "BetterFunctionName"
# 应用于任何具有给定名称的结构体、联合体或枚举的主体前面的文本表。这可以用于添加不改变 ABI 的方法,将字段标记为私有等
[export.pre_body]
"MyType" = """
MyType() = delete;
private:
"""
# 应用于任何具有给定名称的结构体、联合体或枚举的主体后面的文本表。这可以用于添加不改变 ABI 的方法。
[export.body]
"MyType" = """
void cppMethod() const;
"""
# 名称混淆的配置
[export.mangle]
# 是否在名称混淆期间重命名类型,例如
# c_char -> CChar 等。
rename_types = "PascalCase"
# 是否省略名称混淆名称中的下划线。
remove_underscores = false
[layout]
# 应该放在任何被标记为 `#[repr(packed)]` 的类型名称前面的字符串。例如,针对 gcc/clang,合理的值可能是 "__attribute__((packed))"。一个更便携的解决方案可能涉及发出你在平台特定方式中定义的宏的名称。例如 "PACKED"
#
# 默认值:`#[repr(packed)]` 类型将被视为不透明,因为对于 C 调用者来说,使用一个布局不正确的联合体将是不安全的。
packed = "PACKED"
#
应该放在任何被标记为 `#[repr(align(n))]` 的类型名称前面的字符串。这个字符串必须是接受单个参数(请求的对齐方式,`n`)的函数式宏。例如,如果在 `header` 中定义为 `ALIGNED(n)` 的宏,其转换为 `__attribute__((aligned(n)))` 可能是针对 gcc/clang 的合理值。
#
# 默认值:`#[repr(align(n))]` 类型将被视为不透明,因为对于 C 调用者来说,使用一个不正确对齐的联合体可能是不安全的。
aligned_n = "ALIGNED"
[fn]
# 每个函数声明前放置的可选前缀
# 默认值:不添加前缀
prefix = "WR_START_FUNC"
# 任何函数声明后放置的可选后缀
# 默认值:不添加后缀
postfix = "WR_END_FUNC"
# 格式化函数参数的方式
#
# 可能的值:
# * "horizontal":在同一行放置所有参数
# * "vertical":每个参数单独一行
# * "auto":如果水平布局超出 line_length,则使用垂直布局
#
# 默认值:"auto"
args = "horizontal"
# 应该放在被标记为 `#[must_use]` 的函数声明前的可选字符串。例如,如果针对 gcc/clang,"__attribute__((warn_unused_result))" 可能是一个合理的值。一个更便携的解决方案可能涉及发出你在平台特定方式中定义的宏的名称。例如 "MUST_USE_FUNC"
# 默认值:对于 must_use 函数不发出任何内容
must_use = "MUST_USE_FUNC"
# 应该放在被标记为 `#[deprecated]` 且没有注释的函数声明前的可选字符串。例如,如果针对 gcc/clang,"__attribute__((deprecated))" 可能是一个合理的值。一个更便携的解决方案可能涉及发出你在平台特定方式中定义的宏的名称。例如 "DEPRECATED_FUNC"
# 默认值:对于已弃用的函数不发出任何内容
deprecated = "DEPRECATED_FUNC"
# 应该放在被标记为 `#[deprecated(note = "reason")]` 的函数声明前的可选字符串。`{}` 将被替换为双引号字符串。例如,如果针对 gcc/clang,"__attribute__((deprecated({})))" 可能是一个合理的值。一个更便携的解决方案可能涉及发出你在平台特定方式中定义的宏的名称。例如 "DEPRECATED_FUNC_WITH_NOTE(note)"
# 默认值:对于已弃用的函数不发出任何内容
deprecated_with_notes = "DEPRECATED_FUNC_WITH_NOTE"
# 应用于不返回的函数(在 Rust 中返回 `!`)的属性位置的可选字符串。
#
# 例如,如果针对 gcc/clang,"__attribute__((noreturn))" 可能是一个合理的值。
no_return = "NO_RETURN"
# 用于为生成的函数生成 Swift 函数和方法签名的可选字符串,例如 "CF_SWIFT_NAME"。
# 如果你的工具链中没有这样的宏,你可以使用 cbindgen.toml 中的 `header` 选项来定义一个
# 默认值:不生成 swift_name 函数属性
swift_name_macro = "CF_SWIFT_NAME"
# 用于重命名函数参数名称的规则。重命名假设输入是 Rust 标准的 snake_case,但它接受所有不同的 rename_args 输入。这意味着这里的许多选项都是无操作或多余的。
#
# 可能的值(实际有效的):
# * "CamelCase":my_arg => myArg
# * "PascalCase":my_arg => MyArg
# * "GeckoCase":my_arg => aMyArg
# * "ScreamingSnakeCase":my_arg => MY_ARG
# * "None":不应用重命名
#
# 技术上可能的值(在这里不应该有目的):
# * "SnakeCase":不应用重命名
# * "LowerCase":不应用重命名(实际上应用 to_lowercase,这是不是一个 bug?)
# * "UpperCase":在这种情况下与 ScreamingSnakeCase 相同
# * "QualifiedScreamingSnakeCase" => 在这种情况下与 ScreamingSnakeCase 相同
#
# 默认值:"None"
rename_args = "PascalCase"
# 指定函数将如何排序的规则。
#
# "Name":按函数名称排序
# "None":保持函数已解析的顺序
#
# 默认值:"None"
sort_by = "Name"
[struct]
# 用于重命名结构体字段名称的规则。重命名假设输入是 Rust 标准的 snake_case,但它接受所有不同的 rename_args 输入。这意味着这里的许多选项都是无操作或多余的。
#
# 可能的值(实际有效的):
# * "CamelCase":my_arg => myArg
# * "PascalCase":my_arg => MyArg
# * "GeckoCase":my_arg => mMyArg
# * "ScreamingSnakeCase":my_arg => MY_ARG
# * "None":不应用重命名
#
# 技术上可能的值(在这里不应该有目的):
# * "SnakeCase":不应用重命名
# * "LowerCase":不应用重命名(实际上应用 to_lowercase,这是不是一个 bug?)
# * "UpperCase":在这种情况下与 ScreamingSnakeCase 相同
# * "QualifiedScreamingSnakeCase" => 在这种情况下与 ScreamingSnakeCase 相同
#
# 默认值:"None"
rename_fields = "PascalCase"
# 应该放在被标记为 `#[must_use]` 的任何结构体名称之前的可选字符串。例如,如果针对 gcc/clang,"__attribute__((warn_unused))" 可能是一个合理的值。一个更便携的解决方案可能涉及发出你在平台特定方式中定义的宏的名称。例如 "MUST_USE_STRUCT"
#
# 默认值:对于 must_use 结构体不发出任何内容
must_use = "MUST_USE_STRUCT"
# 应该放在被标记为 `#[deprecated]` 且没有注释的任何结构体名称之前的可选字符串。例如,如果针对 gcc/clang,"__attribute__((deprecated))" 可能是一个合理的值。一个更便携的解决方案可能涉及发出你在平台特定方式中定义的宏的名称。例如 "DEPRECATED_STRUCT"
# 默认值:对于已弃用的结构体不发出任何内容
deprecated = "DEPRECATED_STRUCT"
# 应该放在被标记为 `#[deprecated(note = "reason")]` 的任何结构体名称之前的可选字符串。`{}` 将被替换为双引号字符串。例如,如果针对 gcc/clang,"__attribute__((deprecated({})))" 可能是一个合理的值。一个更便携的解决方案可能涉及发出你在平台特定方式中定义的宏的名称。例如 "DEPRECATED_STRUCT_WITH_NOTE(note)"
# 默认值:对于已弃用的结构体不发出任何内容
deprecated_with_notes = "DEPRECATED_STRUCT_WITH_NOTE"
# 是否应该在结构体中发出与关联常量一起的 Rust 类型。如果目标是 C,或者
# [const]allow_static_const = false,这不会做任何事情
#
# 默认值:false
# associated_constants_in_body: false
# 是否派生一个简单的构造函数,该构造函数为每个字段获取一个值。
# 默认值:false
derive_constructor = true
# 是否为所有结构体派生 operator==。
# 默认值:false
derive_eq = false
# 是否为所有结构体派生 operator!=。
# 默认值:false
derive_neq = false
# 是否为所有结构体派生 operator<。
# 默认值:false
derive_lt = false
# 是否为所有结构体派生 operator<=。
# 默认值:false
derive_lte = false
# 是否为所有结构体派生 operator>。
# 默认值:false
derive_gt = false
# 是否为所有结构体派生 operator>=。
# 默认值:false
derive_gte = false
[enum]
# 用于重命名枚举变体以及这些变体可能具有的字段名称的规则。这可能应该被拆分成两个单独的选项,但目前,它们是相同的!看看 [struct]rename_fields 对字段如何应用的文档。变体的重命名假设输入是 Rust 标准的 PascalCase。在 QualifiedScreamingSnakeCase 的情况下,它还假设枚举的名称是 PascalCase。
#
# 可能的值(实际有效的):
# * "CamelCase":MyVariant => myVariant
# * "SnakeCase":MyVariant => my_variant
# * "ScreamingSnakeCase":MyVariant => MY_VARIANT
# * "QualifiedScreamingSnakeCase":MyVariant => ENUM_NAME_MY_VARIANT
# * "LowerCase":MyVariant => myvariant
# * "UpperCase":MyVariant => MYVARIANT
# * "None":不应用重命名
#
# 技术上可能的值(对于变体不应该有目的):
# * "PascalCase":不应用重命名
# * "GeckoCase":不应用重命名
#
# 默认值:"None"
rename_variants = "None"
# 是否为所有生成的枚举添加额外的 "sentinel" 枚举变
体。Firefox 在他们的 IPC 序列化库中使用这个。
#
# 警告:如果 sentinel 曾经传递到 Rust,行为将是未定义的。
# Rust 不知道这个值,并将假设它不可能发生。
#
# 默认值:false
add_sentinel = false
# 是否应该用枚举的名称作为前缀来发出枚举变体名称。
# 默认值:false
prefix_with_name = false
# 当目标是 C++ 时,是否使用 "enum class" 发出枚举。
# 默认值:true
enum_class = true
# 是否为带有字段的枚举生成静态 `::MyVariant(..)` 构造函数和 `bool IsMyVariant()` 方法。
#
# 默认值:false
derive_helper_methods = false
# 是否为带有字段的枚举生成 `const MyVariant& AsMyVariant() const` 方法。
# 默认值:false
derive_const_casts = false
# 是否为带有字段的枚举生成 `MyVariant& AsMyVariant()` 方法。
# 默认值:false
derive_mut_casts = false
# 在派生 `AsMyVariant()` 强制类型方法的正文中使用断言 `IsMyVariant()` 的宏/函数的名称。
#
# 默认值:"assert"(但也会默认包含 `<cassert>`)
cast_assert_name = "MOZ_RELEASE_ASSERT"
# 应该放在被标记为 `#[must_use]` 的任何枚举名称之前的可选字符串。例如,如果针对 gcc/clang,"__attribute__((warn_unused))" 可能是一个合理的值。一个更便携的解决方案可能涉及发出你在平台特定方式中定义的宏的名称。例如 "MUST_USE_ENUM"
#
# 注意,这指的是 *输出* 类型。这意味着这将不适用于带有字段的枚举,因为它将作为结构体发出。`[struct]must_use` 将在那里适用。
#
# 默认值:对于 must_use 枚举不发出任何内容
must_use = "MUST_USE_ENUM"
# 应该放在被标记为 `#[deprecated]` 且没有注释的任何枚举名称之前的可选字符串。例如,如果针对 gcc/clang,"__attribute__((deprecated))" 可能是一个合理的值。一个更便携的解决方案可能涉及发出你在平台特定方式中定义的宏的名称。例如 "DEPRECATED_ENUM"
# 默认值:对于已弃用的枚举不发出任何内容
deprecated = "DEPRECATED_ENUM"
# 应该放在被标记为 `#[deprecated(note = "reason")]` 的任何枚举名称之前的可选字符串。`{}` 将被替换为双引号字符串。例如,如果针对 gcc/clang,"__attribute__((deprecated({})))" 可能是一个合理的值。一个更便携的解决方案可能涉及发出你在平台特定方式中定义的宏的名称。例如 "DEPRECATED_ENUM_WITH_NOTE(note)"
# 默认值:对于已弃用的枚举不发出任何内容
deprecated_with_notes = "DEPRECATED_ENUM_WITH_NOTE"
# 应该放在被标记为 `#[deprecated]` 且没有注释的任何枚举变体名称之后的可选字符串。例如,如果针对 gcc/clang,"__attribute__((deprecated))" 可能是一个合理的值。一个更便携的解决方案可能涉及发出你在平台特定方式中定义的宏的名称。例如 "DEPRECATED_ENUM_VARIANT"
# 默认值:对于已弃用的枚举变体不发出任何内容
deprecated_variant = "DEPRECATED_ENUM_VARIANT"
# 应该放在被标记为 `#[deprecated(note = "reason")]` 的任何枚举变体名称之后的可选字符串。`{}` 将被替换为双引号字符串。例如,如果针对 gcc/clang,"__attribute__((deprecated({})))" 可能是一个合理的值。一个更便携的解决方案可能涉及发出你在平台特定方式中定义的宏的名称。例如 "DEPRECATED_ENUM_WITH_NOTE(note)"
# 默认值:对于已弃用的枚举变体不发出任何内容
deprecated_variant_with_notes = "DEPRECATED_ENUM_VARIANT_WITH_NOTE({})"
# 是否应该为带有字段的枚举生成析构函数。这使得泛型枚举能够使用具有析构函数的 C++ 类型正确实例化。这对结构体来说是不必要的,因为 C++ 有规则来自动派生这些类型的正确构造函数和析构函数。
#
# 应该谨慎使用此选项,因为 Rust 和 C++ 无法正确地相互操作彼此的析构函数概念。此外,这可能会改变类型的 ABI。要么你的析构函数完整的枚举必须完全在 C++ 中使用,要么它们必须只在 C++ 和 Rust 之间按引用传递。
#
# 默认值:false
derive_tagged_enum_destructor = false
# 是否应该为带有字段的枚举生成复制构造函数。看看 derive_tagged_enum_destructor 对为什么这既有用又非常危险进行讨论。
#
# 默认值:false
derive_tagged_enum_copy_constructor = false
# 是否应该为带有字段的枚举生成复制赋值运算符。
#
# 这取决于是否也派生了复制构造函数,并且强烈建议将其设置为 true。
#
# 默认值:false
derive_tagged_enum_copy_assignment = false
# 是否应该为带有字段的枚举生成一个空的、私有的析构函数。
# 这允许自动生成的构造函数编译,如果有非平凡构造的成员。这与 `derive_tagged_enum_copy_constructor` 等处于同一危险家族。
#
# 默认值:false
private_default_tagged_enum_constructor = false
[const]
# 是否可以在 C++ 模式下生成的常量作为静态常量。我不知道你为什么想关闭这个。
#
# 默认值:true
allow_static_const = true
# 是否可以在 C++ 模式下生成的常量作为 constexpr。
#
# 默认值:true
allow_constexpr = false
# 指定将如何排序常量。
#
# "Name":按常量名称排序
# "None":保持常量已解析的顺序
#
# 默认值:"None"
sort_by = "Name"
[macro_expansion]
# 是否应该为 bitflags! 宏的实例生成绑定。
# 默认值:false
bitflags = true
# 如何解析你的 Rust 库的选项
[parse]
# 是否解析依赖的 crates 并将它们的类型包含在输出中
# 默认值:false
parse_deps = true
# 允许解析的 crate 名称白名单。如果定义了此列表,则只解析在此列表中找到的 crates。
#
# 默认值:没有白名单(注意:这与 [] 相反)
include = ["webrender", "webrender_traits"]
# 不允许解析的 crate 名称黑名单。
# 默认值:[]
exclude = ["libc"]
# 运行 `rustc -Zunpretty=expanded` 时是否应该使用一个新的临时目标目录。这可能对某些构建过程是必需的。
#
# 默认值:false
clean = false
# 我们应该为哪些除了顶层绑定 crate 之外的 crates 生成绑定。
#
# 默认值:[]
extra_bindings = ["my_awesome_dep"]
[parse.expand]
# 在解析之前应该通过 `cargo expand` 运行的 crate 名称列表,以展开任何宏。注意,如果在这里命名了一个 crate,无论黑白名单如何,它总是会被解析。
#
# 默认值:[]
crates = ["euclid"]
# 如果启用,使用 `--all-features` 选项进行扩展。当设置 `features` 时将被忽略。为了向后兼容,如果使用 `expand = ["euclid"]` 简写,这将被强制开启。
#
# 默认值:false
all_features = false
# 当 `all_features` 被禁用且此选项也被禁用时,使用 `--no-default-features` 选项进行扩展。
#
# 默认值:true
default_features = true
# 运行 `cargo expand` 时应使用的 feature 名称列表。这与 `default_features` 一起使用,就像在 `Cargo.toml` 中一样。注意,这里列出的特性是当前正在构建的 crate 的特性,*不是* 正在展开的 crates 的特性。crate 的 `Cargo.toml` 必须在其依赖项中启用适当的特性。
#
# 默认值:[]
features = ["cbindgen"]
[ptr]
# 用于装饰所有需要非空的指针的可选字符串。空性是从 Rust 类型推断出来的:`&T`、`&mut T` 和 `NonNull<T>` 都需要有效的指针值。
non_null_attribute = "_Nonnull"
# 特定于 Cython 绑定的选项。
[cython]
# 在顶层 `cdef extern from header:` 声明中指定的头文件。
#
# 默认值:*
header = '"my_header.h"'
# 在 C 中你会得到包含的地方添加 `from module cimport name1, name2` 声明
[cython.cimports]
module = ["name1", "name2"]
如果你遇到这样的情况,请提交一个问题: https://github.com/mozilla/cbindgen/issues/new
[2]在这里阅读完整的用户文档!: docs.md
[3]在这里获取模板 cbindgen.toml。: template.toml
[4]测试: tests/rust/
[5]milksnake: https://github.com/getsentry/milksnake
[6]webrender: https://searchfox.org/mozilla-central/source/gfx/webrender_bindings
[7]生成的头文件: https://searchfox.org/mozilla-central/source/GENERATED/gfx/webrender_bindings/webrender_ffi_generated.h
[8]stylo: https://searchfox.org/mozilla-central/source/layout/style
[9]生成的头文件: https://searchfox.org/mozilla-central/source/GENERATED/layout/style/ServoStyleConsts.h
[10]maturin: https://github.com/PyO3/maturin
[11]tquic: https://github.com/Tencent/tquic
[12]生成的头文件: https://github.com/Tencent/tquic/blob/develop/include/tquic.h
[13]Cython: https://cython.org
[14]在这里获取模板 cbindgen.toml: template.toml
[15]Builder
: https://docs.rs/cbindgen//cbindgen/struct.Builder.html#methods*
README 有一些有用的链接: README.md#examples