2016-02-19 2 views
1

У меня есть состояние struct в Rust и C библиотека, которая примет и вызовет extern "C" fn.Захват переменной в обратном вызове C

fn get_callback(state: State) -> extern "C" fn ... { 
    extern "C" fn callback(args: &[Whatever]) -> Something { 
     // I need to use that state here 
    } 

    callback 
} 

Конечно, это не работает, потому что callback получает определенный вне get_callback, как и любой другой функции C.

Как я могу получить определенное состояние внутри обратного вызова? Мне нужно это, чтобы добавить обратные вызовы mruby в Rust и использование глобальной переменной для состояния нежелательно, поскольку каждое состояние mruby имеет свою собственную переменную.

ответ

1

Вы можете сделать это только в том случае, если ваш обратный вызов принимает некоторые аргументы «пользовательские данные», которые вводятся вызывающей стороной и устанавливаются, когда этот обратный вызов настроен, например. если у вас есть API, как это:

type Callback = extern fn(*mut c_void); 
extern { 
    fn register_callback(callback: Callback, user_data: *mut c_void); 
} 

Если это не выполняется для C API, то нет никакого способа сделать это без какого-либо глобального государства!

Вы можете обеспечить закрытие этой функции обратного вызова, как это:

fn register<F: FnMut() + 'static>(cb: F) { 
    extern fn internal_callback(user_data: *mut c_void) { 
     let callback = user_data as *mut Box<FnMut()>; 
     let callback = unsafe { &mut *callback }; 
     callback(); 
    } 

    let cb: Box<Box<FnMut()>> = Box::new(Box::new(cb)); 
    let cb_raw = Box::into_raw(cb) as *mut c_void; 

    unsafe { 
     register_callback(internal_callback, cb_raw); 
    } 
} 

Это, вероятно, имеет смысл использовать recover() для защиты от паники пересечения границы языка, но я опустил его для простоты.

У вышеуказанного API есть проблема, однако: он позволяет средам обратного вызова протекать. Я не вижу для него общего решения; это сильно зависит от вашего API.

Например, у вас может быть функция, которая использует только пройденный обратный вызов только во время ее выполнения, то есть нет необходимости хранить состояние обратного вызова в любом месте. Это может выглядеть следующим образом:

type Callback = extern fn(i32, *mut c_void); 
extern { 
    fn c_do_something(arg: i32, user_data: *mut c_void, callback: Callback); 
} 

Тогда он может быть использован, как это:

fn do_something<F: FnMut(i32)>(arg: i32, mut cb: F) { 
    extern fn internal_callback(arg: i32, user_data: *mut c_void) { 
     let callback = user_data as *mut &mut FnMut(i32); 
     let callback = unsafe { &mut *user_data }; 
     callback(arg); 
    } 
    let cb: &mut &mut FnMut = &mut &mut cb; 
    let cb_raw = cb as *mut _ as *mut c_void; 

    unsafe { 
     c_do_something(arg, cb_raw, internal_callback); 
    } 
} 

В обоих этих подходах мы создали функцию прокси обратного вызова, которая принимает аргумент пользовательских данных и интерпретирует его как указатель на среду закрытия. Кстати, возможно, можно сделать internal_callback() родовым и избежать создания объектов объектов из закрытий, но это не сильно меняет всю картину.

Если ваш C API больше похож на первый пример (регистрируя обратный вызов), вы можете, вероятно, сохранить свои обратные вызовы внутри некоторой структуры, передавая только указатели в эту структуру на сторону C. Затем вам нужно убедиться, что сторона C не вызовет эти обратные вызовы после того, как ваша структура будет удалена. После преобразования ссылок на ваши обратные вызовы на raw-указатели ссылка на ресурс с сайта использования обратного вызова на свое определение будет разорвана и вы становитесь ответственным за соблюдение ограничений продолжительности жизни. Это может быть хорошо, если ваши обратные вызовы вызываются какой-либо организацией, которая также имеет ограниченный срок службы на стороне C. Затем вы можете связать свою жизнь с целым временем существования закрытий структуры, тщательно разработав API.

И, наконец, в тяжелой ситуации, когда у вас есть глобальные обратные вызовы, которые могут часто меняться, ваш единственный выбор - выделить глобальное состояние для ваших обратных вызовов, по одному для каждого обратного вызова, которое будет хранить «текущий» обратный вызов. Затем вам нужны ваши обертки Rust для функций регистрации, которые заменяют старый обратный вызов на новый.Это может выглядеть так:

type Callback = extern fn(*mut c_void); 
extern { 
    fn register_callback(callback: Callback, user_data: *mut c_void); 
} 

fn register<F: FnMut() + 'static>(cb: F) { 
    extern fn internal_callback(user_data: *mut c_void) { 
     let callback = user_data as *mut Mutex<Option<Box<Box<FnMut()>>>>; 
     let callback = unsafe { &mut *callback }; 
     let callback = callback.lock(); 
     if let Some(callback) = callback.as_mut() { 
      callback(); 
     } 
    } 
    lazy_static! { 
     static ref CURRENT_CALLBACK: Mutex<Option<Box<Box<FnMut() + 'static>>>> = Mutex::new(None); 
    } 
    let cb: Box<Box<FnMut()>> = Box::new(Box::new(cb)); 
    // extract the old callback and destroy it, if needed 
    mem::replace(&mut *CURRENT_CALLBACK.lock(), Some(cb)); 
    let cb_raw = &mut *CURRENT_CALLBACK as *mut _; 

    unsafe { 
     register_callback(internal_callback, cb_raw); 
    } 
} 

Здесь внутренняя обертка несколько сложнее; он обеспечивает синхронизацию доступа к состоянию обратного вызова и позволяет только один обратный вызов настраиваться в любой момент времени, при необходимости очищая старый обратный вызов. Тем не менее, сторона C не должна использовать старый указатель обратного вызова, когда register_callback() вызывается с новым указателем, иначе ситуация сломается.

+0

Любые причины, по которым этот вопрос не является дубликатом одного [вы уже ответили] (http://stackoverflow.com/q/32270030/155423)? – Shepmaster

+0

@Shepmaster только, что я забыл, что был такой вопрос и ответ :) –

+0

Я помню * все * ;-) – Shepmaster

Смежные вопросы