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