如何在Rust中正确地封装C函数指针?

5

我有一个名为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设置为指向wrapped_cb,并将internal指针设置为指向Bar,这样我就能够从wrapped_cb内部调用rust_cb

虽然这段代码能够运行,但却抱怨未初始化的内存访问。当我使用Valgrind运行它时,我看到在访问(*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),其中有一个成员变量ptr指向一个C结构体(Foo),该C结构体本身具有指向Rust结构体Barvoid * internal

想法是在Rust结构体Bar中每个函数指针都有一个trait对象和包装器函数。当创建一个Bar对象时,我们执行以下操作:

  • 创建C Foo并在Bar中保留一个指针。
  • Foo->callback指向包装的Rust函数。
  • Foo->internal指向Bar

由于将内部指针传递给包装函数,因此我们可以得到指向Bar的指针并调用相应的闭包(从trait对象)。

我能够将C void* 指向我的Rust结构体,我也能够从Rust回调(或闭包)中获取指向它的指针,这就是相关问题解决的内容。我正在面临的问题可能与生命周期有关,因为其中一个值的使用时间不足以使其在回调中可用。


2
我认为您不能直接使用特质对象。C 代码需要函数指针,但特质对象是一个胖指针。 - Peter Hall
实际上,我没有直接将C函数指针指向特质对象。相反,我将其指向了wrapped_cb外部函数。 - Shoaib Ahmed
你了解栈是什么吗?你正在获取栈上的一个项目的引用,然后退出函数,导致地址失效。 - Shepmaster
你是指这里 (*bar.ptr).internal = bar_ptr; 吗?但是这个值不是仍然存在于 main() 函数中的 bar 变量中吗,也就是这里 let bar = Bar::new(); - Shoaib Ahmed
1个回答

2

这是一个由@Shepmaster识别出来的bug,发生在Bar::new()函数中,因为我对Rust的move语义存在基本误解。通过让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
        }
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接