Как преобразовать замыкание Rust в обратный вызов в стиле C?

Я пытаюсь написать Rusty обертку для части C API. Есть одна конструкция C, с которой я борюсь:

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

Функция выполняет свою работу для диапазона чисел, если слушатель не возвращает false. В этом случае он прерывает вычисления. Я хочу иметь оболочку Rust, как это:

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

rust-bindgen создал это для меня, слегка отредактировал для наглядности:

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 в моем 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))
    };
}

3 ответа

Решение

Вы не можете сделать это, если C API не позволяет передать предоставленный пользователем параметр обратного вызова. Если это не так, вы можете использовать только статические функции.

Причина в том, что замыкания - это не просто функции. Как следует из их названия, замыкания "закрывают" переменные из своей лексической области видимости. Каждое замыкание имеет связанный фрагмент данных, который содержит либо значения захваченных переменных (если move ключевое слово) или ссылки на них. Эти данные могут рассматриваться как неназванные, анонимные struct,

Компилятор автоматически добавляет реализацию соответствующего Fn* черты этих анонимных структур. Как видите, методы по этим признакам self в дополнение к аргументам закрытия. В данном контексте, self это struct на котором реализована черта. Это означает, что каждая функция, которая соответствует замыканию, также имеет дополнительный параметр, который содержит среду замыкания.

Если ваш C API позволяет только передавать функции без каких-либо пользовательских параметров, вы не можете написать оболочку, которая позволила бы вам использовать замыкания. Я предполагаю, что возможно написать какого-то глобального держателя для среды замыканий, но я сомневаюсь, что это будет легко и безопасно.

Если ваш C API действительно позволяет передавать пользовательский аргумент, то вы можете делать то, что вы хотите, с объектами trait:

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

Это будет работать только если do_something не хранит указатель на обратный вызов где-то. Если это так, вам нужно использовать Box<Fn(..) -> ..> отследить объект и пропустить его после передачи функции. Затем, если возможно, он должен быть получен обратно из вашей библиотеки C и утилизирован. Это может выглядеть так:

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

Двойная косвенность (т.е. Box<Box<...>>) необходимо, потому что Box<Fn(..) -> ..> является чертой объекта и, следовательно, толстый указатель, несовместимый с *mut c_void из-за разного размера.

Vladimir Matveev больше не работает так, как написано. Размер &mut FnMut(i32) -> bool а также *mut c_void отличается, и такие броски приводят к краху. Исправленный пример ( манеж):

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

В C указатель на функцию не имеет ассоциированного контекста, поэтому обычно функция обратного вызова C обычно содержит дополнительный void* аргумент передать контекст...

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)

... или иметь API для хранения пользовательских данных...

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

Если библиотека, которую вы хотите обернуть, не предоставляет ничего из вышеперечисленного, вам нужно будет передать контекст через другие каналы, например, через глобальную переменную, хотя этот контекст будет общим для всего процесса:

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

Также возможно создать функцию батута, чтобы закрепить контекст непосредственно в функции, но это чрезвычайно сложно и небезопасно.

Ответ вручную перенесен с https://stackru.com/a/42597209/224671

Другие вопросы по тегам