11.4 从 C 调用 Rust 函数

除了调用 C 函数,Rust 还支持将自身函数导出为 C 兼容的接口,供外部 C 程序调用。这种能力使得 Rust 可作为高性能、内存安全的模块嵌入到现有 C/C++ 项目中,例如实现关键算法、解析器或加密逻辑。

要让 Rust 函数能被 C 调用,需满足两个条件:使用 C 的 ABI防止符号名混淆(name mangling)

使用 extern "C" 导出函数

通过在函数前添加 extern "C",Rust 会按照 C 的调用约定生成函数:

#[no_mangle]
pub extern "C" fn add(a: i32, b: i32) -> i32 {
    a + b
}
  • extern "C":指定函数使用 C ABI,确保参数传递和返回方式与 C 一致;
  • #[no_mangle]:禁用 Rust 默认的符号名混淆(mangling),使函数名在编译后保持为 add,便于 C 代码链接。

静态库或动态库构建

Rust 代码需编译为库(而非可执行文件),供 C 链接。在 Cargo.toml 中配置:

[lib]
crate-type = ["staticlib", "cdylib"]
  • staticlib → 生成 .a(Unix)或 .lib(Windows)静态库;
  • cdylib → 生成 .so(Linux)、.dylib(macOS)或 .dll(Windows)动态库。

运行 cargo build 后,可在 target/debug/target/release/ 目录找到生成的库文件。

C 端调用示例

在 C 代码中声明并调用该函数:

// main.c
#include <stdio.h>

// 声明 Rust 函数
int add(int a, int b);

int main() {
    int result = add(3, 4);
    printf("3 + 4 = %d\n", result); // 输出: 3 + 4 = 7
    return 0;
}

编译并链接(以 Linux 为例):

# 编译 Rust 库
cargo build --release

# 编译 C 程序并链接 Rust 静态库
gcc main.c -L./target/release -l<crate_name> -lpthread -ldl -lm -o main
./main

注意:Rust 标准库依赖一些系统库(如 pthreaddlm),链接时需一并包含。

处理复杂类型

C 无法直接理解 Rust 的高级类型(如 StringVec),因此导出函数应使用 C 兼容类型:

  • 整数:i32u64 等(建议使用 std::os::raw 中的 c_intc_char 等);
  • 指针:*const u8*mut c_void
  • 结构体:需用 #[repr(C)] 控制布局(见 11.5 节)。

例如,导出一个处理字符串的函数:

use std::ffi::{CStr, CString};
use std::os::raw::c_char;

#[no_mangle]
pub extern "C" fn greet(name: *const c_char) -> *mut c_char {
    if name.is_null() {
        return std::ptr::null_mut();
    }

    let c_str = unsafe { CStr::from_ptr(name) };
    let name_str = match c_str.to_str() {
        Ok(s) => s,
        Err(_) => return std::ptr::null_mut(),
    };

    let output = format!("Hello, {}!", name_str);
    match CString::new(output) {
        Ok(c_string) => c_string.into_raw(),
        Err(_) => std::ptr::null_mut(),
    }
}

此函数接收 C 字符串,返回新分配的 C 字符串。注意:调用者需负责释放返回的内存(通常通过另一个导出的 free 函数):

#[no_mangle]
pub extern " C" fn free_greeting(s: *mut c_char) {
    if !s.is_null() {
        unsafe {
            let _ = CString::from_raw(s);
        }
    }
}

错误处理与 panic 安全

Rust 的 panic 绝不能跨越 FFI 边界。若 Rust 函数在被 C 调用时 panic,程序将触发未定义行为(通常 abort)。因此:

  • 在导出函数中避免可能 panic 的操作;
  • 使用 Result 并转换为错误码或输出参数;
  • 或用 catch_unwind 捕获 panic(但仅适用于 unwindable panic,不适用于 panic=abort)。

小结

将 Rust 函数暴露给 C,是构建混合系统的重要手段。关键在于使用 extern "C"#[no_mangle] 确保 ABI 兼容与符号可见性,并通过 C 兼容类型传递数据。同时,必须谨慎管理内存生命周期和错误处理,防止 panic 泄露。通过这种方式,Rust 可作为“安全内核”无缝集成到传统 C 生态中,提升整体系统的可靠性。

#Rust 入门教程 分享于 1 周前

内容由 AI 创作和分享,仅供参考