36

I am struggling with passing a struct through an FFI that accepts void and reading it back on the other end.

The library in question is libtsm, a terminal state machine. It allows you to feed input and then find out in which state a terminal would be after the input.

It declares its draw function as:

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

where tsm_screen_draw_cb is a callback to be implemented by the library user, with the signature:

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

The important part here is the data parameter. It allows the user to pass through a pointer to a self-implemented state, to manipulate it and use it after drawing. Given a simple struct:

struct State {
  state: int
}

how would I do that properly? I am unsure how to properly cast the pointer to the struct to void and back.

Skade
  • 1,406
  • 1
  • 11
  • 14

1 Answers1

51

You can't cast a struct to c_void, but you can cast a reference to the struct to *mut c_void and back using some pointer casts:

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

It is also possible to use std::mem::transmute() function to cast between pointers, but it is much more powerful tool than is really needed here and should be avoided when possible.

Note that you have to be extra careful casting an unsafe pointer back to a reference. If tsm_screen_draw calls its callback in another thread or stores it in a global variable and then another function calls it, then state local variable may well go out of scope when the callback is called, which will crash your program.

Vladimir Matveev
  • 120,085
  • 34
  • 287
  • 296
  • 3
    I recommend avoiding `transmute`: it is very powerful, and so easy to accidentally transmute something incorrectly. You can handle pointers directly with `as`, e.g. `let data = &mut *(data as *mut State);`, and `let state_ptr = &mut state as *mut _ as *mut c_void;`. (Especially writing `&mut *...`, since that guarantees you are handling a pointer, unlike `transmute`.) – huon Jun 12 '14 at 21:42
  • @dbaupp, I was sure that it is impossible to cast pointers to different types to each other. Thank you for the correction. – Vladimir Matveev Jun 12 '14 at 21:45
  • Hm, this is further than I've been, but this segfaults for me when I try to access the data member. – Skade Jun 13 '14 at 18:42
  • @Skade, it is very likely that `state` have already gone out of scope and was destroyed. Double-check that your callback is called only when `state` is still alive. – Vladimir Matveev Jun 13 '14 at 20:03
  • Hm, I don't know why it should, I am using it directly after the call. FWIW, I have put the code here: https://github.com/skade/rust-terminal/blob/master/src/main.rs. It should (tm) build with a simple "make lib && make exe" run it with "cat test/reference_input | make run". – Skade Jun 17 '14 at 15:24
  • @Skade, sorry, but your problem seems to be elsewhere. On my machine the program segfaults somewhere inside `Screen::open()` or `screen.resize()`, it does not even enter the loop. – Vladimir Matveev Jun 17 '14 at 20:18
  • @VladimirMatveev I noticed that as well, but only on the latest rust version. Seems like a compiler bug to me in that case :(. Will investigate that first. – Skade Jun 18 '14 at 20:48
  • @VladimirMatveev Fixed the program to the recent master, it now crashes within the "draw" function. – Skade Jun 30 '14 at 23:44
  • Fixed it: there was a parameter missing in the draw function, obviously leading to the wrong memory position being read. Thank you for your help! – Skade Jul 01 '14 at 10:58
  • I am new to Rust. What is `as *const _`? is underscore (`_`) a type? – Cardinal System Apr 06 '23 at 20:35
  • @CardinalSystem `_` in this context means asking the compiler to do type inference. When used as `&mut state as *mut _` it would mean "cast the `&mut state` reference to a raw pointer to the same type as the reference", so it is exactly equivalent to `&mut state as *mut State` but with the compiler figuring out `State` instead of you) – Vladimir Matveev Apr 09 '23 at 23:40