Rust FFI passing trait object as context to call c

2019-02-18 14:01发布

问题:

Okay, I'm trying to achieve the following:

  1. C calls into rust
  2. rust calls back into c and registers a callback on a user defined trait object
  3. c calls into rust with the context
  4. rust calls the callback on the context (trait object)

I've been playing around with it quite a bit. I got quite far, but still not quite there.

The C bit:

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

The rust bit:

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

The issue:

  • My program segfaults when I try to call "callback" on the trait object in "rust_cb"

One Solution: - Change the function signature of "rust_cb" to

pub extern fn rust_cb(context: *mut MyFoo)

but that's not what I want, as I'm trying to create a safe wrapper that only knows the trait of the listener

Any help appreciated

PS: my assumption is that it segfaults, because the compiler doesn't know the offset of callback on the trait Foo, it needs the actual object to determine where it is. but then i have no idea how to work around that

回答1:

So, if you need to represent the Foo as a void *, you can use this:

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

I think you may be misunderstanding what a trait object is. A trait object is a type that is the size of two pointers (so, 128 bits on a 64-bit system). In this example, Foo is not a trait object, it is a dynamically sized type (i.e. a type which has a variable size, such as str). Box<Foo> is a trait object. Box<Box<Foo>> is neither a trait object or a dynamically sized type, it is a type that has the same size as a pointer, which is why we need to use it here since we want to convert it into a void *.

I call it "leaking" because when you call Box::into_raw, you are leaking the memory of whatever is in the box, which means that that you are responsible for making sure the destructor (the Drop implementation) gets called.



回答2:

Rust trait objects such as Box<Foo> are double the size of a normal pointer, so you can't use void * to represent them. See std::raw::TraitObject for more information. Here is a working version of your code:

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

This will only work on nightly rustc (because of the #![feature(raw)]) and will also give a warning because TraitObject isn't FFI-safe. If you want something that will work on stable, you can define some struct of an appropriate size like this and use it instead of TraitObject:

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

Another option, of course, would just be to use Box<Foo> in place of TraitObject, but then you would still get a warning:

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

If you really want to use a void *, you could consider leaking the TraitObject as well as the MyFoo and use two levels of indirection.



标签: c rust ffi