códigomediano
Bien, estoy tratando de lograr lo siguiente:
- C llama al óxido
- rust vuelve a llamar a c y registra una devolución de llamada en un objeto de rasgo definido por el usuario
- c llama a rust con el contexto
- rust llama a la devolución de llamada en el contexto (objeto de rasgo)
He estado jugando un poco con eso. Llegué bastante lejos, pero todavía no del todo.
El bit 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);
}
El pedacito de óxido:
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);
}
}
La cuestión:
- Mi programa falla cuando intento llamar a “devolución de llamada” en el objeto de rasgo en “rust_cb”
Una solución: – Cambie la firma de función de “rust_cb” a
pub extern fn rust_cb(context: *mut MyFoo)
pero eso no es lo que quiero, ya que estoy tratando de crear un envoltorio seguro que solo conozca el rasgo del oyente
Cualquier ayuda apreciada
PD: mi suposición es que falla, porque el compilador no conoce el desplazamiento de la devolución de llamada en el rasgo Foo, necesita el objeto real para determinar dónde está. pero entonces no tengo idea de cómo solucionar eso
Entonces, si necesita representar el Foo
como un void *
puedes usar esto:
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);
}
}
Creo que puede estar malinterpretando qué es un objeto de rasgo. Un objeto de rasgo es un tipo que tiene el tamaño de dos punteros (es decir, 128 bits en un sistema de 64 bits). En este ejemplo, Foo
no es un objeto de rasgo, es un tipo de tamaño dinámico (es decir, un tipo que tiene un tamaño variable, como str
). Box<Foo>
es un objeto de rasgo. Box<Box<Foo>>
no es un objeto de rasgo ni un tipo de tamaño dinámico, es un tipo que tiene el mismo tamaño que un puntero, por lo que necesitamos usarlo aquí, ya que queremos convertirlo en un void *
.
Yo lo llamo “fuga” porque cuando llamas Box::into_raw
está perdiendo la memoria de lo que sea que haya en la caja, lo que significa que usted es responsable de asegurarse de que el destructor (el Drop
implementación) es llamado.
-
¡Increíble gracias! Creo que ahora lo entiendo. No sé por qué, pero me resulta bastante difícil entender todas esas cosas que vienen de C 🙁
– codemedian
26 de noviembre de 2015 a las 20:51
-
rust_function
no tiene que ser pub externo, ¿o sí?– Beni
16 dic 2019 a las 22:50
Adrián
Objetos de rasgo de óxido como Box<Foo>
tienen el doble del tamaño de un puntero normal, por lo que no puede usar void *
para representarlos. Ver std::raw::TraitObject
para más información. Aquí hay una versión funcional de su código:
programa.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));
}
}
Esto solo funcionará en nighty rustc (debido a la #![feature(raw)]
) y también dará una advertencia porque TraitObject
no es seguro para FFI. Si quiere algo que funcione en estable, puede definir alguna estructura de un tamaño apropiado como esta y usarla en lugar de TraitObject
:
#[repr(C)]
struct FFITraitObject {
data: usize,
vtable: usize,
}
Otra opción, por supuesto, sería usar Box<Foo>
en lugar de TraitObject
pero aún recibiría una advertencia:
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);
}
}
Si realmente quieres usar un void *
podría considerar filtrar el TraitObject
así como el MyFoo
y utilizar dos niveles de direccionamiento indirecto.
-
¡Gracias! No entiendo muy bien cómo hacer que funcione con c_void y cómo “filtrar” el TraitObject. Realmente no puedo/no quiero cambiar el lado C, y me gustaría usar la menor sobrecarga/indirección posible. Tal vez lo que quiero no es fácilmente alcanzable. (PD: puedo pasar argumentos adicionales a la función C real, ¿así que tal vez pueda pasar el tipo a rust?)
– codemedian
26 de noviembre de 2015 a las 11:04