Работа с 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)


Не можете да прехвърляте структура към 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
Хм, това е по-далеч от това, което бях, но този сегмент дава грешка за мен, когато се опитам да осъществя достъп до члена с данни. - 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, съжалявам, но проблемът ти изглежда е другаде. На моята машина програмата segfaults някъде в Screen::open() или screen.resize(), тя дори не влиза в цикъла. - person Vladimir Matveev; 18.06.2014
comment
@VladimirMatveev И аз го забелязах, но само на най-новата rust версия. В този случай ми изглежда като грешка в компилатора :(. Първо ще проуча това. - person Skade; 19.06.2014
comment
@VladimirMatveev Коригира програмата към скорошния главен, сега се срива във функцията за рисуване. - person Skade; 01.07.2014
comment
Поправено е: във функцията за чертане липсваше параметър, което очевидно води до прочитане на грешна позиция в паметта. Благодаря ти за помощта! - person Skade; 01.07.2014