Come posso convertire una chiusura di rust in una callback in stile C?

Sto cercando di scrivere un wrapper Rusty per un pezzo di API C. C’è un costrutto C con cui lotto:

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

La funzione fa il suo lavoro per un intervallo di numeri, a meno che l’ascoltatore non restituisca false. In tal caso interrompe il calcolo. Voglio avere un wrapper Rust come questo:

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

rust-bindgen creato questo per me, leggermente modificato per chiarezza:

 pub type listener_t = Option c_bool>; pub fn TCOD_line(xFrom: c_int, yFrom: c_int, xTo: c_int, yTo: c_int, listener: listener_t) -> c_bool; 

Come dovrei convertire una chiusura o un riferimento di tratto ad un callback in stile C nelle mie funzioni do_with :

 pub fn do_with_callback(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)) }; } 

Non è ansible farlo a meno che l’API C non consenta il passaggio di un parametro di callback fornito dall’utente. In caso contrario, è ansible utilizzare solo le funzioni statiche.

La ragione è che le chiusure non sono “solo” funzioni. Come suggerisce il loro nome, le chiusure “chiudono” le variabili dal loro ambito lessicale. Ogni chiusura ha una porzione di dati associata che contiene i valori delle variabili acquisite (se viene utilizzata la parola chiave move ) o i riferimenti ad esse. Questi dati possono essere considerati come una struct anonima anonima.

Il compilatore aggiunge automaticamente un’implementazione dei tratti Fn* corrispondenti per queste strutture anonime. Come puoi vedere , i metodi su questi tratti accettano self oltre agli argomenti di chiusura. In questo contesto, il self è la struct su cui viene implementato il tratto. Ciò significa che ogni funzione che corrisponde a una chiusura ha anche un parametro aggiuntivo che contiene l’ambiente di chiusura.

Se la tua API C ti consente solo di passare funzioni senza parametri definiti dall’utente, non puoi scrivere un wrapper che ti permetta di usare chiusure. Immagino che potrebbe essere ansible scrivere un supporto globale per l’ambiente delle chiusure, ma dubito che sarebbe facile e sicuro.

Se l’API C consente di passare un argomento definito dall’utente, è ansible eseguire ciò che si desidera con gli oggetti tratto:

 extern crate libc; use std::mem; use libc::{c_int, c_void}; extern "C" { fn do_something(f: Option 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(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 } } 

do_something solo se do_something non memorizza il puntatore sul callback da qualche parte. Se lo fa, devi usare una Box ..> object tratto e farla fuoriuscire dopo averla passata alla funzione. Quindi, se ansible, dovrebbe essere recuperato dalla libreria C e smaltito. Potrebbe assomigliare a questo:

 extern crate libc; use std::mem; use libc::{c_int, c_void}; extern "C" { fn set_handler(f: Option 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 bool> = unsafe { mem::transmute(arg) }; closure(x as i32) as c_int } pub fn set_callback(callback: F) where F: FnMut(i32) -> bool, F: 'static { let cb: Box 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 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 doppia indirezione (cioè Box> ) è necessaria perché Box ..> è un object tratto e quindi un puntatore grasso, incompatibile con *mut c_void causa di dimensioni diverse.

In C, un puntatore a funzione non ha un contesto associato, ed è per questo che di solito una funzione di callback C di solito porta un argomento extra void* che passa il contesto …

 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 avere un’API da lasciare per memorizzare i dati dell’utente …

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

Se la libreria che desideri racchiudere non fornisce nulla di quanto sopra, dovrai passare il contesto tramite altri canali, ad esempio tramite una variabile globale, anche se tale contesto sarà condiviso nell'intero processo:

 lazy_static! { static ref REAL_CALLBACK: Mutex 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!(""); } } fn main() { *REAL_CALLBACK.lock().unwrap() = Some(Box::new(move |x, y| { println!("..."); true })); unsafe { do_it(callback); } } 

È anche ansible creare una funzione trampolino per attaccare il contesto direttamente nella funzione, ma è estremamente difficile e non sicuro.

Rispondi manualmente migrato da https://stackoverflow.com/a/42597209/224671

Il primo frammento di Vladimir Matveev non funziona più come scritto. La dimensione di &mut FnMut(i32) -> bool e *mut c_void è diversa e tali *mut c_void portano ad un crash. Esempio corretto ( box ):

 extern crate libc; use std::mem::*; use libc::c_void; pub fn run(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 })); }