Rust FFI将trait对象作为上下文传递给调用回调

好的,我正在努力实现以下目标:

  1. C召唤生锈
  2. rust调用回c并在用户定义的trait对象上注册回调
  3. c因上下文而生锈
  4. rust调用上下文的回调(trait对象)

我一直在玩它很多。 我走得很远,但还是不太好。

C位:

#include  #include  void *global_ctx; void c_function(void* ctx) { printf("Called c_function\n"); global_ctx = ctx; } int main(void) { void *thing = dlopen("thing/target/debug/libthing.dylib", RTLD_NOW | RTLD_GLOBAL); if (!thing) { printf("error: %s\n", dlerror()); return 1; } void (*rust_function)(void) = dlsym(thing, "rust_function"); void (*rust_cb)(void*) = dlsym(thing, "rust_cb"); printf("rust_function = %p\n", rust_function); rust_function(); rust_cb(global_ctx); } 

生锈位:

 extern crate libc; pub trait Foo { fn callback(&self); } extern { fn c_function(context: *mut libc::c_void); } pub struct MyFoo; impl Foo for MyFoo { fn callback(&self) { println!("callback on trait"); } } #[no_mangle] pub extern fn rust_cb(context: *mut Foo) { unsafe { let cb:Box = Box::from_raw(context); cb.callback(); } } #[no_mangle] pub extern fn rust_function() { println!("Called rust_function"); let tmp = Box::new(MyFoo); unsafe { c_function(Box::into_raw(tmp) as *const Foo as *mut libc::c_void); } } 

问题:

  • 当我尝试在“rust_cb”中对trait对象调用“callback”时,我的程序会出现段错误

一种解决方案: – 将“rust_cb”的function签名更改为

 pub extern fn rust_cb(context: *mut MyFoo) 

但这不是我想要的,因为我正在尝试创建一个只知道监听器特征的安全包装器

任何帮助赞赏

PS:我的假设是它是segfaults,因为编译器不知道特征Foo上的回调偏移量,它需要实际的对象来确定它的位置。 但后来我不知道如何解决这个问题

所以,如果你需要将Foo表示为void * ,你可以使用:

 extern crate libc; pub trait Foo { fn callback(&self); } extern { fn c_function(context: *mut libc::c_void); } pub struct MyFoo; impl Foo for MyFoo { fn callback(&self) { println!("callback on trait"); } } #[no_mangle] pub extern fn rust_cb(context: *mut Box) { unsafe { let cb: Box> = Box::from_raw(context); cb.callback(); } } #[no_mangle] pub extern fn rust_function() { println!("Called rust_function"); let tmp: Box> = Box::new(Box::new(MyFoo)); unsafe { c_function(Box::into_raw(tmp) as *mut Box as *mut libc::c_void); } } 

我想你可能误解了特质对象是什么。 特征对象是两个指针大小的类型(因此,64位系统上为128位)。 在此示例中, Foo不是特征对象,它是动态大小的类型(即具有可变大小的类型,例如str )。 Box是一个特质对象。 Box>既不是特征对象也不是动态大小的类型,它是一个与指针大小相同的类型,这就是为什么我们需要在这里使用它,因为我们想将它转换为void *

我将其称为“泄漏”,因为当您调用Box::into_raw ,您正在泄漏框中所有内容的内存,这意味着您负责确保调用析构函数( Drop实现)。

诸如Box类的Rust特征对象的大小是普通指针的两倍,因此您无法使用void *来表示它们。 有关更多信息,请参阅std::raw::TraitObject 。 这是您的代码的工作版本:

program.c:

 #include  #include  struct rs_trait_obj { void *data; void *vtable; }; struct rs_trait_obj global_ctx; void c_function(struct rs_trait_obj ctx) { printf("Called c_function\n"); global_ctx = ctx; } int main(void) { void *thing = dlopen("thing/target/debug/libthing.dylib", RTLD_NOW | RTLD_GLOBAL); if (!thing) { printf("error: %s\n", dlerror()); return 1; } void (*rust_function)(void) = dlsym(thing, "rust_function"); void (*rust_cb)(struct rs_trait_obj) = dlsym(thing, "rust_cb"); printf("rust_function = %p\n", rust_function); rust_function(); rust_cb(global_ctx); } 

lib.rs:

 #![feature(raw)] extern crate libc; use std::raw::TraitObject; use std::mem; pub trait Foo { fn callback(&self); } extern { fn c_function(context: TraitObject); } pub struct MyFoo; impl Foo for MyFoo { fn callback(&self) { println!("callback on trait"); } } #[no_mangle] pub extern fn rust_cb(context: TraitObject) { unsafe { let cb: Box = mem::transmute(context); cb.callback(); } } #[no_mangle] pub extern fn rust_function() { println!("Called rust_function"); let tmp: Box = Box::new(MyFoo); unsafe { c_function(mem::transmute(tmp)); } } 

这只适用于夜间rustc(因为#![feature(raw)] ),并且还会发出警告,因为TraitObject不是FFI安全的。 如果你想要一些适用于stable的东西,你可以定义一些像这样的合适大小的结构,并使用它而不是TraitObject

 #[repr(C)] struct FFITraitObject { data: usize, vtable: usize, } 

当然,另一种选择只是使用Box代替TraitObject ,但是你仍然会收到警告:

 extern crate libc; pub trait Foo { fn callback(&self); } extern { fn c_function(context: Box); } pub struct MyFoo; impl Foo for MyFoo { fn callback(&self) { println!("callback on trait"); } } #[no_mangle] pub extern fn rust_cb(context: Box) { context.callback(); } #[no_mangle] pub extern fn rust_function() { println!("Called rust_function"); let tmp: Box = Box::new(MyFoo); unsafe { c_function(tmp); } } 

如果你真的想使用void * ,你可以考虑泄漏TraitObject以及MyFoo并使用两个级别的间接。