Rust FFI 将 trait 对象作为上下文来调用回调

cod*_*ian 5 c ffi rust

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

  1. C调用生锈
  2. rust 回调 c 并在用户定义的 trait 对象上注册回调
  3. c 使用上下文调用 Rust
  4. Rust 调用上下文(trait 对象)上的回调

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

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);
}
Run Code Online (Sandbox Code Playgroud)

锈点:

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

问题:

  • 当我尝试在“rust_cb”中的特征对象上调用“回调”时,我的程序出现段错误

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

pub extern fn rust_cb(context: *mut MyFoo)
Run Code Online (Sandbox Code Playgroud)

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

任何帮助表示赞赏

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

Adr*_*ian 5

因此,如果您需要将 表示Foo为 a 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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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