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系统提供了:
- 互操作性:与其他语言无缝集成
- 性能:零开销的跨语言调用
- 安全性:在FFI边界处保持类型安全
- 灵活性:支持各种调用约定和数据类型
FFI是Rust生态系统的重要组成部分,它让我们能够利用现有的C/C++库,同时享受Rust的安全性和性能优势。通过合理的抽象和错误处理,我们可以构建出既安全又高效的跨语言接口。
