🦀 第20课:FFI外部函数接口

实用工具 第20/25课

FFI(Foreign Function Interface)让Rust能够调用C语言库,也让C能调用Rust。这是Rust与现有生态系统集成的关键桥梁。

🔗 调用C函数

// 声明外部函数
extern "C" {
    fn abs(input: i32) -> i32;
    fn strlen(s: *const i8) -> usize;
}

fn main() {
    // 调用C标准库函数
    let x = -42;
    unsafe {
        println!("|{}| = {}", x, abs(x));
    }
    
    // C字符串处理
    let c_str = b"Hello, FFI!\0";
    unsafe {
        println!("长度: {}", strlen(c_str.as_ptr() as *const i8));
    }
    
    // 从C字符串创建Rust字符串
    use std::ffi::{CStr, CString};
    let c_string = CString::new("Hello from C").unwrap();
    unsafe {
        let rust_str = CStr::from_ptr(c_string.as_ptr());
        println!("Rust读取: {}", rust_str.to_str().unwrap());
    }
    
    // Rust字符串转C字符串
    let rust_string = String::from("Hello from Rust");
    let c_string = CString::new(rust_string).unwrap();
    unsafe {
        println!("C可读: {}", CStr::from_ptr(c_string.as_ptr()).to_str().unwrap());
    }
}
|-42| = 42 长度: 11 Rust读取: Hello from C C可读: Hello from Rust

✅ 验证通过

📤 从Rust导出C接口

// 让C调用的Rust函数
#[no_mangle]
pub extern "C" fn rust_add(a: i32, b: i32) -> i32 {
    a + b
}

#[no_mangle]
pub extern "C" fn rust_greet(name: *const i8) -> i32 {
    use std::ffi::CStr;
    unsafe {
        if name.is_null() { return -1; }
        let c_str = CStr::from_ptr(name);
        match c_str.to_str() {
            Ok(s) => { println!("你好, {}!", s); 0 }
            Err(_) => -2,
        }
    }
}

fn main() {
    // 模拟C调用Rust
    let sum = rust_add(3, 5);
    println!("3 + 5 = {}", sum);
    
    use std::ffi::CString;
    let name = CString::new("World").unwrap();
    let result = rust_greet(name.as_ptr());
    println!("返回码: {}", result);
}
3 + 5 = 8 你好, World! 返回码: 0

✅ 验证通过

🏗️ 综合实战:跨语言数学库

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

// Rust实现的数学函数,导出给C
#[no_mangle]
pub extern "C" fn math_factorial(n: u32) -> u64 {
    (1..=n as u64).product()
}

#[no_mangle]
pub extern "C" fn math_fibonacci(n: u32) -> u64 {
    let mut a = 0u64;
    let mut b = 1u64;
    for _ in 0..n {
        let temp = a + b;
        a = b;
        b = temp;
    }
    a
}

#[no_mangle]
pub extern "C" fn math_is_prime(n: u64) -> bool {
    if n < 2 { return false; }
    if n < 4 { return true; }
    if n % 2 == 0 || n % 3 == 0 { return false; }
    let mut i = 5u64;
    while i * i <= n {
        if n % i == 0 || n % (i + 2) == 0 { return false; }
        i += 6;
    }
    true
}

// 接收C字符串,返回C字符串
#[no_mangle]
pub extern "C" fn math_describe(n: u32) -> *mut c_char {
    let desc = format!(
        "{}: 阶乘={}, 斐波那契={}, 素数={}",
        n, math_factorial(n), math_fibonacci(n), math_is_prime(n as u64)
    );
    CString::new(desc).unwrap().into_raw()
}

fn main() {
    for n in [5, 7, 10] {
        let desc = unsafe { CStr::from_ptr(math_describe(n)) };
        println!("{}", desc.to_str().unwrap());
    }
}
5: 阶乘=120, 斐波那契=5, 素数=true 7: 阶乘=5040, 斐波那契=13, 素数=true 10: 阶乘=3628800, 斐波那契=55, 素数=false

✅ 验证通过

📝 练习

练习1:调用libc

添加libc依赖,调用getpid()和time()函数。

练习2:回调函数

实现C的qsort回调:Rust实现比较函数,通过FFI传给C的qsort。

🏆 本课成就

🔧 FFI最佳实践

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

mod safe_ffi {
    use std::ffi::{CStr, CString};
    use std::os::raw::c_char;
    pub fn receive_c_string(ptr: *const c_char) -> Option {
        if ptr.is_null() { return None; }
        unsafe { CStr::from_ptr(ptr).to_str().ok().map(|s| s.to_string()) }
    }
    pub fn return_c_string(s: String) -> *mut c_char {
        CString::new(s).unwrap().into_raw()
    }
    pub fn free_c_string(ptr: *mut c_char) {
        if !ptr.is_null() { unsafe { let _ = CString::from_raw(ptr); } }
    }
}

type Callback = extern "C" fn(i32) -> i32;
fn apply_callback(value: i32, cb: Callback) -> i32 { cb(value) }
extern "C" fn double(x: i32) -> i32 { x * 2 }

fn main() {
    let c_str = CString::new("Hello FFI").unwrap();
    let rust_str = safe_ffi::receive_c_string(c_str.as_ptr());
    println!("收到: {:?}", rust_str);
    println!("回调: double(5) = {}", apply_callback(5, double));
}
收到: Some("Hello FFI") 回调: double(5) = 10

✅ 验证通过

📊 FFI使用决策

// 何时使用FFI:
// 1. 使用已有的C/C++库(OpenSSL, SQLite, zlib等)
// 2. 性能关键路径需要底层优化
// 3. 与嵌入式系统交互
// 4. 系统调用封装
//
// Rust→C→Rust 数据流:
// Rust String → CString → *const c_char → C函数 → *mut c_char → CStr → &str
//
// 重要注意事项:
// 1. C没有所有权概念,需手动管理内存
// 2. C没有空指针检查,Rust侧必须处理
// 3. C字符串以\0结尾,Rust字符串不一定
// 4. C的int大小不确定,用c_int/c_uint

fn main() {
    // 常见FFI crate
    println!("常用FFI crate:");
    println!("  libc     - C标准库绑定");
    println!("  bindgen  - 自动生成C头文件绑定");
    println!("  cbindgen - 从Rust生成C头文件");
    println!("  cxx      - 安全的C++互操作");
    println!("  pyo3     - Python绑定");
    println!("  neon     - Node.js绑定");
    println!("  uniFFI   - 跨语言绑定生成器");
    
    println!("
使用bindgen流程:");
    println!("  1. 编写wrapper.h包含C头文件");
    println!("  2. bindgen wrapper.h -o bindings.rs");
    println!("  3. 在Rust代码中使用生成的绑定");
    
    println!("
使用cbindgen导出:");
    println!("  1. 编写Rust库,标注#[no_mangle]");
    println!("  2. cbindgen --crate mylib -o mylib.h");
    println!("  3. C代码include头文件调用");
}

🏗️ 综合实战:C互操作数学库

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

#[no_mangle]
pub extern "C" fn math_gcd(a: i64, b: i64) -> i64 {
    let mut a = a.abs();
    let mut b = b.abs();
    while b != 0 { let t = b; b = a % b; a = t; }
    a
}

#[no_mangle]
pub extern "C" fn math_lcm(a: i64, b: i64) -> i64 {
    if a == 0 || b == 0 { return 0; }
    (a.abs() / math_gcd(a, b)) * b.abs()
}

#[no_mangle]
pub extern "C" fn math_extended_gcd(a: i64, b: i64) -> MathResult {
    let (old_r, r) = (a, b);
    let (mut old_s, mut s) = (1i64, 0i64);
    let (mut old_t, mut t) = (0i64, 1i64);
    let (mut old_r, mut r) = (old_r, r);
    while r != 0 {
        let q = old_r / r;
        (old_r, r) = (r, old_r - q * r);
        (old_s, s) = (s, old_s - q * s);
        (old_t, t) = (t, old_t - q * t);
    }
    MathResult { gcd: old_r, x: old_s, y: old_t }
}

#[repr(C)]
pub struct MathResult { gcd: i64, x: i64, y: i64 }

fn main() {
    println!("GCD(48, 18) = {}", math_gcd(48, 18));
    println!("LCM(12, 18) = {}", math_lcm(12, 18));
    let r = math_extended_gcd(35, 15);
    println!("ext_gcd(35,15): gcd={}, x={}, y={}", r.gcd, r.x, r.y);
    println!("验证: 35*{} + 15*{} = {}", r.x, r.y, 35*r.x + 15*r.y);
}
GCD(48, 18) = 6 LCM(12, 18) = 36 ext_gcd(35,15): gcd=5, x=1, y=-2 验证: 35*1 + 15*-2 = 5

✅ 验证通过

📋 FFI最佳实践清单

安全FFI原则

🔧 常见FFI模式

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

// 模式1: Rust调用C
// extern "C" { fn c_function(arg: i32) -> i32; }

// 模式2: C调用Rust
// #[no_mangle] pub extern "C" fn rust_function() -> i32 { 42 }

// 模式3: 回调函数
// type Callback = extern "C" fn(i32) -> i32;
// extern "C" { fn register_callback(cb: Callback); }

// 模式4: 结构体传递
#[repr(C)]
struct PointC { x: f64, y: f64 }

fn main() {
    let p = PointC { x: 3.0, y: 4.0 };
    println!("C兼容点: ({}, {})", p.x, p.y);
    
    // 字符串转换备忘
    let rust_s = "Hello";
    let c_s = CString::new(rust_s).unwrap();
    let back_to_rust = unsafe { CStr::from_ptr(c_s.as_ptr()).to_str().unwrap() };
    println!("往返: {} → C → {}", rust_s, back_to_rust);
    
    println!("
常用FFI工具:");
    println!("  bindgen - 自动生成C绑定");
    println!("  cbindgen - 生成C头文件");
    println!("  cxx - 安全C++互操作");
    println!("  cc - 构建C代码");
}

🏗️ FFI实战:Rust-Python桥接

// PyO3概念演示(不依赖pyo3)
use std::ffi::{CStr, CString};
use std::os::raw::c_char;

// 模拟Python调用Rust的接口
#[no_mangle]
pub extern "C" fn py_calculate_fibonacci(n: u32) -> u64 {
    let mut a = 0u64; let mut b = 1u64;
    for _ in 0..n { let t = a + b; a = b; b = t; }
    a
}

#[no_mangle]
pub extern "C" fn py_is_prime(n: u64) -> bool {
    if n < 2 { return false; }
    if n < 4 { return true; }
    if n % 2 == 0 || n % 3 == 0 { return false; }
    let mut i = 5u64;
    while i * i <= n { if n % i == 0 || n % (i+2) == 0 { return false; } i += 6; }
    true
}

#[no_mangle]
pub extern "C" fn py_format_result(n: u64, result: u64) -> *mut c_char {
    CString::new(format!("fib({})={}", n, result)).unwrap().into_raw()
}

fn main() {
    // 模拟Python调用
    let n = 30u32;
    let result = py_calculate_fibonacci(n);
    println!("fib({}) = {}", n, result);
    
    // 素数检查
    println!("17是素数: {}", py_is_prime(17));
    println!("100是素数: {}", py_is_prime(100));
    
    // 格式化结果
    let c_str = py_format_result(30, result);
    unsafe {
        let s = CStr::from_ptr(c_str);
        println!("格式化: {}", s.to_str().unwrap());
        let _ = CString::from_raw(c_str); // 释放
    }
    
    println!("
PyO3使用流程:");
    println!("  1. pip install maturin");
    println!("  2. maturin init");
    println!("  3. 编写Rust代码(#[pyfunction])");
    println!("  4. maturin develop");
    println!("  5. Python: import mylib");
}
fib(30) = 832040 17是素数: true 100是素数: false 格式化: fib(30)=832040

✅ 验证通过

💡 本课要点回顾

Rust的设计哲学贯穿始终:安全、并发、高性能。每个特性都是这三个目标的具体体现。本课所学内容是构建真实Rust应用的基石,务必在实践中反复练习巩固。

记住:Rust的学习曲线虽陡,但一旦掌握,你将获得前所未有的编程信心——编译器就是你的最佳队友。

🔧 FFI速查表

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

// 类型对应表:
// Rust        C
// i32         int        (c_int)
// i64         long long  (c_longlong)
// f64         double     (c_double)
// *mut T      T*
// *const T    const T*
// bool        _Bool
// ()          void

// 字符串转换流程:
// Rust String → CString → *const c_char → C函数
// C函数 → *const c_char → CStr → &str → String

// 内存所有权规则:
// 1. Rust分配的内存,Rust释放
// 2. C分配的内存,C释放
// 3. 跨边界传递时明确所有权

fn main() {
    // C类型大小
    println!("C类型大小:");
    println!("  c_int: {}字节", std::mem::size_of::());
    println!("  c_double: {}字节", std::mem::size_of::());
    println!("  *const c_char: {}字节", std::mem::size_of::<*const c_char>());
    
    // 字符串转换示例
    let rust_str = "Hello from Rust";
    let c_string = CString::new(rust_str).unwrap();
    let c_ptr = c_string.as_ptr();
    unsafe {
        let back = CStr::from_ptr(c_ptr);
        println!("往返: {}", back.to_str().unwrap());
    }
    
    // 常用FFI crate
    println!("
推荐FFI工具:");
    println!("  bindgen: 自动生成C绑定");
    println!("  cbindgen: 生成C头文件");
    println!("  cxx: 安全C++绑定");
    println!("  pyo3: Python绑定");
    println!("  neon: Node.js绑定");
    println!("  jni: Java绑定");
}