将安全的 Rust 函数指针传递给 C
Passing a safe rust function pointer to C
我已经创建了 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 中的函数指针是无状态的,不接收任何类型的用户数据。 (如有错误请指正。)
我通过阅读这些来源和类似来源得出了这个结论:
作为后备,我也许可以想象一个像这样的 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 and there is an issue 我创建的与此相关的库。
非常感谢。
首先,这是一个很难解决的问题。显然,您需要某种方式将数据传递给参数之外的函数。但是,几乎任何通过 static
执行此操作的方法都可能很容易导致竞争条件或更糟,具体取决于 c 库的功能以及库的使用方式。另一种选择是 JIT 一些调用闭包的胶水代码。乍一看,这似乎更糟,但 libffi abstracts most of that away. A wrapper using the 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)) };
}
我没有游乐场link,但我在本地测试时它运行良好。这段代码有两点需要注意:
假设在整个程序持续时间内从多个线程重复调用该函数,那么对于 c 代码的作用是最悲观的。根据 libpd 的功能,您也许可以摆脱更少的限制。
它会泄漏内存以确保回调在程序的生命周期内有效。这可能很好,因为回调通常只设置一次。如果不保留指向已注册回调的指针,就无法安全地恢复此内存。
还值得注意的是 libffi::high::ClosureMutN
结构是不可靠的,因为它们允许对传递的包装闭包使用别名可变引用。有一个 PR 可以解决等待合并的问题。
我已经创建了 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 中的函数指针是无状态的,不接收任何类型的用户数据。 (如有错误请指正。)
我通过阅读这些来源和类似来源得出了这个结论:
作为后备,我也许可以想象一个像这样的 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 and there is an issue 我创建的与此相关的库。
非常感谢。
首先,这是一个很难解决的问题。显然,您需要某种方式将数据传递给参数之外的函数。但是,几乎任何通过 static
执行此操作的方法都可能很容易导致竞争条件或更糟,具体取决于 c 库的功能以及库的使用方式。另一种选择是 JIT 一些调用闭包的胶水代码。乍一看,这似乎更糟,但 libffi abstracts most of that away. A wrapper using the 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)) };
}
我没有游乐场link,但我在本地测试时它运行良好。这段代码有两点需要注意:
假设在整个程序持续时间内从多个线程重复调用该函数,那么对于 c 代码的作用是最悲观的。根据 libpd 的功能,您也许可以摆脱更少的限制。
它会泄漏内存以确保回调在程序的生命周期内有效。这可能很好,因为回调通常只设置一次。如果不保留指向已注册回调的指针,就无法安全地恢复此内存。
还值得注意的是 libffi::high::ClosureMutN
结构是不可靠的,因为它们允许对传递的包装闭包使用别名可变引用。有一个 PR 可以解决等待合并的问题。