Rust FFI passing trait object as context to call c

2019-02-18 13:44发布

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

标签: c rust ffi
2条回答
倾城 Initia
2楼-- · 2019-02-18 14:03

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.

查看更多
小情绪 Triste *
3楼-- · 2019-02-18 14:07

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.

查看更多
登录 后发表回答