3

我已经为 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);

绑定:

// 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);
}

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

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

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

通过阅读这些资料和类似资料,我得出了这个结论:

蹦床技术

类似的蹦床技术

Hacky 线程局部技术

Shepmaster的答案中的来源

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

fn handler(argument: &str) {
    // Do something with the argument..
}
//..
on_something(handler);
//..

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

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

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

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

非常感谢。

4

1 回答 1

2

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

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)) };
}

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

  1. 假设在程序的整个持续时间内从多个线程重复调用该函数,它对 c 代码的作用极为悲观。根据 libpd 的功能,您可能能够摆脱更少的限制。

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

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

于 2022-01-25T05:00:46.257 回答