Rust调用接收void*参数的C库回调函数时出现段错误的解决方案
解决Rust向C库传递回调函数时的段错误问题
首先要明确核心问题:你的C库call_it函数实际上期望接收的是**void(*)(int)类型的函数指针**,但你的Rust代码传递的是指向trait对象的指针,类型完全不匹配,导致C侧错误地把这个非函数指针当成可执行地址调用,直接触发段错误。下面分两种场景给出解决方案:
场景1:使用无捕获的普通函数
这是最简单的情况,只需要让Rust函数的签名严格匹配C的回调类型,直接传递函数指针即可:
use std::os::raw::{c_int, c_void}; // 链接到你的C静态库 #[link(name = "demo", kind = "static")] extern "C" { fn call_it(f: *mut c_void); } // 函数签名完全匹配C的void(*)(int):参数用c_int对应C的int,无返回值 fn print_number(n: c_int) { println!("Received number from C: {}", n); } fn main() { unsafe { // 将Rust函数指针转换为*mut c_void,直接传给C函数 call_it(print_number as *mut c_void); } }
场景2:使用带捕获变量的闭包
由于C库的接口没有提供额外的用户数据参数(只能传递函数指针本身),我们需要借助**跳板函数(trampoline)**和全局存储来实现闭包的传递:
use std::os::raw::{c_int, c_void}; use std::sync::Mutex; // 用线程安全的全局互斥锁存储闭包,避免多线程冲突 static mut CALLBACK: Mutex<Option<Box<dyn FnMut(c_int) + 'static>>> = Mutex::new(None); // 跳板函数,签名严格匹配C的回调类型void(*)(int) extern "C" fn callback_trampoline(n: c_int) { unsafe { // 锁定全局存储,取出闭包并调用 if let Some(ref mut cb) = *CALLBACK.lock().unwrap() { cb(n); } } } #[link(name = "demo", kind = "static")] extern "C" { fn call_it(f: *mut c_void); } // 封装闭包传递逻辑的函数 fn run_with_closure<F>(mut f: F) where F: FnMut(c_int) + 'static, { unsafe { // 将闭包装箱后存入全局存储 *CALLBACK.lock().unwrap() = Some(Box::new(f)); // 把跳板函数的指针传给C库 call_it(callback_trampoline as *mut c_void); // 调用完成后清空全局存储,避免内存泄漏 *CALLBACK.lock().unwrap() = None; } } fn main() { let captured_value = 100; run_with_closure(move |n| { println!("C sent: {}, captured value: {}", n, captured_value); }); }
为什么你的原始代码会触发段错误?
你在run函数中把指向&mut dyn FnMut(isize) trait对象的指针转成*mut c_void传给了call_it,但C侧会把这个指针当成可执行的函数地址去调用。而trait对象是一个胖指针(包含数据指针和虚表指针),根本不是有效的函数入口地址,因此执行时直接触发内存访问错误(SIGSEGV)。
注意事项
- 如果C库会异步调用回调(比如在其他线程或后续时间点调用),要保证全局存储的闭包生命周期足够长,不能提前清空。
- 单线程场景下可以去掉互斥锁,简化为普通静态变量,但仍然要注意unsafe操作的安全性。
- 始终确保Rust侧的函数/闭包签名与C侧严格匹配:C的
int对应Rust的c_int,调用约定使用extern "C"保持一致。
内容的提问来源于stack exchange,提问作者Netwave
相关产品推荐
相关产品推荐

