3个版本
使用旧的Rust 2015
0.4.2 | 2017年11月8日 |
---|---|
0.4.1 | 2017年4月11日 |
0.4.0 | 2017年4月4日 |
#674 in 构建工具
69KB
1K SLoC
这是一个 rusty-cheddar 的分支,直到 rusty-binder 版本可用。
moz-cheddar
moz-cheddar 是一个将Rust源文件转换为C头文件的库。
关于版本号的说明:尽管 moz-cheddar 仍然是预 v1.0.0
版本,但它可能会经历许多破坏性变化。我们试图遵循 semver,并在添加新功能或更改输出行为时提高次要版本号。
moz-cheddar 旨在支持 C99 或更高版本(用于单行注释,以及使用 stdint.h
和 stdbool.h
)。
使用 moz-cheddar 的最有效方式是在构建脚本中。为此,请将以下 build-dependencies
部分添加到您的 Cargo.toml
(如果将其用作普通库,只需将 build-dependencies
替换为 dependencies
即可)
# Cargo.toml
[build-dependencies]
moz-cheddar = "0.4.0"
然后创建以下 build.rs
// build.rs
extern crate cheddar;
fn main() {
cheddar::Cheddar::new().expect("could not read manifest")
.run_build("include/my_header.h");
}
如果您的项目设置正确,则应按原样工作。 不要忘记将 build = ...
添加到您的 [package]
部分,有关更多信息,请参阅 cargo 文档。
moz-cheddar 将在 include/
中创建一个 my_header.h
文件。请注意,moz-cheddar 产生的警告非常少,编写可以正确从 C 调用的库是程序员的责任。
模块中的API
您还可以将您的API放入模块中,以帮助保持源代码整洁。为此,您必须向Cheddar提供模块名称,然后确保项在顶级作用域中可用。
// build.rs
extern crate cheddar;
fn main() {
cheddar::Cheddar::new().expect("could not read manifest")
.module("c_api").expect("malformed module path")
.run_build("target/include/rusty.h");
}
// src/lib.rs
pub use c_api::*;
mod c_api {
// api goes here ...
}
此外,还有用于更精细控制的.compile()
和.compile_code()
方法。
转换
以下示例中省略了头部的模板代码。
类型别名
moz-cheddar将pub type A = B
转换为typedef B A;
。包含泛型的类型将被忽略。
Rust
type UInt32 = u32;
pub type UInt64 = u64;
pub type MyOption<T> = Option<T>
头文件
// Some boilerplate omitted.
typedef uint64_t UInt64;
// Some more boilerplate omitted.
枚举
moz-cheddar将标记为#[repr(C)]
的公共枚举转换为C语言。如果枚举是泛型或包含元组或结构体变体,则cheddar将失败。moz-cheddar应该能够正确处理显式的区分符。
Rust
#[repr(C)]
pub enum Colours {
Red = -6,
Blue,
Green = 7,
Yellow,
}
// This would fail if it was #[repr(C)].
pub enum Tastes<T> {
Savoury(T),
Sweet,
}
// This would fail if it was public.
#[repr(C)]
enum Units {
Kg(f64),
M(f64),
S(f64),
A(f64),
K(f64),
Mol(f64),
Cd(f64),
}
头文件
// Some boilerplate omitted.
typedef enum Colours {
Colours_Red = -6,
Colours_Blue,
Colours_Green = 7,
Colours_Yellow,
} Colours;
// Some more boilerplate omitted.
结构体
结构体的处理方式与枚举非常相似,它们必须是公共的,标记为#[repr(C)]
,并且不能包含泛型。目前这仅在结构体级别进行检查。泛型字段不会被检查。
Rust
#[repr(C)]
pub struct Person {
age: i32,
height: f64,
weight: f64,
}
头文件
// Some boilerplate omitted.
typedef struct Person {
int32_t age;
double height;
double weight;
} Person;
// Some more boilerplate omitted.
不透明结构体
一种常见的C语言习惯是使用不透明结构体来隐藏结构体的实现,它只能通过指针使用。这在Rust-C接口中特别有用,因为它允许您在C中使用任何任意的Rust结构体。
要定义一个不透明结构体,您必须定义一个标记为#[repr(C)]
的公共新类型。
Rust
struct Foo<T> {
bar: i32,
baz: Option<T>,
}
#[repr(C)]
pub struct MyCrate_Foo(Foo<PathBuf>);
头文件
// Some boilerplate omitted.
typedef struct MyCrate_Foo MyCrate_Foo;
// Some boilerplate omitted.
请注意,新类型必须不是泛型的,但包装的类型可以是任意的。
函数
为了让moz-cheddar能够识别函数声明,它必须是公共的,标记为#[no_mangle]
,并具有以下之一ABIs
- C
- Cdecl
- Stdcall
- Fastcall
- System
如果您认为其中之一被错误地包含,或者如果遗漏了一个,请在仓库中提交一个issue。
moz-cheddar会在标记为发散的函数上失败(-> !
)。
Rust
use std::ops::Add;
#[no_mangle]
pub extern fn hello() {
println!("Hello!");
}
fn add<O, R, L: Add<R, Output=O>>(l: L, r: R) -> O {
l + r
}
#[no_mangle]
#[allow(non_snake_case)]
pub extern fn MyAdd_add_u8(l: u8, r: u8) -> u8 {
add(l, r)
}
#[no_mangle]
#[allow(non_snake_case)]
pub extern fn MyAdd_add_u16(l: u16, r: u16) -> u16 {
add(l, r)
}
头文件
// Some boilerplate omitted.
void hello();
uint8_t MyAdd_add_u8(uint8_t l, uint8_t r);
uint16_t MyAdd_add_u16(uint16_t l, uint16_t r);
// Some more boilerplate omitted.
路径
您不应该在不将其隐藏在不透明结构体之后的情况下,在其他模块中定义的类型放在导出的类型签名中。这是因为C编译器必须知道类型的布局,而moz-cheddar目前还不能搜索其他模块。
本规则的非常重要的例外是定义在 libc
crate 和 std::os::raw
中的 C ABI 类型。这两个模块中的类型 必须 完全限定(例如 libc::c_void
或 std::os::raw::c_longlong
),以便正确转换。使用 use
语句导入它们将不起作用。
贡献
欢迎对 moz-cheddar 的贡献。
错误
如果你发现错误或有功能请求,请创建一个问题。
如果你在源代码中发现任何不清楚的地方,我认为那是一个错误。我试图使我的源代码尽可能清晰,因此任何这方面的帮助都将受到赞赏。
当然也欢迎拉取请求。
测试
测试需要你安装一个版本的 CppHeaderParser(> v2.7.2
),用于与作为 python
安装的 Python 版本(通常是 Python 2)匹配。此外,由于测试是一个庞大的垃圾代码堆,你必须处于 moz-cheddar 的 Cargo.toml
相同目录中才能成功运行它们。
如果你没有这个,尝试
pip install CppHeaderParser
依赖关系
~0.9–1.7MB
~27K SLoC