使用c_void在FFI中工作

37

我正在努力通过一个接受void参数的FFI传递结构体,并在另一端读取它。

所涉及的库是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类型,然后再转回来。

1个回答

54

你不能将 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局部变量可能会在回调被调用时超出作用域,这将导致程序崩溃。


3
我建议避免使用transmute函数:它非常强大,而且很容易出现错误地转换某些东西的情况。你可以直接使用as处理指针,例如let data = &mut *(data as *mut State);let state_ptr = &mut state as *mut _ as *mut c_void;。(特别是编写&mut *...,因为那样可以保证你正在处理指针,而不像transmute那样不确定。) - huon
哦,这比我做过的更深入了,但是当我尝试访问数据成员时,它会导致段错误。 - Skade
@Skade,很可能state已经超出了作用域并被销毁。请仔细检查您的回调函数是否仅在state仍然存在时才被调用。 - Vladimir Matveev
@VladimirMatveev 修复了程序到最新的主分支,但现在它在“绘制”函数中崩溃。 - Skade
已修复:在绘制函数中缺少了一个参数,导致错误地读取了内存位置。感谢您的帮助! - Skade
显示剩余6条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接