Rust与C/C++互操作指南:从理论到实战
2026/4/6 7:56:44 网站建设 项目流程
Rust与C/C互操作指南从理论到实战前言大家好我是第一程序员名字大人很菜一个正在跟Rust所有权和生命周期死磕的后端转Rust萌新。最近我开始学习Rust与C/C的互操作发现这是一个非常实用但也比较复杂的特性。今天就来分享一下我的学习心得希望能帮助到同样在学习Rust与C/C互操作的小伙伴们也欢迎大佬们轻喷指正Rust与C/C互操作的必要性在开始之前我想先聊聊为什么需要Rust与C/C互操作。Rust作为一种新兴的系统级编程语言虽然有很多优势但C/C已经存在了几十年拥有庞大的生态系统和丰富的库。在实际开发中我们经常需要使用现有的C/C库很多领域都有成熟的C/C库我们可以在Rust中使用这些库而不需要重新实现。将Rust代码集成到C/C项目中我们可以用Rust编写一些性能关键或安全关键的代码然后集成到现有的C/C项目中。逐步迁移C/C代码到Rust对于大型C/C项目我们可以逐步将一些模块迁移到Rust而不是一次性重写整个项目。Rust调用C/C代码Rust提供了extern C语法来调用C/C代码。让我们来看一个简单的例子1. 创建C/C库首先我们创建一个简单的C库// math.c int add(int a, int b) { return a b; } int subtract(int a, int b) { return a - b; }然后我们将其编译为动态库gcc -c -fPIC math.c -o math.o gcc -shared -o libmath.so math.o2. 在Rust中调用C库现在我们可以在Rust中调用这个C库// src/main.rs #[link(name math)] extern C { fn add(a: i32, b: i32) - i32; fn subtract(a: i32, b: i32) - i32; } fn main() { unsafe { let result1 add(5, 3); let result2 subtract(5, 3); println!(5 3 {}, result1); println!(5 - 3 {}, result2); } }需要注意的是调用C代码需要在unsafe块中进行因为Rust无法保证C代码的安全性。3. 使用bindgen自动生成绑定对于大型C/C库手动编写绑定会非常繁琐。我们可以使用bindgen工具来自动生成绑定cargo install bindgen然后创建一个wrapper.h文件包含我们需要的C头文件// wrapper.h int add(int a, int b); int subtract(int a, int b);最后使用bindgen生成绑定bindgen wrapper.h -o src/bindings.rs现在我们可以在Rust中使用生成的绑定// src/main.rs include!(bindings.rs); fn main() { unsafe { let result1 add(5, 3); let result2 subtract(5, 3); println!(5 3 {}, result1); println!(5 - 3 {}, result2); } }C/C调用Rust代码Rust也可以生成C/C可以调用的库。让我们来看一个简单的例子1. 创建Rust库首先我们创建一个Rust库// src/lib.rs #[no_mangle] pub extern C fn rust_add(a: i32, b: i32) - i32 { a b } #[no_mangle] pub extern C fn rust_subtract(a: i32, b: i32) - i32 { a - b }然后我们在Cargo.toml文件中设置库类型[lib] crate-type [cdylib]最后我们编译这个库cargo build --release2. 在C/C中调用Rust库现在我们可以在C/C中调用这个Rust库// main.c #include stdio.h // 声明Rust函数 extern int rust_add(int a, int b); extern int rust_subtract(int a, int b); int main() { int result1 rust_add(5, 3); int result2 rust_subtract(5, 3); printf(5 3 %d\n, result1); printf(5 - 3 %d\n, result2); return 0; }然后我们编译并链接这个C程序gcc main.c -Ltarget/release -lrust_cpp_interop -o main类型转换在Rust与C/C互操作时我们需要注意类型转换的问题。Rust和C/C的类型系统有所不同我们需要确保类型转换的正确性。基本类型转换Rust类型C/C类型i8signed chari16shorti32inti64long longu8unsigned charu16unsigned shortu32unsigned intu64unsigned long longf32floatf64doubleboolbool*const Tconst T**mut TT*字符串转换Rust的字符串类型和C/C的字符串类型有所不同我们需要进行适当的转换// Rust字符串转换为C字符串 use std::ffi::{CString, CStr}; use std::os::raw::c_char; #[no_mangle] pub extern C fn rust_greet(name: *const c_char) - *mut c_char { let c_str unsafe { CStr::from_ptr(name) }; let rust_str c_str.to_str().unwrap(); let greet format!(Hello, {}!, rust_str); let c_greet CString::new(greet).unwrap(); c_greet.into_raw() } // 释放C字符串 #[no_mangle] pub extern C fn rust_free(ptr: *mut c_char) { unsafe { if !ptr.is_null() { CString::from_raw(ptr); } } }在C/C中使用// main.c #include stdio.h #include stdlib.h extern char* rust_greet(const char* name); extern void rust_free(char* ptr); int main() { char* name World; char* greet rust_greet(name); printf(%s\n, greet); rust_free(greet); return 0; }结构体转换对于结构体我们需要确保Rust和C/C的结构体布局一致// src/lib.rs #[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 { let p1 unsafe { *p1 }; let p2 unsafe { *p2 }; let dx p1.x - p2.x; let dy p1.y - p2.y; (dx * dx dy * dy).sqrt() }在C/C中使用// main.c #include stdio.h // 声明Rust结构体 typedef struct Point { double x; double y; } Point; extern double rust_distance(const Point* p1, const Point* p2); int main() { Point p1 {0.0, 0.0}; Point p2 {3.0, 4.0}; double distance rust_distance(p1, p2); printf(Distance: %f\n, distance); return 0; }内存管理在Rust与C/C互操作时内存管理是一个重要的问题。我们需要确保内存的正确分配和释放避免内存泄漏和悬垂指针。Rust分配C/C释放如果Rust分配内存并传递给C/C那么C/C需要负责释放内存。我们可以提供一个释放内存的函数#[no_mangle] pub extern C fn rust_allocate(size: usize) - *mut u8 { let mut vec Vec::with_capacity(size); let ptr vec.as_mut_ptr(); std::mem::forget(vec); // 避免Vec自动释放内存 ptr } #[no_mangle] pub extern C fn rust_deallocate(ptr: *mut u8, size: usize) { unsafe { if !ptr.is_null() { Vec::from_raw_parts(ptr, 0, size); } } }C/C分配Rust释放如果C/C分配内存并传递给Rust那么Rust需要负责释放内存。我们可以使用Box::from_raw来获取所有权#[no_mangle] pub extern C fn rust_process_data(ptr: *mut i32, len: usize) { let slice unsafe { std::slice::from_raw_parts_mut(ptr, len) }; // 处理数据 for i in 0..len { slice[i] * 2; } // 释放内存 unsafe { libc::free(ptr as *mut libc::c_void); } }实战案例Rust与OpenCV互操作现在让我们实现一个Rust与OpenCV互操作的例子来实践一下Rust与C/C的互操作1. 安装OpenCV首先我们需要安装OpenCV# Ubuntu sudo apt-get install libopencv-dev # macOS brew install opencv2. 创建Rust项目创建一个新的Rust项目cargo new rust-opencv-example cd rust-opencv-example3. 添加依赖在Cargo.toml文件中添加bindgen和cc依赖[package] name rust-opencv-example version 0.1.0 edition 2021 [dependencies] [build-dependencies] bindgen 0.60 cc 1.04. 创建构建脚本创建build.rs文件用于生成OpenCV的绑定// build.rs use bindgen::Builder; use std::env; use std::path::PathBuf; fn main() { // 编译C代码 cc::Build::new() .cpp(true) .file(src/cpp/opencv_wrapper.cpp) .include(/usr/include/opencv4) .link_libs([opencv_core, opencv_imgcodecs, opencv_highgui]) .compile(opencv_wrapper); // 生成绑定 let bindings Builder::default() .header(src/cpp/opencv_wrapper.h) .include(/usr/include/opencv4) .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(Couldnt write bindings!); }5. 创建C包装器创建src/cpp/opencv_wrapper.h文件// src/cpp/opencv_wrapper.h extern C { void display_image(const char* path); }创建src/cpp/opencv_wrapper.cpp文件// src/cpp/opencv_wrapper.cpp #include opencv2/opencv.hpp #include opencv_wrapper.h void display_image(const char* path) { cv::Mat image cv::imread(path); if (image.empty()) { return; } cv::imshow(Image, image); cv::waitKey(0); }6. 在Rust中使用OpenCV创建src/main.rs文件// src/main.rs include!(concat!(env!(OUT_DIR), /bindings.rs)); fn main() { unsafe { display_image(test.jpg.as_ptr() as *const i8); } }7. 编译和运行cargo run互操作的最佳实践通过学习和实践我总结了一些Rust与C/C互操作的最佳实践使用repr(C)对于需要在Rust和C/C之间传递的结构体使用#[repr(C)]来确保布局一致。使用no_mangle对于需要在C/C中调用的Rust函数使用#[no_mangle]来确保函数名不被Rust编译器修改。注意内存管理确保内存的正确分配和释放避免内存泄漏和悬垂指针。使用bindgen对于大型C/C库使用bindgen来自动生成绑定避免手动编写绑定的繁琐和错误。使用unsafe块调用C/C代码时需要在unsafe块中进行因为Rust无法保证C/C代码的安全性。注意类型转换确保Rust和C/C之间的类型转换正确避免类型不匹配的问题。提供清晰的接口设计清晰的接口避免复杂的类型和内存管理操作。测试充分测试互操作代码确保其正确性和安全性。学习心得通过学习Rust与C/C的互操作我总结了以下几点心得互操作是Rust的重要特性Rust与C/C的互操作是Rust的重要特性它使得Rust可以与现有的C/C生态系统无缝集成。需要注意安全性调用C/C代码时需要在unsafe块中进行因为Rust无法保证C/C代码的安全性。内存管理是关键在Rust与C/C互操作时内存管理是一个关键问题需要确保内存的正确分配和释放。类型转换需要注意Rust和C/C的类型系统有所不同需要确保类型转换的正确性。工具可以提高效率使用bindgen等工具可以自动生成绑定提高开发效率。多实践多练习互操作是一个需要实践的技能只有通过大量的实践才能掌握。总结Rust与C/C的互操作是一个非常实用的特性它使得Rust可以与现有的C/C生态系统无缝集成。通过本文的介绍希望能帮助大家了解如何进行Rust与C/C的互操作也希望大家能在实际开发中应用这些技术。保持学习保持输出今天终于搞懂了Rust与C/C的互操作哭死如果本文对你有帮助欢迎点赞、收藏也欢迎在评论区分享你的学习心得和问题。向大佬们低头学习参考资料Rust官方文档 - Foreign Function InterfaceRust程序设计语言中文版- 外部函数接口bindgen documentationcc-rs documentation

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询