Как правильно обернуть указатель функции C в 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);
}

Мое текущее решение - создать структуру RustBar который имеет указатель на созданную bindgen структуру C foo_sys::Foo, и в нем у меня есть объект-признак (rust_cb), который является фактическим обратным вызовом, который я хотел бы показать в Rust API. Я поставил Ccb указать на wrapped_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 для вызова обратного вызова Rust из C.

Они описывают решения для передачи замыканий в виде указателей на функции C.

Я пытаюсь создать структуру Rust (Bar) который имеет переменную-член ptr который указывает на структуру C (Foo), который сам имеет void *internal что указывает на структуру RustBar.

Идея состоит в том, чтобы иметь один типажный объект и функцию-оболочку в структуре Rust. Bar на указатель функции в структуре C Foo. КогдаBar объект создан, делаем следующее:

  • создать C Foo и держите указатель на него в Bar.
  • Точка Foo->callback чтобы обернуть функцию Rust.
  • Точка Foo->internal к Bar.

Поскольку функция-оболочка передается internal указатель, мы можем получить указатель на Bar и вызываем соответствующее закрытие (от trait obj).

Я могу указать C void*в мою структуру Rust, и я также могу получить указатель на нее из обратного вызова Rust (или закрытия), что является тем, что касается связанных вопросов. Проблема, которую я имею облицовкой, что, вероятно, связано с временем жизни, потому что одна из ценностей не живут достаточно долго, чтобы быть доступны в функции обратного вызова.

1 ответ

Решение

Это ошибка (идентифицированная @Shepmaster) в Bar::new()функция, вызванная моим фундаментальным непониманием семантики перемещения 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
        }
    }
}
Другие вопросы по тегам