rust closures ffi

rust - ¿Cómo convierto un cierre de óxido en una devolución de llamada de estilo C?



closures ffi (3)

Estoy tratando de escribir un contenedor Rusty para una pieza de C API. Hay una construcción C con la que lucho:

typedef bool (*listener_t) (int, int); bool do_it(int x1, int y1, int x2, int y2, listener_t listener)

La función hace su trabajo para un rango de números a menos que el oyente devuelva falso. En ese caso, aborta la computación. Quiero tener un contenedor de óxido como este:

fn do_with_callback<F>(start: (i32, i32), end: (i32, i32), callback: F) where F: Fn(i32, i32) -> bool

rust-bindgen creó esto para mí, ligeramente editado para mayor claridad:

pub type listener_t = Option<extern "C" fn(x: c_int, y: c_int) -> c_bool>; pub fn TCOD_line(xFrom: c_int, yFrom: c_int, xTo: c_int, yTo: c_int, listener: listener_t) -> c_bool;

¿Cómo debo convertir un cierre o una referencia de rasgo a una devolución de llamada de estilo C en mis funciones do_with :

pub fn do_with_callback<F>(start: (i32, i32), end: (i32, i32), callback: F) -> Self where F: Fn(i32, i32) -> bool { let wrapper = ???; unsafe { ffi::do_it(start.0, start.1, end.0, end.1, Some(wrapper)) }; }


En C, un puntero de función no tiene un contexto asociado, razón por la cual, por lo general, una función de devolución de llamada de C generalmente lleva un argumento void* adicional que pasa el contexto ...

typedef bool (*listener_t)(int, int, void* user_data); bool do_it(void* user_data, int x1, int y1, int x2, int y2, listener_t listener)

... o tener una API para permitir almacenar los datos del usuario ...

void api_set_user_data(void* user_data); // <-- caller set the context void* api_get_user_data(); // <-- callback use this to retrieve context.

Si la biblioteca que desea ajustar no proporciona ninguno de los anteriores, deberá pasar el contexto a través de otros canales, por ejemplo, a través de una variable global, aunque ese contexto se compartirá en todo el proceso:

lazy_static! { static ref REAL_CALLBACK: Mutex<Option<Box<FnMut(c_int, c_int) -> bool + Send>>> = Default::default(); } extern "C" fn callback(x: c_int, y: c_int) -> bool { if let Some(ref mut real_callback) = *REAL_CALLBACK.lock().unwrap() { real_callback(x, y) } else { panic!("<handle error here>"); } } fn main() { *REAL_CALLBACK.lock().unwrap() = Some(Box::new(move |x, y| { println!("..."); true })); unsafe { do_it(callback); } }

También es posible crear una función de trampolín para pegar el contexto directamente en la función, pero es extremadamente difícil e inseguro.

Respuesta migrada manualmente desde https://.com/a/42597209/224671


No puede hacerlo a menos que la API de C permita pasar un parámetro de devolución de llamada proporcionado por el usuario. Si no es así, solo puede usar funciones estáticas.

La razón es que los cierres no son funciones "solo". Como su nombre lo indica, los cierres "cierran" las variables de su ámbito léxico. Cada cierre tiene un dato asociado que contiene valores de variables capturadas (si se usa la palabra clave move ) o referencias a ellas. Estos datos pueden considerarse como una struct anónima sin nombre.

El compilador agrega automáticamente una implementación de los rasgos Fn* correspondientes para estas estructuras anónimas. Como puede ver , los métodos en estos rasgos se aceptan a self además de los argumentos de cierre. En este contexto, self es la struct en la que se implementa el rasgo. Esto significa que cada función que corresponde a un cierre también tiene un parámetro adicional que contiene el entorno de cierre.

Si su API de C solo le permite pasar funciones sin ningún parámetro definido por el usuario, no puede escribir un contenedor que le permita usar cierres. Supongo que es posible escribir un titular global para el entorno de los cierres, pero dudo que sea fácil y seguro.

Si su API C permite pasar un argumento definido por el usuario, entonces es posible hacer lo que quiera con los objetos de rasgo:

extern crate libc; use std::mem; use libc::{c_int, c_void}; extern "C" { fn do_something(f: Option<extern "C" fn(x: c_int, arg: *mut c_void) -> c_int>, arg: *mut c_void) -> c_int; } extern "C" fn do_something_handler(x: c_int, arg: *mut c_void) -> c_int { let closure: &mut &mut FnMut(i32) -> bool = unsafe { mem::transmute(arg) }; closure(x as i32) as c_int } pub fn do_with_callback<F>(x: i32, mut callback: F) -> bool where F: FnMut(i32) -> bool { // reason for double indirection is described below let mut cb: &mut FnMut(i32) -> bool = &mut callback; let cb = &mut cb; unsafe { do_something(Some(do_something_handler), cb as *mut _ as *mut c_void) > 0 } }

Esto solo funcionará si do_something no almacena el puntero a la devolución de llamada en alguna parte. Si lo hace, debe usar un objeto de rasgo Box<Fn(..) -> ..> y filtrarlo después de pasarlo a la función. Luego, si es posible, debe obtenerlo de su biblioteca de C y desecharlo. Podría verse así:

extern crate libc; use std::mem; use libc::{c_int, c_void}; extern "C" { fn set_handler(f: Option<extern "C" fn(x: c_int, arg: *mut c_void) -> c_int>, arg: *mut c_void); fn invoke_handler(x: c_int) -> c_int; fn unset_handler() -> *mut c_void; } extern "C" fn do_something_handler(x: c_int, arg: *mut c_void) -> c_int { let closure: &mut Box<FnMut(i32) -> bool> = unsafe { mem::transmute(arg) }; closure(x as i32) as c_int } pub fn set_callback<F>(callback: F) where F: FnMut(i32) -> bool, F: ''static { let cb: Box<Box<FnMut(i32) -> bool>> = Box::new(Box::new(callback)); unsafe { set_handler(Some(do_something_handler), Box::into_raw(cb) as *mut _); } } pub fn invoke_callback(x: i32) -> bool { unsafe { invoke_handler(x as c_int) > 0 } } pub fn unset_callback() { let ptr = unsafe { unset_handler() }; // drop the callback let _: Box<Box<FnMut(i32) -> bool>> = unsafe { Box::from_raw(ptr as *mut _) }; } fn main() { let mut y = 0; set_callback(move |x| { y += 1; x > y }); println!("First: {}", invoke_callback(2)); println!("Second: {}", invoke_callback(2)); unset_callback(); }

La doble indirección (es decir, Box<Box<...>> ) es necesaria porque Box<Fn(..) -> ..> es un objeto de rasgo y, por lo tanto, un puntero grueso, incompatible con *mut c_void debido a su diferente tamaño.


ya no funciona como está escrito. El tamaño de &mut FnMut(i32) -> bool y *mut c_void es diferente y estos lanzamientos conducen a un bloqueo. Ejemplo corregido ( playpen ):

extern crate libc; use std::mem::*; use libc::c_void; pub fn run<F>(mut callback: F) -> bool where F: FnMut(i32) -> bool { let mut cb: &mut FnMut(i32) -> bool = &mut callback; println!("sizeof(cb/*-ptr): {}/{}", size_of::<*mut FnMut(i32) -> bool>(), size_of::<*mut c_void>()); let ctx = &mut cb as *mut &mut FnMut(i32) -> bool as *mut c_void; println!("ctx: {:?}", ctx); //---------------------------------------------------------- // Convert backward let cb2: *mut *mut FnMut(i32) -> bool = unsafe { transmute(ctx) }; println!("cb2: {:?}", cb2); // this is more useful, but can''t be printed, because not implement Debug let closure: &mut &mut FnMut(i32) -> bool = unsafe { transmute(ctx) }; closure(0xDEAD) } fn main() { println!("answer: {}", run(|x| { println!("What can change nature of a man?"); x > 42 })); }