Rust编程-核心篇-FFI_rust编程第一课

FFI的重要性

在现代软件开发中,很少有项目是完全独立的。我们经常需要:

  • 调用现有的C/C++库
  • 与系统API交互
  • 集成第三方服务
  • 在现有代码库中嵌入Rust代码

Rust的FFI(Foreign Function Interface)系统提供了强大的跨语言互操作能力,让我们能够安全地与其他语言交互,同时保持Rust的内存安全和性能优势。

FFI基础

基本语法

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

// 声明外部C函数
extern "C" {
    fn printf(format: *const c_char, ...) -> c_int;
    fn strlen(s: *const c_char) -> usize;
}

fn main() {
    let s = CString::new("Hello, World!").unwrap();
    unsafe {
        printf(s.as_ptr());
        printf(CString::new("\n").unwrap().as_ptr());
        
        let len = strlen(s.as_ptr());
        println!("String length: {}", len);
    }
}

类型映射

use std::os::raw::{c_char, c_int, c_long, c_float, c_double};

// C类型到Rust类型的映射
extern "C" {
    // int -> c_int
    fn abs(n: c_int) -> c_int;
    
    // long -> c_long
    fn labs(n: c_long) -> c_long;
    
    // float -> c_float
    fn fabsf(n: c_float) -> c_float;
    
    // double -> c_double
    fn fabs(n: c_double) -> c_double;
    
    // char* -> *const c_char
    fn puts(s: *const c_char) -> c_int;
}

fn main() {
    unsafe {
        println!("abs(-5) = {}", abs(-5));
        println!("labs(-123456789) = {}", labs(-123456789));
        println!("fabsf(-3.14) = {}", fabsf(-3.14));
        println!("fabs(-2.718) = {}", fabs(-2.718));
        
        let s = CString::new("Hello from C!").unwrap();
        puts(s.as_ptr());
    }
}

实际应用

Windows API示例

use std::ffi::CString;
use std::os::raw::c_char;
use std::ptr;

#[cfg(target_os = "windows")]
mod windows {
    use super::*;
    
    extern "system" {
        fn GetCurrentDirectoryA(
            nBufferLength: u32,
            lpBuffer: *mut c_char,
        ) -> u32;
        
        fn SetCurrentDirectoryA(lpPathName: *const c_char) -> i32;
    }
    
    pub fn get_current_directory() -> Result<String, String> {
        let mut buffer = vec![0u8; 260];
        
        unsafe {
            let result = GetCurrentDirectoryA(
                buffer.len() as u32,
                buffer.as_mut_ptr() as *mut c_char,
            );
            
            if result == 0 {
                Err("Failed to get current directory".to_string())
            } else {
                let c_str = CStr::from_ptr(buffer.as_ptr() as *const c_char);
                Ok(c_str.to_string_lossy().to_string())
            }
        }
    }
    
    pub fn set_current_directory(path: &str) -> Result<(), String> {
        let c_path = CString::new(path).map_err(|_| "Invalid path")?;
        
        unsafe {
            let result = SetCurrentDirectoryA(c_path.as_ptr());
            if result == 0 {
                Err("Failed to set current directory".to_string())
            } else {
                Ok(())
            }
        }
    }
}

#[cfg(target_os = "windows")]
fn main() {
    match windows::get_current_directory() {
        Ok(dir) => println!("Current directory: {}", dir),
        Err(e) => println!("Error: {}", e),
    }
    
    if let Err(e) = windows::set_current_directory("C:\\") {
        println!("Error setting directory: {}", e);
    }
}

#[cfg(not(target_os = "windows"))]
fn main() {
    println!("This example only works on Windows");
}

Unix系统调用示例

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

#[cfg(unix)]
mod unix {
    use super::*;
    
    extern "C" {
        fn getcwd(buf: *mut c_char, size: usize) -> *mut c_char;
        fn chdir(path: *const c_char) -> c_int;
    }
    
    pub fn get_current_directory() -> Result<String, String> {
        let mut buffer = vec![0u8; 1024];
        
        unsafe {
            let result = getcwd(buffer.as_mut_ptr() as *mut c_char, buffer.len());
            if result.is_null() {
                Err("Failed to get current directory".to_string())
            } else {
                let c_str = CStr::from_ptr(result);
                Ok(c_str.to_string_lossy().to_string())
            }
        }
    }
    
    pub fn set_current_directory(path: &str) -> Result<(), String> {
        let c_path = CString::new(path).map_err(|_| "Invalid path")?;
        
        unsafe {
            let result = chdir(c_path.as_ptr());
            if result != 0 {
                Err("Failed to set current directory".to_string())
            } else {
                Ok(())
            }
        }
    }
}

#[cfg(unix)]
fn main() {
    match unix::get_current_directory() {
        Ok(dir) => println!("Current directory: {}", dir),
        Err(e) => println!("Error: {}", e),
    }
    
    if let Err(e) = unix::set_current_directory("/tmp") {
        println!("Error setting directory: {}", e);
    }
}

#[cfg(not(unix))]
fn main() {
    println!("This example only works on Unix-like systems");
}

调用C库

use std::ffi::CString;
use std::os::raw::c_double;

// 链接数学库
#[link(name = "m")]
extern "C" {
    fn sin(x: c_double) -> c_double;
    fn cos(x: c_double) -> c_double;
    fn sqrt(x: c_double) -> c_double;
    fn pow(x: c_double, y: c_double) -> c_double;
}

// 安全的Rust包装
pub struct MathLib;

impl MathLib {
    pub fn sin(x: f64) -> f64 {
        unsafe { sin(x) }
    }
    
    pub fn cos(x: f64) -> f64 {
        unsafe { cos(x) }
    }
    
    pub fn sqrt(x: f64) -> f64 {
        unsafe { sqrt(x) }
    }
    
    pub fn pow(x: f64, y: f64) -> f64 {
        unsafe { pow(x, y) }
    }
}

fn main() {
    let angle = std::f64::consts::PI / 4.0;
    
    println!("sin(π/4) = {}", MathLib::sin(angle));
    println!("cos(π/4) = {}", MathLib::cos(angle));
    println!("sqrt(2) = {}", MathLib::sqrt(2.0));
    println!("2^3 = {}", MathLib::pow(2.0, 3.0));
}

复杂数据结构

传递结构体

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

#[repr(C)]
struct Person {
    name: *const c_char,
    age: c_int,
    height: f64,
}

extern "C" {
    fn print_person(person: *const Person);
    fn create_person(name: *const c_char, age: c_int, height: f64) -> Person;
}

fn main() {
    let name = CString::new("Alice").unwrap();
    
    unsafe {
        let person = create_person(name.as_ptr(), 30, 165.5);
        print_person(&person);
    }
}

处理联合体

use std::os::raw::c_int;

#[repr(C)]
union Number {
    integer: c_int,
    float: f32,
}

extern "C" {
    fn process_number(num: Number, is_float: c_int);
}

fn main() {
    unsafe {
        let int_num = Number { integer: 42 };
        process_number(int_num, 0);
        
        let float_num = Number { float: 3.14 };
        process_number(float_num, 1);
    }
}

回调函数

定义回调函数

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

// 定义回调函数类型
type Callback = extern "C" fn(*const c_char) -> c_int;

extern "C" {
    fn register_callback(callback: Callback);
    fn trigger_callback(message: *const c_char);
}

// Rust回调函数
extern "C" fn rust_callback(message: *const c_char) -> c_int {
    unsafe {
        let c_str = std::ffi::CStr::from_ptr(message);
        let message_str = c_str.to_string_lossy();
        println!("Rust callback received: {}", message_str);
    }
    0
}

fn main() {
    unsafe {
        register_callback(rust_callback);
        
        let message = CString::new("Hello from C!").unwrap();
        trigger_callback(message.as_ptr());
    }
}

带用户数据的回调

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

type CallbackWithData = extern "C" fn(*const c_char, *mut c_void) -> c_int;

extern "C" {
    fn register_callback_with_data(callback: CallbackWithData, user_data: *mut c_void);
    fn trigger_callback_with_data(message: *const c_char);
}

// 用户数据结构
struct UserData {
    count: i32,
    name: String,
}

extern "C" fn rust_callback_with_data(message: *const c_char, user_data: *mut c_void) -> c_int {
    unsafe {
        let c_str = std::ffi::CStr::from_ptr(message);
        let message_str = c_str.to_string_lossy();
        
        let user_data = &mut *(user_data as *mut UserData);
        user_data.count += 1;
        
        println!("Callback #{} for {}: {}", user_data.count, user_data.name, message_str);
    }
    0
}

fn main() {
    let mut user_data = Box::new(UserData {
        count: 0,
        name: "Rust User".to_string(),
    });
    
    unsafe {
        register_callback_with_data(
            rust_callback_with_data,
            user_data.as_mut() as *mut UserData as *mut c_void,
        );
        
        let message = CString::new("Hello with data!").unwrap();
        trigger_callback_with_data(message.as_ptr());
        
        // 保持user_data的生命周期
        std::mem::forget(user_data);
    }
}

图像处理库

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

#[repr(C)]
struct Image {
    width: c_uint,
    height: c_uint,
    data: *mut u8,
}

extern "C" {
    fn load_image(filename: *const c_char) -> *mut Image;
    fn save_image(image: *const Image, filename: *const c_char) -> c_int;
    fn blur_image(image: *mut Image, radius: c_int) -> c_int;
    fn free_image(image: *mut Image);
}

pub struct SafeImage {
    image: *mut Image,
}

impl SafeImage {
    pub fn load(filename: &str) -> Result<Self, String> {
        let c_filename = CString::new(filename).map_err(|_| "Invalid filename")?;
        
        unsafe {
            let image = load_image(c_filename.as_ptr());
            if image.is_null() {
                Err("Failed to load image".to_string())
            } else {
                Ok(SafeImage { image })
            }
        }
    }
    
    pub fn save(&self, filename: &str) -> Result<(), String> {
        let c_filename = CString::new(filename).map_err(|_| "Invalid filename")?;
        
        unsafe {
            let result = save_image(self.image, c_filename.as_ptr());
            if result != 0 {
                Err("Failed to save image".to_string())
            } else {
                Ok(())
            }
        }
    }
    
    pub fn blur(&mut self, radius: i32) -> Result<(), String> {
        unsafe {
            let result = blur_image(self.image, radius as c_int);
            if result != 0 {
                Err("Failed to blur image".to_string())
            } else {
                Ok(())
            }
        }
    }
    
    pub fn width(&self) -> u32 {
        unsafe { (*self.image).width }
    }
    
    pub fn height(&self) -> u32 {
        unsafe { (*self.image).height }
    }
}

impl Drop for SafeImage {
    fn drop(&mut self) {
        unsafe {
            free_image(self.image);
        }
    }
}

fn main() {
    match SafeImage::load("input.jpg") {
        Ok(mut image) => {
            println!("Loaded image: {}x{}", image.width(), image.height());
            
            if let Err(e) = image.blur(5) {
                println!("Error blurring image: {}", e);
            } else if let Err(e) = image.save("output.jpg") {
                println!("Error saving image: {}", e);
            } else {
                println!("Image processed successfully");
            }
        }
        Err(e) => println!("Error loading image: {}", e),
    }
}

错误处理

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

#[derive(Debug)]
enum FfiError {
    InvalidInput(String),
    OperationFailed(String),
    MemoryError,
}

impl std::fmt::Display for FfiError {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        match self {
            FfiError::InvalidInput(msg) => write!(f, "Invalid input: {}", msg),
            FfiError::OperationFailed(msg) => write!(f, "Operation failed: {}", msg),
            FfiError::MemoryError => write!(f, "Memory error"),
        }
    }
}

impl std::error::Error for FfiError {}

extern "C" {
    fn process_data(input: *const c_char, output: *mut c_char, output_size: c_int) -> c_int;
    fn get_last_error() -> *const c_char;
}

pub fn safe_process_data(input: &str, output_size: usize) -> Result<String, FfiError> {
    let c_input = CString::new(input).map_err(|_| FfiError::InvalidInput("Invalid input string".to_string()))?;
    let mut output_buffer = vec![0u8; output_size];
    
    unsafe {
        let result = process_data(
            c_input.as_ptr(),
            output_buffer.as_mut_ptr() as *mut c_char,
            output_size as c_int,
        );
        
        if result == 0 {
            let c_str = CStr::from_ptr(output_buffer.as_ptr() as *const c_char);
            Ok(c_str.to_string_lossy().to_string())
        } else {
            let error_ptr = get_last_error();
            if error_ptr.is_null() {
                Err(FfiError::OperationFailed("Unknown error".to_string()))
            } else {
                let error_c_str = CStr::from_ptr(error_ptr);
                Err(FfiError::OperationFailed(error_c_str.to_string_lossy().to_string()))
            }
        }
    }
}

fn main() {
    match safe_process_data("test input", 256) {
        Ok(result) => println!("Result: {}", result),
        Err(e) => println!("Error: {}", e),
    }
}

性能优化

批量操作

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

extern "C" {
    fn process_batch(inputs: *const *const c_char, count: c_int, outputs: *mut c_int) -> c_int;
}

pub fn safe_process_batch(inputs: &[String]) -> Result<Vec<i32>, String> {
    let c_strings: Vec<CString> = inputs
        .iter()
        .map(|s| CString::new(s.clone()))
        .collect::<Result<Vec<_>, _>>()
        .map_err(|_| "Invalid input string")?;
    
    let c_ptrs: Vec<*const c_char> = c_strings.iter().map(|s| s.as_ptr()).collect();
    let mut outputs = vec![0i32; inputs.len()];
    
    unsafe {
        let result = process_batch(c_ptrs.as_ptr(), inputs.len() as c_int, outputs.as_mut_ptr());
        if result == 0 {
            Ok(outputs)
        } else {
            Err("Batch processing failed".to_string())
        }
    }
}

fn main() {
    let inputs = vec![
        "input1".to_string(),
        "input2".to_string(),
        "input3".to_string(),
    ];
    
    match safe_process_batch(&inputs) {
        Ok(outputs) => {
            for (i, output) in outputs.iter().enumerate() {
                println!("Input {} -> Output {}", i, output);
            }
        }
        Err(e) => println!("Error: {}", e),
    }
}

常见陷阱与最佳实践

1. 内存管理

// 错误的做法:忘记释放内存
fn bad_example() {
    unsafe {
        let ptr = some_c_function_that_allocates();
        // 忘记调用对应的free函数
    }
}

// 正确的做法:使用RAII
struct SafePtr<T> {
    ptr: *mut T,
    free_fn: unsafe fn(*mut T),
}

impl<T> SafePtr<T> {
    fn new(ptr: *mut T, free_fn: unsafe fn(*mut T)) -> Self {
        Self { ptr, free_fn }
    }
}

impl<T> Drop for SafePtr<T> {
    fn drop(&mut self) {
        if !self.ptr.is_null() {
            unsafe {
                (self.free_fn)(self.ptr);
            }
        }
    }
}

2. 字符串处理

use std::ffi::{CString, CStr};

// 错误的做法:直接使用字符串字面量
fn bad_string_example() {
    unsafe {
        some_c_function("hello".as_ptr() as *const i8); // 可能包含null字节
    }
}

// 正确的做法:使用CString
fn good_string_example() {
    let c_string = CString::new("hello").unwrap();
    unsafe {
        some_c_function(c_string.as_ptr());
    }
}

3. 错误处理

// 错误的做法:忽略错误
fn bad_error_handling() {
    unsafe {
        let result = some_c_function();
        // 忽略返回值
    }
}

// 正确的做法:检查错误
fn good_error_handling() -> Result<(), String> {
    unsafe {
        let result = some_c_function();
        if result == 0 {
            Ok(())
        } else {
            Err("C function failed".to_string())
        }
    }
}

写在最后

Rust的FFI系统提供了:

  1. 互操作性:与其他语言无缝集成
  2. 性能:零开销的跨语言调用
  3. 安全性:在FFI边界处保持类型安全
  4. 灵活性:支持各种调用约定和数据类型

FFI是Rust生态系统的重要组成部分,它让我们能够利用现有的C/C++库,同时享受Rust的安全性和性能优势。通过合理的抽象和错误处理,我们可以构建出既安全又高效的跨语言接口。

原文链接:,转发请注明来源!