在本章中,我们将探讨如何使用 Rust 来调用 C 语言编写的代码。这种跨语言的交互能力是许多系统级编程和性能敏感应用的重要组成部分。
使用 FFI (Foreign Function Interface)
FFI 允许我们在 Rust 中调用非 Rust 语言编写的函数。这是实现 Rust 和 C 语言之间交互的基础。
创建 C 代码
首先,我们需要创建一个简单的 C 函数。假设我们有一个 C 文件 example.c,其中包含以下代码:
// example.c
#include <stdio.h>
void say_hello(const char *name) {
printf("Hello, %s!\n", name);
}接下来,我们需要为这个 C 文件生成头文件,以便 Rust 能够引用它。使用以下命令生成头文件:
gcc -S example.c -o example.s gcc -E example.c > example.h
编译 C 代码
为了能够在 Rust 程序中调用这个 C 函数,我们需要将 C 代码编译成一个共享库。对于 Linux 和 macOS 用户,可以使用以下命令:
gcc -shared -o libexample.so example.c
对于 Windows 用户,可以使用以下命令:
gcc -shared -o example.dll example.c
在 Rust 中调用 C 函数
接下来,我们需要在 Rust 代码中声明并调用这个 C 函数。首先,在 Rust 项目中添加以下代码:
// Cargo.toml [dependencies] libc = "0.2"
然后,在 Rust 源代码文件中,我们可以使用 extern 关键字来声明 C 函数,并使用 #[link] 属性来指定链接到的库:
// www.javascriptcn.com code example
// src/main.rs
#[link(name = "example")]
extern "C" {
fn say_hello(name: *const libc::c_char);
}
use std::ffi::{CString, CStr};
use std::os::raw::c_char;
fn main() {
let rust_string = String::from("Rustacean");
let c_string = CString::new(rust_string).expect("CString conversion failed");
unsafe {
say_hello(c_string.as_ptr());
}
}解释代码
Cargo.toml:
- 添加了
libc库依赖,这将帮助我们处理 C 语言中的数据类型。
- 添加了
main.rs:
- 使用
#[link(name = "example")]指定链接到的库文件。 - 使用
extern "C"声明 C 函数,"C"表示使用 C 调用约定。 - 使用
CString::new将 Rust 字符串转换为 C 字符串,因为 C 函数期望的是 C 字符串指针。 - 使用
unsafe块来调用 C 函数,因为直接调用外部函数是不安全的操作。
- 使用
编译和运行
确保你的共享库文件在运行时可访问。对于 Linux 和 macOS 用户,你可以将共享库放在与可执行文件相同的目录中,或者将其路径添加到 LD_LIBRARY_PATH 或 DYLD_LIBRARY_PATH 环境变量中。
cargo run
如果一切正常,你应该会看到输出:
Hello, Rustacean!
使用 bindgen 自动生成绑定
手动管理 FFI 绑定可能会很繁琐且容易出错。幸运的是,我们可以使用 bindgen 工具来自动生成这些绑定。
安装 bindgen
首先,安装 bindgen:
cargo install bindgen
生成绑定
使用 bindgen 从 C 头文件生成 Rust 绑定:
bindgen example.h --output bindings.rs
使用自动生成的绑定
接下来,修改 main.rs 以使用自动生成的绑定:
// src/main.rs
extern crate example;
use example::say_hello;
fn main() {
say_hello("Rustacean");
}修改 Cargo.toml
添加对自动生成绑定文件的依赖:
# Cargo.toml
[build-dependencies]
bindgen = "0.59"
[dependencies]
example = { path = "bindings" }构建和运行
确保在构建之前运行绑定生成脚本。你可以在 build.rs 中添加以下代码来自动化这个过程:
// www.javascriptcn.com code example
// build.rs
extern crate bindgen;
fn main() {
bindgen::Builder::default()
.header("example.h")
.generate()
.expect("Unable to generate bindings")
.write_to_file("src/bindings.rs")
.expect("Couldn't write bindings!");
}现在,当你运行 cargo build 时,build.rs 脚本会自动运行,生成绑定文件,并将其写入 src/bindings.rs。
总结
通过以上步骤,我们展示了如何在 Rust 中调用 C 语言编写的代码。使用 FFI 可以极大地扩展 Rust 的功能,使其能够与现有的 C 库无缝集成。借助工具如 bindgen,我们可以更方便地管理这些复杂的绑定,从而减少手动工作量和潜在错误。