Rust FFI передает объект черты в качестве контекста для вызова обратных вызовов на

Хорошо, я пытаюсь добиться следующего:

  1. С вызывает ржавчину
  2. rust перезванивает в c и регистрирует обратный вызов для заданного пользователем объекта черты
  3. c вызывает ржавчину с контекстом
  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);
}

Немного ржавчины:

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

Проблема:

  • Моя программа не работает, когда я пытаюсь вызвать "обратный вызов" для объекта черты в "rust_cb"

Одно из решений: - Измените сигнатуру функции "rust_cb" на

pub extern fn rust_cb(context: *mut MyFoo)

но это не то, что я хочу, так как я пытаюсь создать безопасную оболочку, которая знает только черты слушателя

Любая помощь приветствуется

PS: я предполагаю, что это segfaults, потому что компилятор не знает смещения обратного вызова на признаке Foo, ему нужен фактический объект, чтобы определить, где он находится. но тогда я понятия не имею, как обойти это

2 ответа

Решение

Итак, если вам нужно представить 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);
    }
}

Я думаю, что вы, возможно, неправильно понимаете, что такое черта объекта. Объект признака - это тип, размер которого равен двум указателям (например, 128 бит в 64-битной системе). В этом примере Foo это не объект признака, это тип с динамическим размером (т. е. тип с переменным размером, такой как str). Box<Foo> это черта объекта. Box<Box<Foo>> не является ни объектом trait, ни типом динамического размера, это тип, который имеет тот же размер, что и указатель, поэтому мы должны использовать его здесь, так как мы хотим преобразовать его в void *,

Я называю это "утечка", потому что, когда вы звоните Box::into_rawвы теряете память о том, что находится в коробке, что означает, что вы несете ответственность за то, чтобы убедиться, что деструктор (Drop реализация) вызывается.

Ржавчина черта объектов, таких как Box<Foo> в два раза больше нормального указателя, поэтому вы не можете использовать 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));
    }
}

Это будет работать только на ночных рустах (из-за #![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 и использовать два уровня косвенности.

Другие вопросы по тегам