【问题标题】:How do I handle an FFI unsized type that could be owned or borrowed?我如何处理可以拥有或借用的 FFI 大小不一的类型?
【发布时间】:2017-07-25 14:59:38
【问题描述】:

c_strange_t 是一种不透明的 C 类型,只能在指针后面看到。在包装这种类型时,有时我们有责任使用c_free_strange_t(*c_strange_t)释放内存,而其他时候我们不负责释放数据,我们只负责准确控制生命周期。

如果这种类型可以映射到 Rust 中的 2 种类型,它们的工作方式类似于 strString,这将符合人体工程学,其中有 impl Deref<Target=str> for String。需要将借用的类型标记为仅在引用后有效。

这可能吗,怎么做?

【问题讨论】:

标签: rust ffi lifetime


【解决方案1】:

这似乎可行,但它确实需要使用一个小的 unsafe 块,因此您应该在 Miri 和 Valgrind 等普通工具下进行测试。这里的主要假设1c_void 不能正常构造。 #[repr(transparent)] 用于确保FooBorrowed 新类型与c_void 具有相同的内存布局。一切都应该以“只是一个指针”结束:

use std::{ffi::c_void, mem, ops::Deref};

#[repr(transparent)]
struct FooBorrowed(c_void);
struct FooOwned(*mut c_void);

fn fake_foo_new(v: u8) -> *mut c_void {
    println!("C new called");
    Box::into_raw(Box::new(v)) as *mut c_void
}

fn fake_foo_free(p: *mut c_void) {
    println!("C free called");
    let p = p as *mut u8;
    if !p.is_null() {
        unsafe { Box::from_raw(p) };
    }
}

fn fake_foo_value(p: *const c_void) -> u8 {
    println!("C value called");
    let p = p as *const u8;
    unsafe {
        p.as_ref().map_or(255, |p| *p)
    }
}

impl FooBorrowed {
    fn value(&self) -> u8 {
        fake_foo_value(&self.0)
    }
}

impl FooOwned {
    fn new(v: u8) -> FooOwned {
        FooOwned(fake_foo_new(v))
    }
}

impl Deref for FooOwned {
    type Target = FooBorrowed;

    fn deref(&self) -> &Self::Target {
        unsafe { mem::transmute(self.0) }
    }
}

impl Drop for FooOwned {
    fn drop(&mut self) {
        fake_foo_free(self.0)
    }
}

fn use_it(foo: &FooBorrowed) {
    println!("{}", foo.value())
}

fn main() {
    let f = FooOwned::new(42);
    use_it(&f);
}

如果 C 库真的给你一个指针,你需要做更多unsafe:

fn fake_foo_borrowed() -> *const c_void {
    println!("C borrow called");
    static VALUE_OWNED_ELSEWHERE: u8 = 99;
    &VALUE_OWNED_ELSEWHERE as *const u8 as *const c_void
}

impl FooBorrowed {
    unsafe fn new<'a>(p: *const c_void) -> &'a FooBorrowed {
        mem::transmute(p)
    }
}

fn main() {
    let f2 = unsafe { FooBorrowed::new(fake_foo_borrowed()) };
    use_it(f2);
}

如您所见,FooBorrowed::new 返回一个具有不受限制的生命周期的引用;这是相当危险的。在许多情况下,您可以构建更小的范围并使用提供生命周期的东西:

impl FooBorrowed {
    unsafe fn new<'a>(p: &'a *const c_void) -> &'a FooBorrowed {
        mem::transmute(*p)
    }
}

fn main() {
    let p = fake_foo_borrowed();
    let f2 = unsafe { FooBorrowed::new(&p) };
    use_it(f2);
}

这会阻止您在指针变量有效时使用引用,保证是真正的生命周期,但在许多情况下“足够接近”。太短不能太长更重要!


1 — 在未来的 Rust 版本中,您应该使用外部类型来创建有保证的不透明类型:

extern "C" {
    type my_opaque_t;
}

【讨论】:

    猜你喜欢
    • 2016-08-10
    • 2011-02-16
    • 1970-01-01
    • 2018-10-10
    • 2020-10-19
    • 2014-10-14
    相关资源
    最近更新 更多