如何处理可能是拥有或借用的FFI不定大小类型?

4

c_strange_t是一个不透明的C类型,只能通过指针来访问。在封装此类型时,有时我们需要使用c_free_strange_t(*c_strange_t)来释放内存,而其他时候,我们不需要负责释放数据,只需要准确控制生命周期。

如果可以将此类型映射为Rust中的2种类型,并以类似于strString的方式工作,则会更加人性化,其中borrowed type需要标记为仅在引用后有效。

这是否可能,并且如何实现呢?


我在IRC上询问了一下,答案似乎是:是的,这是可能的,但它会变得更好 https://github.com/rust-lang/rfcs/pull/1861 - derekdreery
1个回答

3
这似乎有效,但它需要使用一个小的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类型创建保证的不透明类型:

extern "C" {
    type my_opaque_t;
}

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