将安全的 Rust 函数指针传递给 C

Ali*_*may 6 ffi rust libpd

我已经创建了到 C 库的 Rust 绑定,目前正在围绕它编写安全包装器。

问题是关于接受 C 函数指针的 C 函数,而这些指针无法接受任何自定义用户数据。

用一个例子来解释更容易,

C 库:

// The function pointer I need to pass,
typedef void (*t_function_pointer_library_wants)(const char *argument);
// The function which I need to pass the function pointer to,
void register_hook(const t_function_pointer_library_wants hook);
Run Code Online (Sandbox Code Playgroud)

绑定:

// For the function pointer type
pub type t_function_pointer_library_wants = ::std::option::Option<unsafe extern "C" fn(argument: *const ::std::os::raw::c_char)>;
// For the function which accepts the pointer
extern "C" {
    pub fn register_hook(hook: t_function_pointer_library_wants);
}
Run Code Online (Sandbox Code Playgroud)

如果我可以像下面这样向用户公开 api,那就太好了,

// Let's assume my safe wrapper is named on_something
// ..
on_something(|argument|{
    // Do something with the argument..
});
// ..
Run Code Online (Sandbox Code Playgroud)

尽管根据下面的消息来源,由于缺乏将存储闭包状态的内存部分的管理移交给 C 的能力,因此阻止了我创建此类 API。因为 C 中的函数指针是无状态的,并且不接受某种类型的任何用户数据。(如果我错了,请纠正我。)

我通过阅读这些来源和类似来源得出了这个结论:

蹦床技术

类似的蹦床技术

Hacky 线程局部技术

来源 Shepmaster 的回答

作为后备方案,我也许可以想象一个像这样的 API,其中我传递一个函数指针。

fn handler(argument: &str) {
    // Do something with the argument..
}
//..
on_something(handler);
//..
Run Code Online (Sandbox Code Playgroud)

但我对转换一个有点困惑fn(&str)

到一个unsafe extern "C" fn(argument: *const std::os::raw::c_char)..

如果您能为我指明正确的方向,我将非常高兴。

* 实际关注的库是libpd,我创建了一个与此相关的问题。

多谢。

Aid*_*en4 3

首先,这是一个很难解决的问题。显然,您需要某种方法将数据传递到函数参数之外的函数中。然而,几乎任何通过 a 执行此操作的方法都static可能很容易导致竞争条件或更糟,具体取决于 c 库的作用以及该库的使用方式。另一种选择是 JIT 一些调用闭包的粘合代码。乍一看,这似乎更糟糕,但libffi抽象了其中的大部分内容。使用libffi 板条箱的包装器将如下所示:

use std::ffi::CStr;
use libffi::high::Closure1;

fn on_something<F: Fn(&str) + Send + Sync + 'static>(f: F) {
    let closure: &'static _ = Box::leak(Box::new(move |string: *const c_char| {
        let string = unsafe { CStr::from_ptr(string).to_str().unwrap() };
        f(string);
    }));
    let callback = Closure1::new(closure);
    let &code = callback.code_ptr();
    let ptr:unsafe extern "C" fn (*const c_char) = unsafe { std::mem::transmute(code) };
    std::mem::forget(callback);
    unsafe { register_handler(Some(ptr)) };
}
Run Code Online (Sandbox Code Playgroud)

我没有游乐场链接,但当我在本地测试时它运行良好。这段代码有两点需要注意:

  1. 假设在程序的整个持续时间内从多个线程重复调用该函数,那么对于 c 代码的作用是非常悲观的。您也许可以摆脱更少的限制,具体取决于 libpd 的作用。

  2. 它会泄漏内存以确保回调在程序的生命周期内有效。这可能没问题,因为回调通常只设置一次。如果不保留指向已注册回调的指针,则无法安全地恢复此内存。

还值得注意的是,这些libffi::high::ClosureMutN结构是不健全的,因为它们允许对传递的包装闭包使用别名可变引用。有一个 PR 可以修复等待合并的问题。