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

时间:2015-11-26 01:11:10

标签: c rust ffi

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

  1. C召唤生锈
  2. rust回调到c并在用户定义的特征对象上注册回调
  3. c通过上下文调用生锈
  4. rust在上下文(trait对象)上调用回调
  5. 我一直在玩它。我走得很远,但还是不太好。

    C位:

    var a = [0,1,2,3,4,5,6,7,8,9];
    
    var select = (n) => {
        return a.reduce((memo, val, index) => {
            var p = (n-memo.length); // How many remaining to choose.
            var q = a.length-index; // How many remaining to choose from.
            if (Math.random() < p/q){
                memo.push(val);
            }
            return memo;
        }, []);
    };
    
    console.log(select(3));
    

    生锈位:

    #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);
    }
    

    问题:

    • 当我尝试拨打&#34;回拨&#34;我的程序会出现段错误。关于&#34; rust_cb&#34;
    • 中的特征对象

    一种解决方案:   - 更改&#34; rust_cb&#34;的功能签名到

    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);
        }
    }
    

    但这不是我想要的,因为我试图创建一个只知道听众特征的安全包装

    任何帮助表示赞赏

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

2 个答案:

答案 0 :(得分:4)

诸如Box<Foo>之类的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<Foo>代替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 *,可以考虑泄漏TraitObject以及MyFoo并使用两个间接级别。

答案 1 :(得分:3)

因此,如果您需要将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);
    }
}

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

我称之为&#34;泄漏&#34;因为当你调用void *时,你正在泄漏框中任何内容的内存,这意味着你有责任确保调用析构函数(Box::into_raw实现)。