在你的 C 代码中加入一点 Rust
在 C 或 C++ 项目中使用 Rust 代码主要包括两个部分。
- 在 Rust 中创建 C 友好的 API
- 将你的 Rust 项目嵌入到外部构建系统中
除了 cargo
和 meson
之外,大多数构建系统都没有原生 Rust 支持。因此,你最好使用 cargo
来编译你的 crate 和任何依赖项。
设置项目
像往常一样创建一个新的 cargo
项目。
有一些标志可以告诉 cargo
生成系统库,而不是其常规的 Rust 目标。这还允许你为你的库设置不同的输出名称,如果你希望它与你的 crate 的其余部分不同。
[lib]
name = "your_crate"
crate-type = ["cdylib"] # Creates dynamic lib
# crate-type = ["staticlib"] # Creates static lib
构建 C
API
由于 C++ 没有稳定的 ABI 供 Rust 编译器作为目标,因此我们使用 C
来实现不同语言之间的任何互操作性。在 C 和 C++ 代码中使用 Rust 时,情况也是如此。
#[no_mangle]
Rust 编译器对符号名称的处理方式与原生代码链接器不同。因此,任何 Rust 导出的供在 Rust 之外使用的函数都需要告诉编译器不要对其进行混淆。
extern "C"
默认情况下,你在 Rust 中编写的任何函数都将使用 Rust ABI(它也没有稳定)。相反,在构建面向外部的 FFI API 时,我们需要告诉编译器使用系统 ABI。
根据你的平台,你可能希望针对特定的 ABI 版本,这些版本在 这里 有记录。
将这些部分组合在一起,你将得到一个看起来大致像这样的函数。
#[no_mangle]
pub extern "C" fn rust_function() {
}
就像在你的 Rust 项目中使用 C
代码一样,你现在需要将数据从一种形式转换为另一种形式,以便应用程序的其余部分能够理解。
链接和更大的项目上下文。
那么,问题的一半就解决了。你现在如何使用它呢?
这在很大程度上取决于你的项目和/或构建系统
cargo
将创建一个 my_lib.so
/my_lib.dll
或 my_lib.a
文件,具体取决于你的平台和设置。这个库可以简单地由你的构建系统链接。
但是,从 C 调用 Rust 函数需要一个头文件来声明函数签名。
你的 Rust-ffi API 中的每个函数都需要有一个对应的头函数。
#[no_mangle]
pub extern "C" fn rust_function() {}
将变成
void rust_function();
等等。
有一个工具可以自动化这个过程,称为 cbindgen,它分析你的 Rust 代码,然后从它生成 C 和 C++ 项目的头文件。
此时,从 C 使用 Rust 函数就像包含头文件并调用它们一样简单!
#include "my-rust-project.h"
rust_function();