【问题标题】:What is proper Rust way to allocate opaque buffer for external C library?为外部 C 库分配不透明缓冲区的正确 Rust 方法是什么?
【发布时间】:2020-02-02 11:40:04
【问题描述】:

我有一个外部库(例如 libcisland.so),其界面如下:

size_t lib_handle_size();
typedef void* handle;
int lib_init(handle h);
int lib_store(handle h, int value);
int lib_restore(handle h, int *pvalue);

这个库的用户应该做以下事情:

// allocate some buffer in client address space
handle h = malloc(lib_handle_size());
// pass this buffer to library for initialization
if (lib_init(h)) { /* handle errors */ }
// library initializes this handle by some opaque fashion
// then user uses it
lib_store(h,42);
int r;
lib_restore(h,&r);
// after all work is done, user frees this handle
free(h);

我不知道如何正确地将这个接口包装到 Rust。 这就是我最终的结果:

pub struct Island {
    h: Handle,
    v: Vec<u8>,
}

impl Island {
    pub fn new() -> Island {
        let len = unsafe { lib_handle_size() };
        let mut v: Vec<u8> = Vec::with_capacity(len);
        let h: Handle = v.as_mut_ptr();
        Island { v:v, h:h, }
    }

    pub fn store(&mut self, v: i32) {
        unsafe { lib_store(self.h, v); }
    }

    pub fn restore(&mut self) -> i32 {
        let mut v = 0;
        unsafe { lib_restore(self.h, &mut v); }
        v
    }
}

impl Drop for Island {
    fn drop(&mut self) {
        drop(&mut self.v);
    }
}

/// unsafe part
use libc::size_t;
pub type Handle = *mut u8;
#[link(name="cisland")]
extern {
    pub fn lib_handle_size() -> size_t;
    pub fn lib_init(h: Handle) -> i32;
    pub fn lib_store(h: Handle, value: i32) -> i32;
    pub fn lib_restore(h: Handle, pvalue: &mut i32) -> i32;
}

可以为此目的使用Vec(u8) 吗?这个Drop trait 实现了吗?

【问题讨论】:

  • drop(&amp;mut self.v); 创建对v 的引用,然后删除该引用,什么也不做。 Drops 会自动递归调用,除非你需要做一些特殊的事情,否则你不需要写任何东西。

标签: c rust ffi


【解决方案1】:

是否可以将 Vec(u8) 用于此目的?

我认为Vec&lt;u8&gt; 可以,但是您应该初始化它而不是使用零长度向量,指向未初始化的内存。使用Box&lt;[u8]&gt; 也会更加健壮,因为这将强制它不会被意外重新分配。

这个 Drop trait 实现了吗?

根本不需要实现DropIsland 的字段无论如何都会正确删除。

我不会存储句柄,而是每次使用一种方法来获取它。那么你的结构就简单多了。

use libc::c_void;

pub struct Island {
    buf: Box<[u8]>,
}

impl Island {
    pub fn new() -> Island {
        let len = unsafe { lib_handle_size() };
        let v: Vec<u8> = vec![0; len];
        Island { buf: v.into_boxed_slice() }
    }

    pub fn store(&mut self, v: i32) {
        unsafe { lib_store(self.handle_mut(), v); }
    }

    pub fn restore(&mut self) -> i32 {
        let mut v = 0;
        unsafe { lib_restore(self.handle_mut(), &mut v); }
        v
    }

    fn handle_mut(&mut self) -> *mut c_void {
        self.buf.as_mut_ptr() as *mut c_void
    }
}

您不需要Drop 实现,因为Box 会在超出范围时自动删除(Vec 也是如此)。

【讨论】:

    【解决方案2】:

    可以为此目的使用Vec(u8)吗?

    向量并不意味着这样使用,即使您的代码应该可以工作,这也不是一个好方法。

    要正确地做到这一点,你需要一个实验特性(这个相当稳定),你需要使用System 结构和Alloc trait。不幸的是,您的库对其句柄没有任何对齐要求,因此我们必须使用1

    pub type Handle = *mut u8; 根据您的typedef void* handle; 不正确(顺便说一下隐藏指针不好)。应该是pub type Handle = *mut libc::c_void;

    #![feature(allocator_api)]
    use std::alloc::{Alloc, Layout, System};
    
    use std::ptr::NonNull;
    
    pub struct Island {
        handle: NonNull<u8>,
        layout: Layout,
    }
    
    impl Island {
        pub fn new() -> Island {
            let size = unsafe { lib_handle_size() };
            let layout = Layout::from_size_align(size, 1).unwrap();
            let handle = unsafe { System.alloc(layout).unwrap() };
            unsafe {
                // can have error I guess ?
                lib_init(handle.as_ptr() as Handle);
            }
            Self { handle, layout }
        }
    
        pub fn store(&mut self, v: i32) -> Result<(), ()> {
            unsafe {
                lib_store(self.handle.as_ptr() as Handle, v);
            }
            Ok(())
        }
    
        pub fn restore(&mut self, v: &mut i32) -> Result<(), ()> {
            unsafe {
                lib_restore(self.handle.as_ptr() as Handle, v);
            }
            Ok(())
        }
    }
    
    impl Drop for Island {
        fn drop(&mut self) {
            unsafe { System.dealloc(self.handle, self.layout) }
        }
    }
    
    /// unsafe part
    use libc::size_t;
    pub type Handle = *mut libc::c_void;
    #[link(name = "cisland")]
    extern "C" {
        pub fn lib_handle_size() -> size_t;
        pub fn lib_init(h: Handle) -> i32;
        pub fn lib_store(h: Handle, value: i32) -> i32;
        pub fn lib_restore(h: Handle, pvalue: &mut i32) -> i32;
    }
    

    我稍微更改了您的 store()restore() 函数以返回结果。我敢打赌你的 C​​ 函数也会这样做。

    【讨论】:

    • 国外图书馆的设计是我无法控制的。 Windows Cryptography NextGeneration 子系统 (CNG) 中使用了类似的技术。以BCryptCreateHash 函数为例。我需要通过调用BCryptGetProperty函数预先获取对象大小并在我身边分配适当的空间。
    • 我使用Vec&lt;u8&gt;(和C++ 中的std::vector&lt;uint8_t&gt;)作为通用分配函数,我认为这没有问题,特别是当Alloc trait 不稳定时。
    猜你喜欢
    • 1970-01-01
    • 2014-11-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-12-10
    • 2012-06-13
    • 1970-01-01
    • 2014-03-05
    相关资源
    最近更新 更多