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.
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
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