You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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)。


注意事项

  1. 如果C库会异步调用回调(比如在其他线程或后续时间点调用),要保证全局存储的闭包生命周期足够长,不能提前清空。
  2. 单线程场景下可以去掉互斥锁,简化为普通静态变量,但仍然要注意unsafe操作的安全性。
  3. 始终确保Rust侧的函数/闭包签名与C侧严格匹配:C的int对应Rust的c_int,调用约定使用extern "C"保持一致。

内容的提问来源于stack exchange,提问作者Netwave

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.27 16:22:34