【问题标题】:Rust FFI passing trait object as context to call callbacks onRust FFI 将 trait 对象作为上下文来调用回调
【发布时间】:2016-02-28 23:38:31
【问题描述】:

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

  1. C 调用 rust
  2. rust 回调到 c 并在用户定义的 trait 对象上注册回调
  3. c 在上下文中调用 rust
  4. rust 在上下文(特征对象)上调用回调

我一直在玩它。我已经走了很远,但还没有到达那里。

C位:

#include <dlfcn.h>
#include <stdio.h>

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<Foo> = 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”中的特征对象调用“回调”时,我的程序出现段错误

一个解决方案: - 将“rust_cb”的函数签名更改为

pub extern fn rust_cb(context: *mut MyFoo)

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

任何帮助表示赞赏

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

【问题讨论】:

    标签: c rust ffi


    【解决方案1】:

    Box&lt;Foo&gt; 等 Rust 特征对象的大小是普通指针的两倍,因此您不能使用 void * 来表示它们。请参阅std::raw::TraitObject 了解更多信息。这是您的代码的工作版本:

    program.c:

    #include <dlfcn.h>
    #include <stdio.h>
    
    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<Foo> = mem::transmute(context);
            cb.callback();
        }
    }
    
    #[no_mangle]
    pub extern fn rust_function() {
        println!("Called rust_function");
        let tmp: Box<Foo> = Box::new(MyFoo);
        unsafe {
            c_function(mem::transmute(tmp));
        }
    }
    

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

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

    当然,另一种选择是使用Box&lt;Foo&gt; 代替TraitObject,但您仍然会收到警告:

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

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

    【讨论】:

    • 谢谢!我不太明白如何让它与 c_void 一起工作以及如何“泄漏” TraitObject。我不能真的/不想改变 C 端 - 并且想尽可能少地使用开销/间接。也许我想要的并不容易实现。 (PS:我可以将额外的参数传递给真正的 C 函数,所以也许我可以将类型传递给 rust?)
    【解决方案2】:

    所以,如果你需要将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<Foo>) {
        unsafe {
            let cb: Box<Box<Foo>> = Box::from_raw(context);
            cb.callback();
        }
    }
    
    #[no_mangle]
    pub extern fn rust_function() {
        println!("Called rust_function");
        let tmp: Box<Box<Foo>> = Box::new(Box::new(MyFoo));
        unsafe {
            c_function(Box::into_raw(tmp) as *mut Box<Foo> as *mut libc::c_void);
        }
    }
    

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

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

    【讨论】:

    • 太棒了,谢谢!我想我现在明白了。不知道为什么,但我发现很难理解所有来自 C 的东西:(
    • rust_function 不一定是 pub extern,不是吗?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-05-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-05-10
    相关资源
    最近更新 更多