Okay, I'm trying to achieve the following:
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:
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
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With