4

我有一个Foo带有函数指针的 C 结构。在我的 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,并且在其中我有一个 trait 对象 ( rust_cb),它是我想在 Rust API 中公开的实际回调。我将 C设置cb为指向 awrapped_cb并将internal指针设置为指向Bar,这样我就可以rust_cb从内部调用wrapped_cb

此代码有效,但抱怨访问未初始化的内存。当我使用 Valgrind 运行它时,我看到invalid reads了访问(*bar).rust_cb内部的点wrapped_cb。我不确定我做错了什么。

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 struct ( ),它有一个指向 C struct ( )Bar的成员变量,它本身有一个指向 Rust struct的成员变量。ptrFoovoid *internalBar

这个想法是在 Rust struct 中Bar为每个函数指针在 C struct中拥有一个 trait 对象和包装函数Foo。创建Bar对象时,我们执行以下操作:

  • 创建 CFoo并在Bar.
  • 指向Foo->callback包装器 Rust 函数。
  • 指向。Foo->internal_Bar

由于包装函数被传递了internal指针,我们能够获得一个指针Bar并调用相应的闭包(来自 trait obj)。

我能够将 Cvoid*指向我的 Rust 结构,并且我还能够从 Rust 回调(或闭包)中获取指向它的指针,这就是相关问题所要解决的问题。面临的问题可能与生命周期有关,因为其中一个值的寿命不够长,无法在回调中使用。

4

1 回答 1

2

这是Bar::new()函数中的一个错误(由@Shepmaster 识别),是由于我对 Rust 移动语义的根本误解而引起的。通过Bar::new()返回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
        }
    }
}
于 2020-04-01T20:17:27.123 回答