我已经创建了到 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 中的函数指针是无状态的,并且不接受某种类型的任何用户数据。(如果我错了,请纠正我。)
我通过阅读这些来源和类似来源得出了这个结论:
作为后备方案,我也许可以想象一个像这样的 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)..
如果您能为我指明正确的方向,我将非常高兴。
多谢。
首先,这是一个很难解决的问题。显然,您需要某种方法将数据传递到函数参数之外的函数中。然而,几乎任何通过 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)
我没有游乐场链接,但当我在本地测试时它运行良好。这段代码有两点需要注意:
假设在程序的整个持续时间内从多个线程重复调用该函数,那么对于 c 代码的作用是非常悲观的。您也许可以摆脱更少的限制,具体取决于 libpd 的作用。
它会泄漏内存以确保回调在程序的生命周期内有效。这可能没问题,因为回调通常只设置一次。如果不保留指向已注册回调的指针,则无法安全地恢复此内存。
还值得注意的是,这些libffi::high::ClosureMutN结构是不健全的,因为它们允许对传递的包装闭包使用别名可变引用。有一个 PR 可以修复等待合并的问题。