Skip to content

FFI外部函数接口

🎯 FFI概述

FFI(Foreign Function Interface)允许Rust代码与其他编程语言(主要是C/C++)编写的代码进行互操作。这是系统编程中的重要技能,让您能够利用现有的C库和系统API。

🔧 调用C函数

基础C函数调用

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

// 声明C标准库函数
extern "C" {
    fn strlen(s: *const c_char) -> usize;
    fn strcmp(s1: *const c_char, s2: *const c_char) -> c_int;
    fn malloc(size: usize) -> *mut std::ffi::c_void;
    fn free(ptr: *mut std::ffi::c_void);
}

fn basic_c_functions() -> Result<(), Box<dyn std::error::Error>> {
    // 使用C字符串
    let rust_string = "Hello, FFI!";
    let c_string = CString::new(rust_string)?;
    
    unsafe {
        let len = strlen(c_string.as_ptr());
        println!("String length (from C): {}", len);
        
        // 字符串比较
        let other_string = CString::new("Hello, FFI!")?;
        let cmp_result = strcmp(c_string.as_ptr(), other_string.as_ptr());
        println!("String comparison: {}", cmp_result);
    }
    
    Ok(())
}

使用libc库

rust
use libc::{getpid, time, c_long};

fn libc_functions() {
    unsafe {
        // 获取进程ID
        let pid = getpid();
        println!("Process ID: {}", pid);
        
        // 获取当前时间
        let current_time = time(std::ptr::null_mut());
        println!("Current time: {}", current_time);
        
        // 使用系统调用
        let result = libc::write(1, b"Hello from libc!\n".as_ptr() as *const _, 17);
        if result == -1 {
            println!("Write failed");
        }
    }
}

📚 创建C兼容的库

导出Rust函数给C使用

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

// 导出给C使用的函数
#[no_mangle]
pub extern "C" fn rust_add(a: c_int, b: c_int) -> c_int {
    a + b
}

#[no_mangle]
pub extern "C" fn rust_string_length(s: *const c_char) -> usize {
    if s.is_null() {
        return 0;
    }
    
    unsafe {
        CStr::from_ptr(s).to_bytes().len()
    }
}

#[no_mangle]
pub extern "C" fn rust_create_string() -> *mut c_char {
    let s = CString::new("Hello from Rust!").unwrap();
    s.into_raw()
}

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

// 复杂数据结构
#[repr(C)]
pub struct Point {
    pub x: f64,
    pub y: f64,
}

#[no_mangle]
pub extern "C" fn rust_distance(p1: *const Point, p2: *const Point) -> f64 {
    if p1.is_null() || p2.is_null() {
        return -1.0;
    }
    
    unsafe {
        let p1 = &*p1;
        let p2 = &*p2;
        let dx = p1.x - p2.x;
        let dy = p1.y - p2.y;
        (dx * dx + dy * dy).sqrt()
    }
}

对应的C头文件

c
// rust_lib.h
#ifndef RUST_LIB_H
#define RUST_LIB_H

#include <stddef.h>

typedef struct {
    double x;
    double y;
} Point;

int rust_add(int a, int b);
size_t rust_string_length(const char* s);
char* rust_create_string(void);
void rust_free_string(char* s);
double rust_distance(const Point* p1, const Point* p2);

#endif

🔗 绑定生成工具

使用bindgen自动生成绑定

toml
# Cargo.toml
[build-dependencies]
bindgen = "0.60"

[dependencies]
libc = "0.2"
rust
// build.rs
extern crate bindgen;

use std::env;
use std::path::PathBuf;

fn main() {
    // 告诉cargo重新运行如果头文件改变
    println!("cargo:rerun-if-changed=wrapper.h");
    
    // 生成绑定
    let bindings = bindgen::Builder::default()
        .header("wrapper.h")
        .parse_callbacks(Box::new(bindgen::CargoCallbacks))
        .generate()
        .expect("Unable to generate bindings");
    
    // 写入绑定文件
    let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
    bindings
        .write_to_file(out_path.join("bindings.rs"))
        .expect("Couldn't write bindings!");
}
c
// wrapper.h
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// 自定义结构体
typedef struct {
    int id;
    char name[64];
    double value;
} MyStruct;

// 函数声明
int process_data(MyStruct* data);
void cleanup_data(MyStruct* data);
rust
// 使用生成的绑定
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));

fn use_generated_bindings() {
    unsafe {
        let mut data = MyStruct {
            id: 1,
            name: [0; 64],
            value: 3.14,
        };
        
        // 设置名称
        let name = b"Test\0";
        std::ptr::copy_nonoverlapping(
            name.as_ptr(),
            data.name.as_mut_ptr() as *mut u8,
            name.len()
        );
        
        let result = process_data(&mut data);
        println!("Process result: {}", result);
        
        cleanup_data(&mut data);
    }
}

🛡️ 安全的FFI封装

创建安全的Rust包装器

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

// 假设的C库函数
extern "C" {
    fn c_create_context() -> *mut std::ffi::c_void;
    fn c_destroy_context(ctx: *mut std::ffi::c_void);
    fn c_process_string(ctx: *mut std::ffi::c_void, input: *const c_char) -> *mut c_char;
    fn c_free_string(s: *mut c_char);
}

// 安全的Rust包装器
pub struct CContext {
    ctx: *mut std::ffi::c_void,
}

impl CContext {
    pub fn new() -> Option<Self> {
        let ctx = unsafe { c_create_context() };
        if ctx.is_null() {
            None
        } else {
            Some(CContext { ctx })
        }
    }
    
    pub fn process_string(&self, input: &str) -> Result<String, Box<dyn std::error::Error>> {
        let c_input = CString::new(input)?;
        
        unsafe {
            let result_ptr = c_process_string(self.ctx, c_input.as_ptr());
            if result_ptr.is_null() {
                return Err("C function returned null".into());
            }
            
            let c_str = CStr::from_ptr(result_ptr);
            let rust_string = c_str.to_string_lossy().into_owned();
            
            c_free_string(result_ptr);
            
            Ok(rust_string)
        }
    }
}

impl Drop for CContext {
    fn drop(&mut self) {
        unsafe {
            c_destroy_context(self.ctx);
        }
    }
}

// 使用安全包装器
fn safe_ffi_usage() -> Result<(), Box<dyn std::error::Error>> {
    let context = CContext::new().ok_or("Failed to create context")?;
    let result = context.process_string("Hello, safe FFI!")?;
    println!("Processed: {}", result);
    Ok(())
}

🔄 回调函数

C回调函数处理

rust
use std::ffi::c_void;
use std::os::raw::{c_int, c_char};

// C函数类型定义
type CCallback = extern "C" fn(data: *const c_char, user_data: *mut c_void) -> c_int;

extern "C" {
    fn register_callback(callback: CCallback, user_data: *mut c_void);
    fn trigger_callback();
}

// Rust回调函数
extern "C" fn rust_callback(data: *const c_char, user_data: *mut c_void) -> c_int {
    unsafe {
        if !data.is_null() {
            let c_str = std::ffi::CStr::from_ptr(data);
            if let Ok(rust_str) = c_str.to_str() {
                println!("Callback received: {}", rust_str);
            }
        }
        
        if !user_data.is_null() {
            let counter = user_data as *mut i32;
            *counter += 1;
            println!("Callback count: {}", *counter);
        }
    }
    
    0 // 成功返回码
}

fn callback_example() {
    let mut counter = 0i32;
    
    unsafe {
        register_callback(rust_callback, &mut counter as *mut i32 as *mut c_void);
        trigger_callback();
    }
    
    println!("Final counter: {}", counter);
}

闭包作为回调

rust
use std::ffi::c_void;
use std::os::raw::c_int;

// 存储闭包的结构
struct CallbackData<F> {
    closure: F,
}

// 通用回调包装器
extern "C" fn callback_wrapper<F>(value: c_int, user_data: *mut c_void) -> c_int
where
    F: FnMut(i32) -> i32,
{
    unsafe {
        let callback_data = &mut *(user_data as *mut CallbackData<F>);
        (callback_data.closure)(value)
    }
}

// 注册闭包回调的安全接口
fn register_closure_callback<F>(mut closure: F) -> Box<CallbackData<F>>
where
    F: FnMut(i32) -> i32,
{
    let callback_data = Box::new(CallbackData { closure });
    
    unsafe {
        // 假设的C函数,注册回调
        // register_c_callback(callback_wrapper::<F>, callback_data.as_ref() as *const _ as *mut c_void);
    }
    
    callback_data
}

🧵 线程安全的FFI

跨线程FFI调用

rust
use std::sync::{Arc, Mutex};
use std::thread;
use std::ffi::CString;

// 线程安全的C库包装器
pub struct ThreadSafeCLib {
    // 使用Mutex保护C库状态
    inner: Arc<Mutex<*mut std::ffi::c_void>>,
}

unsafe impl Send for ThreadSafeCLib {}
unsafe impl Sync for ThreadSafeCLib {}

impl ThreadSafeCLib {
    pub fn new() -> Option<Self> {
        unsafe {
            let ctx = c_create_context();
            if ctx.is_null() {
                None
            } else {
                Some(ThreadSafeCLib {
                    inner: Arc::new(Mutex::new(ctx)),
                })
            }
        }
    }
    
    pub fn process_in_thread(&self, input: String) -> thread::JoinHandle<Option<String>> {
        let inner = Arc::clone(&self.inner);
        
        thread::spawn(move || {
            let ctx = inner.lock().ok()?;
            let c_input = CString::new(input).ok()?;
            
            unsafe {
                let result_ptr = c_process_string(*ctx, c_input.as_ptr());
                if result_ptr.is_null() {
                    return None;
                }
                
                let c_str = std::ffi::CStr::from_ptr(result_ptr);
                let rust_string = c_str.to_string_lossy().into_owned();
                c_free_string(result_ptr);
                
                Some(rust_string)
            }
        })
    }
}

impl Drop for ThreadSafeCLib {
    fn drop(&mut self) {
        if let Ok(ctx) = self.inner.lock() {
            unsafe {
                c_destroy_context(*ctx);
            }
        }
    }
}

🔍 调试FFI代码

FFI调试技巧

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

// 调试宏
macro_rules! ffi_debug {
    ($msg:expr) => {
        #[cfg(debug_assertions)]
        eprintln!("[FFI DEBUG] {}", $msg);
    };
    ($fmt:expr, $($arg:tt)*) => {
        #[cfg(debug_assertions)]
        eprintln!("[FFI DEBUG] {}", format!($fmt, $($arg)*));
    };
}

// 安全的字符串转换函数
fn safe_c_string_to_rust(ptr: *const c_char) -> Option<String> {
    if ptr.is_null() {
        ffi_debug!("Null pointer passed to safe_c_string_to_rust");
        return None;
    }
    
    unsafe {
        match CStr::from_ptr(ptr).to_str() {
            Ok(s) => {
                ffi_debug!("Successfully converted C string: '{}'", s);
                Some(s.to_owned())
            }
            Err(e) => {
                ffi_debug!("Failed to convert C string: {}", e);
                None
            }
        }
    }
}

// 内存泄漏检测
static mut ALLOCATION_COUNT: std::sync::atomic::AtomicUsize = 
    std::sync::atomic::AtomicUsize::new(0);

fn track_c_allocation(ptr: *mut c_void, size: usize) {
    if !ptr.is_null() {
        let count = unsafe { 
            ALLOCATION_COUNT.fetch_add(1, std::sync::atomic::Ordering::Relaxed) 
        };
        ffi_debug!("C allocation #{}: {:p} ({} bytes)", count + 1, ptr, size);
    }
}

fn track_c_deallocation(ptr: *mut c_void) {
    if !ptr.is_null() {
        let count = unsafe { 
            ALLOCATION_COUNT.fetch_sub(1, std::sync::atomic::Ordering::Relaxed) 
        };
        ffi_debug!("C deallocation: {:p} (remaining: {})", ptr, count - 1);
    }
}

📚 最佳实践

FFI安全检查清单

rust
// 1. 空指针检查
fn check_null_pointer<T>(ptr: *const T, name: &str) -> Result<(), &'static str> {
    if ptr.is_null() {
        eprintln!("Null pointer detected: {}", name);
        Err("Null pointer")
    } else {
        Ok(())
    }
}

// 2. 字符串长度验证
fn validate_c_string(ptr: *const c_char, max_len: usize) -> Result<(), &'static str> {
    if ptr.is_null() {
        return Err("Null string pointer");
    }
    
    unsafe {
        let mut len = 0;
        let mut current = ptr;
        
        while len < max_len {
            if *current == 0 {
                return Ok(());
            }
            current = current.add(1);
            len += 1;
        }
        
        Err("String too long or not null-terminated")
    }
}

// 3. 错误码处理
#[derive(Debug)]
enum CLibError {
    Success = 0,
    InvalidArgument = -1,
    OutOfMemory = -2,
    IOError = -3,
}

impl From<c_int> for CLibError {
    fn from(code: c_int) -> Self {
        match code {
            0 => CLibError::Success,
            -1 => CLibError::InvalidArgument,
            -2 => CLibError::OutOfMemory,
            -3 => CLibError::IOError,
            _ => CLibError::InvalidArgument,
        }
    }
}

fn handle_c_result(result: c_int) -> Result<(), CLibError> {
    let error = CLibError::from(result);
    match error {
        CLibError::Success => Ok(()),
        _ => Err(error),
    }
}

FFI是连接Rust与现有C/C++生态系统的桥梁,掌握它将大大扩展您的系统编程能力!🔗