Работа с c_void в FFI

Я изо всех сил пытаюсь передать структуру через FFI, который принимает void, и читать ее на другом конце.

Речь идет о libtsm, конечном автомате. Он позволяет вводить ввод, а затем выяснять, в каком состоянии будет находиться терминал после ввода.

Он объявляет свою функцию рисования как:

pub fn tsm_screen_draw(con: *tsm_screen, draw_cb: tsm_screen_draw_cb, data: *mut c_void) -> tsm_age_t;

где tsm_screen_draw_cb - это обратный вызов, который должен быть реализован пользователем библиотеки, с подписью:

pub type tsm_screen_draw_cb = extern "C" fn(
  con: *tsm_screen,
  id: u32,
  ch: *const uint32_t,
  len: size_t,
  width: uint,
  posx: uint,
  posy: uint,
  attr: *tsm_screen_attr,
  age: tsm_age_t,
  data: *mut c_void
);

Важной частью здесь является параметр data. Он позволяет пользователю передавать указатель на самореализованное состояние, манипулировать им и использовать его после рисования. Учитывая простую структуру:

struct State {
  state: int
}

как бы я сделал это правильно? Я не уверен, как правильно преобразовать указатель на структуру в void и обратно.


person Skade    schedule 12.06.2014    source источник


Ответы (1)


Вы не можете преобразовать struct в c_void, но вы можете преобразовать ссылку на структуру в *mut c_void и обратно, используя некоторые преобразования указателя:

fn my_callback(con: *tsm_screen, ..., data: *mut c_void) {
    // unsafe is needed because we dereference a raw pointer here
    let data: &mut State = unsafe { &mut *(data as *mut State) };
    println!("state: {}", data.state);
    state.x = 10;
}

// ...

let mut state = State { state: 20 };
let state_ptr: *mut c_void = &mut state as *mut _ as *mut c_void;
tsm_screen_draw(con, my_callback, state_ptr);

Также можно использовать функцию std::mem::transmute() для приведения типов между указателями, но это гораздо более мощный инструмент, чем здесь действительно нужен, и его следует избегать, когда это возможно.

Обратите внимание, что вы должны быть очень осторожны, возвращая небезопасный указатель к ссылке. Если tsm_screen_draw вызывает свой обратный вызов в другом потоке или сохраняет его в глобальной переменной, а затем его вызывает другая функция, тогда state локальная переменная вполне может выйти за пределы области видимости при вызове обратного вызова, что приведет к сбою вашей программы.

person Vladimir Matveev    schedule 12.06.2014
comment
Я рекомендую избегать transmute: он очень мощный, и поэтому легко случайно преобразовать что-то неправильно. Вы можете обрабатывать указатели напрямую с помощью as, например. let data = &mut *(data as *mut State); и let state_ptr = &mut state as *mut _ as *mut c_void;. (Особенно написание &mut *..., поскольку это гарантирует, что вы обрабатываете указатель, в отличие от transmute.) - person huon; 13.06.2014
comment
@dbaupp, я был уверен, что приведение указателей на разные типы друг к другу невозможно. Спасибо за исправление. - person Vladimir Matveev; 13.06.2014
comment
Хм, это дальше, чем я был, но это для меня segfault, когда я пытаюсь получить доступ к элементу данных. - person Skade; 13.06.2014
comment
@Skade, очень вероятно, что state уже вышел за рамки и был уничтожен. Еще раз проверьте, что ваш обратный вызов вызывается только тогда, когда state еще жив. - person Vladimir Matveev; 14.06.2014
comment
Хм, не знаю зачем, пользуюсь сразу после звонка. FWIW, я поместил код здесь: github.com/skade /rust-terminal/blob/master/src/main.rs. Он должен (tm) собрать с помощью простой команды make lib && make exe, запустить его с помощью cat test / reference_input | заставить работать. - person Skade; 17.06.2014
comment
@Skade, извините, но ваша проблема, похоже, в другом. На моей машине программа выходит из строя где-то внутри Screen::open() или screen.resize(), она даже не входит в цикл. - person Vladimir Matveev; 18.06.2014
comment
@VladimirMatveev Я это тоже заметил, но только на последней версии ржавчины. В этом случае мне кажется, что это ошибка компилятора :(. Сначала рассмотрим это. - person Skade; 19.06.2014
comment
@VladimirMatveev Исправил программу на недавний мастер, теперь она вылетает в функции рисования. - person Skade; 01.07.2014
comment
Исправлено: в функции рисования отсутствовал параметр, что, очевидно, приводило к считыванию неправильной позиции памяти. Спасибо за помощь! - person Skade; 01.07.2014