Rust FFI передает объект черты в качестве контекста для вызова обратных вызовов на
Хорошо, я пытаюсь добиться следующего:
- С вызывает ржавчину
- rust перезванивает в c и регистрирует обратный вызов для заданного пользователем объекта черты
- c вызывает ржавчину с контекстом
- 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
и использовать два уровня косвенности.