如何在 Rust 中正确包装 C 函数指针?
How to properly wrap a C function pointer in Rust?
我有一个带有函数指针的 C 结构 Foo
。在我的 Rust 绑定中,我希望允许用户设置此函数指针,但我希望避免用户不得不处理 FFI 类型。
foo.h
struct Foo {
void* internal;
uint8_t a;
void (*cb_mutate_a)(void*);
};
struct Foo* foo_new();
void foo_free(struct Foo* foo);
void foo_call(struct Foo* foo);
foo.c
struct Foo* foo_new() {
return calloc(1, sizeof(struct Foo));
}
void foo_free(struct Foo* foo) {
free(foo);
}
void foo_call(struct Foo* foo) {
return foo->cb_mutate_a(foo->internal);
}
我目前的解决方案是创建一个 Rust 结构 Bar
,它有一个指向 bindgen 生成的 C 结构的指针 foo_sys::Foo
,并且在其中我有一个特征对象(rust_cb
) 这是我想在 Rust API 中公开的实际回调。我将 C cb
设置为指向 wrapped_cb
并将 internal
指针设置为指向 Bar
,这样我就可以从内部调用 rust_cb
wrapped_cb
。
此代码有效,但抱怨访问未初始化的内存。当我使用 Valgrind 运行 时,我在访问 wrapped_cb
中的 (*bar).rust_cb
时看到 invalid reads
。我不确定我做错了什么。
extern crate libc;
use std::ffi;
#[repr(C)]
#[derive(Debug, Copy)]
pub struct Foo {
pub internal: *mut libc::c_void,
pub a: u8,
pub cb_mutate_a: ::core::option::Option<unsafe extern "C" fn(arg1: *mut libc::c_void)>,
}
impl Clone for Foo {
fn clone(&self) -> Self {
*self
}
}
extern "C" {
pub fn foo_new() -> *mut Foo;
}
extern "C" {
pub fn foo_free(foo: *mut Foo);
}
extern "C" {
pub fn foo_call(foo: *mut Foo);
}
struct Bar {
ptr: *mut Foo,
rust_cb: Option<Box<dyn FnMut(&mut u8)>>,
}
impl Bar {
fn new() -> Bar {
unsafe {
let mut bar = Bar {
ptr: foo_new(),
rust_cb: Some(Box::new(rust_cb)),
};
(*bar.ptr).cb_mutate_a = Some(cb);
let bar_ptr: *mut ffi::c_void = &mut bar as *mut _ as *mut ffi::c_void;
(*bar.ptr).internal = bar_ptr;
bar
}
}
}
impl Drop for Bar {
fn drop(&mut self) {
unsafe {
foo_free(self.ptr);
}
}
}
extern "C" fn cb(ptr: *mut libc::c_void) {
let bar = ptr as *mut _ as *mut Bar;
unsafe {
match &mut (*bar).rust_cb {
None => panic!("Missing callback!"),
Some(cb) => (*cb)(&mut (*(*bar).ptr).a),
}
}
}
fn rust_cb(a: &mut u8) {
*a += 2;
}
fn main() {
unsafe {
let bar = Bar::new();
let _ = foo_call(bar.ptr);
}
}
我查看了相关问题,这些问题似乎回答了我的问题,但解决了不同的问题:
这使用 dlsym
从 C 调用 Rust 回调。
这些描述了将闭包作为 C 函数指针传递的解决方案。
我想要实现的是拥有一个 Rust 结构 (Bar
),它有一个指向 C 结构 (Foo
) 的成员变量 ptr
,它本身有一个 void *internal
指向 Rust 结构 Bar
。
想法是在 Rust 结构 Bar
中为每个 C 结构 Foo
中的函数指针设置一个特征对象和包装函数。创建 Bar
对象后,我们执行以下操作:
- 创建 C
Foo
并在 Bar
中保留指向它的指针。
- 将
Foo->callback
指向包装器 Rust 函数。
- 将
Foo->internal
指向 Bar
。
由于包装函数传递了 internal
指针,我们能够获得指向 Bar
的指针并调用相应的闭包(来自 trait obj)。
我能够将 C void*
指向我的 Rust 结构,我也能够从 Rust 回调(或闭包)中获得指向它的指针,这就是相关问题所解决的问题。我 面临的问题可能与生命周期有关,因为其中一个值的寿命不够长,无法在回调中使用。
这是 Bar::new()
函数中的错误(由@Shepmaster 识别),是由于我对 Rust 移动语义的根本误解造成的。通过 Bar::new()
return a Box<Bar>
-
修复
impl Bar {
fn new() -> Box<Bar> {
unsafe {
let mut bar = Box::new(Bar { ptr: foo_sys::foo_new(), rust_cb: Some(Box::new(rust_cb)) });
(*bar.ptr).cb_mutate_a = Some(cb);
let bar_ptr: *mut ffi::c_void = &mut *bar as *mut _ as *mut ffi::c_void;
(*bar.ptr).internal = bar_ptr;
bar
}
}
}
我有一个带有函数指针的 C 结构 Foo
。在我的 Rust 绑定中,我希望允许用户设置此函数指针,但我希望避免用户不得不处理 FFI 类型。
foo.h
struct Foo {
void* internal;
uint8_t a;
void (*cb_mutate_a)(void*);
};
struct Foo* foo_new();
void foo_free(struct Foo* foo);
void foo_call(struct Foo* foo);
foo.c
struct Foo* foo_new() {
return calloc(1, sizeof(struct Foo));
}
void foo_free(struct Foo* foo) {
free(foo);
}
void foo_call(struct Foo* foo) {
return foo->cb_mutate_a(foo->internal);
}
我目前的解决方案是创建一个 Rust 结构 Bar
,它有一个指向 bindgen 生成的 C 结构的指针 foo_sys::Foo
,并且在其中我有一个特征对象(rust_cb
) 这是我想在 Rust API 中公开的实际回调。我将 C cb
设置为指向 wrapped_cb
并将 internal
指针设置为指向 Bar
,这样我就可以从内部调用 rust_cb
wrapped_cb
。
此代码有效,但抱怨访问未初始化的内存。当我使用 Valgrind 运行 时,我在访问 wrapped_cb
中的 (*bar).rust_cb
时看到 invalid reads
。我不确定我做错了什么。
extern crate libc;
use std::ffi;
#[repr(C)]
#[derive(Debug, Copy)]
pub struct Foo {
pub internal: *mut libc::c_void,
pub a: u8,
pub cb_mutate_a: ::core::option::Option<unsafe extern "C" fn(arg1: *mut libc::c_void)>,
}
impl Clone for Foo {
fn clone(&self) -> Self {
*self
}
}
extern "C" {
pub fn foo_new() -> *mut Foo;
}
extern "C" {
pub fn foo_free(foo: *mut Foo);
}
extern "C" {
pub fn foo_call(foo: *mut Foo);
}
struct Bar {
ptr: *mut Foo,
rust_cb: Option<Box<dyn FnMut(&mut u8)>>,
}
impl Bar {
fn new() -> Bar {
unsafe {
let mut bar = Bar {
ptr: foo_new(),
rust_cb: Some(Box::new(rust_cb)),
};
(*bar.ptr).cb_mutate_a = Some(cb);
let bar_ptr: *mut ffi::c_void = &mut bar as *mut _ as *mut ffi::c_void;
(*bar.ptr).internal = bar_ptr;
bar
}
}
}
impl Drop for Bar {
fn drop(&mut self) {
unsafe {
foo_free(self.ptr);
}
}
}
extern "C" fn cb(ptr: *mut libc::c_void) {
let bar = ptr as *mut _ as *mut Bar;
unsafe {
match &mut (*bar).rust_cb {
None => panic!("Missing callback!"),
Some(cb) => (*cb)(&mut (*(*bar).ptr).a),
}
}
}
fn rust_cb(a: &mut u8) {
*a += 2;
}
fn main() {
unsafe {
let bar = Bar::new();
let _ = foo_call(bar.ptr);
}
}
我查看了相关问题,这些问题似乎回答了我的问题,但解决了不同的问题:
这使用 dlsym
从 C 调用 Rust 回调。
这些描述了将闭包作为 C 函数指针传递的解决方案。
我想要实现的是拥有一个 Rust 结构 (Bar
),它有一个指向 C 结构 (Foo
) 的成员变量 ptr
,它本身有一个 void *internal
指向 Rust 结构 Bar
。
想法是在 Rust 结构 Bar
中为每个 C 结构 Foo
中的函数指针设置一个特征对象和包装函数。创建 Bar
对象后,我们执行以下操作:
- 创建 C
Foo
并在Bar
中保留指向它的指针。 - 将
Foo->callback
指向包装器 Rust 函数。 - 将
Foo->internal
指向Bar
。
由于包装函数传递了 internal
指针,我们能够获得指向 Bar
的指针并调用相应的闭包(来自 trait obj)。
我能够将 C void*
指向我的 Rust 结构,我也能够从 Rust 回调(或闭包)中获得指向它的指针,这就是相关问题所解决的问题。我 面临的问题可能与生命周期有关,因为其中一个值的寿命不够长,无法在回调中使用。
这是 Bar::new()
函数中的错误(由@Shepmaster 识别),是由于我对 Rust 移动语义的根本误解造成的。通过 Bar::new()
return a Box<Bar>
-
impl Bar {
fn new() -> Box<Bar> {
unsafe {
let mut bar = Box::new(Bar { ptr: foo_sys::foo_new(), rust_cb: Some(Box::new(rust_cb)) });
(*bar.ptr).cb_mutate_a = Some(cb);
let bar_ptr: *mut ffi::c_void = &mut *bar as *mut _ as *mut ffi::c_void;
(*bar.ptr).internal = bar_ptr;
bar
}
}
}