2015-08-28 2 views
8

Ich versuche ein Rusty Wrapper für ein Stück C API zu schreiben. Es gibt ein C-Konstrukt, mit dem ich Schwierigkeiten habe:Wie konvertiere ich eine Rust-Schließung in einen C-Style Callback?

Die Funktion erledigt ihre Aufgabe für eine Reihe von Zahlen, es sei denn, der Listener gibt false zurück. In diesem Fall bricht es die Berechnung ab. Ich möchte ein Rust Wrapper wie diese haben:

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

rust-bindgen das für mich geschaffen, etwas für Klarheit bearbeitet:

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; 

Wie soll ich einen Verschluss oder ein Merkmal Bezugnahme auf eine C-Stil umwandeln Rückruf in meinen do_with Funktionen:

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

Wie funktioniert ein C-Client dieser Bibliothek Pass Anrufer spezifische Informationen? Dies scheint ein Beispiel für eine API zu sein, die einfach nicht dafür gedacht war. Vielleicht glauben die API-Autoren, dass es nicht benötigt wird und dass Sie alle Entscheidungen treffen können, die Sie benötigen, basierend auf '(x, y)'. – Shepmaster

+1

Nun, die C-Bibliothek ist nicht besonders gut gestaltet. Es beruht sehr auf 'statischem' und globalem Zustand. Und es versucht nicht einmal threadsicher zu sein. – Tomo

Antwort

14

Sie können es nicht tun, wenn die C-API erlaubt es einen Benutzer bereitgestellte Callback-Parameter. Ist dies nicht der Fall, können Sie nur statische Funktionen verwenden.

Der Grund ist, dass Verschlüsse nicht "nur" funktionieren. Wie ihr Name andeutet, "schließen" Schließungen Variablen aus ihrem lexikalischen Geltungsbereich. Jede Schließung hat eine zugeordnete Dateneinheit, die entweder Werte von erfassten Variablen enthält (wenn das Schlüsselwort move verwendet wird) oder Verweise darauf. Diese Daten können als einige unbenannte, anonyme struct angesehen werden.

Der Compiler fügt automatisch eine Implementierung der entsprechenden Fn* Merkmale für diese anonymen Strukturen. As you can see, akzeptieren Methoden zu diesen Merkmalen self zusätzlich zu den Abschlussargumenten. In diesem Zusammenhang ist self der struct, auf dem das Merkmal implementiert ist. Das bedeutet, dass jede Funktion, die einem Closure entspricht, auch einen zusätzlichen Parameter besitzt, der die Closure-Umgebung enthält.

Wenn Ihre C-API nur Funktionen ohne benutzerdefinierte Parameter übergeben kann, können Sie keinen Wrapper schreiben, mit dem Sie Closures verwenden können. Ich denke, es kann möglich sein, einige globale Halter für die Schließungen Umgebung zu schreiben, aber ich bezweifle, dass es einfach und sicher wäre.

Wenn Ihr C-API ein benutzerdefiniertes Argument übergeben, dann erlaubt ist es möglich, was Sie wollen, mit Zug zu tun Objekte:

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 
{ 
    let cb: &mut FnMut(i32) -> bool = &mut callback; 
    unsafe { do_something(Some(do_something_handler), cb as *mut _ as *mut c_void) > 0 } 
} 

Dies funktioniert nur, wenn do_something nicht den Zeiger auf die nicht speichert Rückruf irgendwo. Wenn dies der Fall ist, müssen Sie ein Merkmalsobjekt Box<Fn(..) -> ..> verwenden und es nach dem Übergeben an die Funktion auslaufen lassen. Dann sollte es nach Möglichkeit aus Ihrer C-Bibliothek zurückgeholt und entsorgt werden. Es könnte wie folgt aussehen:

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

Doppel indirection (das heißt Box<Box<...>>) notwendig ist, weil Box<Fn(..) -> ..> ein Merkmal Objekt ist und daher ein dicker Zeiger, unvereinbar mit *mut c_void wegen unterschiedlicher Größe.

+0

Ich verstehe. Leider erlaubt diese spezielle Funktion keine Benutzerdaten zu übergeben. Also, um es funktionieren zu lassen, müsste ich verlangen, dass Benutzer eine "externe" C "-Funktion als Parameter für meinen Rust-Wrapper bereitstellen? Oder gibt es eine Möglichkeit, ein Merkmal/ein Objekt zu benutzen? – Tomo

+0

Leider sehe ich keinen anderen Weg als die 'extern'-Funktion zu übergeben. Vielleicht könnte jemand anderes etwas vorschlagen, aber es ist unwahrscheinlich. –

+0

@VladimirMatveev Ich habe eine ähnliche Anforderung. In meinem Fall wird userdata an Callback übergeben, aber es wird mit einer Funktion wie folgt eingestellt -> 'bindings :: mosquitto_user_data_set'. Aus diesem Grund wird meine Schließung durch die Zeit, die Callback aufgerufen wird, zerstört und ich bekomme Müll auf geschlossenen Variablen. Können Sie die Antwort bitte mit einem Beispiel der von Ihnen vorgeschlagenen "Box" -Methode erweitern? – tez

1

The first snippet from Vladimir Matveev funktioniert nicht mehr wie geschrieben. Die Größe von und *mut c_void ist anders und solche Würfe führen zu einem Absturz.Korrigierte Beispiel (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 
      })); 
} 
+0

Beide Code-Schnipsel in der anderen Antwort kompilieren korrekt. Willst du sagen, dass es einen Absturz gibt, wenn das Programm ausgeführt wird? – Shepmaster

+0

Ja. Probieren Sie es einfach in play.rust-lang.org. Diese Seite meldet keinen Absturz, nur einige 'println'ed strings nicht gedruckt, die mich sagen, dass die Anwendung abstürzt. Schon die Tatsache, dass die unterschiedliche Menge '& mut' beim Erstellen des Zeigers verwendet wird und das Schließen des Index zurückgewonnen wird, muss sich schützen. – Mingun

2

In C ein Funktionszeiger nicht zugeordneten Kontext hat, weshalb in der Regel eine C Callback-Funktion in der Regel ein zusätzliches void* Argument den Kontext passieren tragen ...

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) 

... oder eine API haben, lassen Sie die Benutzerdaten zu speichern ...

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

Wenn die Bibliothek, die Sie umbrechen möchten, keine der oben genannten Funktionen bereitstellt, müssen Sie den Kontext über andere Kanäle übergeben, z. über eine globale Variable, obwohl wird dieser Kontext über den gesamten Prozess geteilt werden:

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

Es ist auch möglich, ein trampoline function zu kleben den Kontext direkt in der Funktion zu schaffen, aber es ist extrem schwierig und unsicher.

Antwort manuell migriert von https://stackoverflow.com/a/42597209/224671

Verwandte Themen