Как правильно обернуть указатель функции 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.
- Как преобразовать замыкание Rust в обратный вызов в стиле C?
- Как передать закрытие через необработанные указатели в качестве аргумента функции 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
}
}
}