首页 文章

分配数据以传递给FFI呼叫的正确方法是什么?

提问于
浏览
2

discussing/learning about the correct way to call a FFI of the Windows-API from Rust之后,我进一步玩了一下,想要仔细检查我的理解 .

我有一个被调用两次的Windows API . 在第一次调用中,它返回实际out参数所需的缓冲区大小 . 然后,使用足够大小的缓冲区第二次调用它 . 我目前正在使用 Vec 作为此缓冲区的数据类型(请参阅下面的示例) .

代码有效,但我想知道这是否是正确的方法,或者是否更好地利用像 alloc::heap::allocate 这样的函数直接保留一些内存,然后使用 transmute 将结果从FFI转换回来 . 我的代码再次起作用,但我试图在幕后看一点 .

extern crate advapi32;
extern crate winapi;
extern crate widestring;
use widestring::WideCString;
use std::io::Error as IOError;
use winapi::winnt;

fn main() {
    let mut lp_buffer: Vec<winnt::WCHAR> = Vec::new();
    let mut pcb_buffer: winapi::DWORD = 0;

    let rtrn_bool = unsafe {
        advapi32::GetUserNameW(lp_buffer.as_mut_ptr(),
                               &mut pcb_buffer )
    };

    if rtrn_bool == 0 {

        match IOError::last_os_error().raw_os_error() {
            Some(122) => {
                // Resizing the buffers sizes so that the data fits in after 2nd 
                lp_buffer.resize(pcb_buffer as usize, 0 as winnt::WCHAR);
            } // This error is to be expected
            Some(e) => panic!("Unknown OS error {}", e),
            None => panic!("That should not happen"),
        }
    }


    let rtrn_bool2 = unsafe {
        advapi32::GetUserNameW(lp_buffer.as_mut_ptr(), 
                               &mut pcb_buffer )
    };

    if rtrn_bool2 == 0 {
        match IOError::last_os_error().raw_os_error() {
            Some(e) => panic!("Unknown OS error {}", e),
            None => panic!("That should not happen"),
        }
    }

    let widestr: WideCString = unsafe { WideCString::from_ptr_str(lp_buffer.as_ptr()) };

    println!("The owner of the file is {:?}", widestr.to_string_lossy());
}

依赖关系:

[dependencies]
advapi32-sys = "0.2"
winapi = "0.2"
widestring = "*"

2 回答

  • 3

    理想情况下,您将使用std::alloc::alloc,因为您可以指定所需的对齐作为布局的一部分:

    pub unsafe fn alloc(layout: Layout) -> *mut u8
    

    主要的缺点是你需要知道对齐,即使你释放了分配 .

    通常的做法是使用 Vec 作为简单的分配机制,但在使用它时需要小心 .

    • 确保您的单位正确 - "length"参数是项目数还是字节数?

    • 如果将 Vec 溶解到组件中,则需要

    • 跟踪长度和容量 . 有些人使用shrink_to_fit来确保这两个值是相同的 .

    • 避免穿越流 - 内核由Rust分配,而 must 由Rust释放 . 将其转换回 Vec 即可删除 .

    • 注意空 Vec 确实 not 有一个NULL指针!:

    fn main() {
        let v: Vec<u8> = Vec::new();
        println!("{:p}", v.as_ptr());
        // => 0x1
    }
    

    对于您的具体情况,我可能建议使用 Veccapacity 而不是自己跟踪第二个变量 . 你会注意到你忘了在第一次调用后更新 pcb_buffer ,所以我很烦人,因为它需要是一个可变的引用,所以你不能完全摆脱它 .

    此外,您可以只使用reserve空间而不是 extend .

    也不能保证第一次通话期间所需的大小与第二次通话期间所需的大小相同 . 你可以做某种循环,但是你必须担心发生无限循环 .

  • 3

    这就是我提出的 .

    pub struct FfiObject {
        pub ptr: *mut u8,
        pub size: usize,
    }
    impl FfiObject {
        // allocate and zero memory
        pub fn new(size: usize) -> FfiObject {
            FfiObject::_from_vec(vec![0u8; size], size)
        }
        // allocate memory without zeroing
        pub fn new_uninitialized(size: usize) -> FfiObject {
            FfiObject::_from_vec(Vec::with_capacity(size), size)
        }
        fn _from_vec(mut v: Vec<u8>, size: usize) -> FfiObject {
            assert!(size > 0);
            let ptr = v.as_mut_ptr();
            std::mem::forget(v);
            FfiObject { ptr, size }
        }
    }
    impl Drop for FfiObject {
        fn drop(&mut self) {
            unsafe { std::mem::drop(Vec::from_raw_parts(self.ptr, 0, self.size)) };
        }
    }
    

    使用 u8 向量创建FFI对象,以便大小以字节为单位 . 这可以推广到使用任意类型而不是 u8 ,但要记住Shepmaster关于字节数和项数之间区别的警告 .

    这是使用 FfiObject 的示例:

    // Ask the library for the size.
    let mut size: usize = 0;
    let mut success = GetObjectSize(&mut size);
    if success && size > 0 {
        // allocate and zero memory for the object
        let ffi_obj = FfiObject::new(size);
        // Pass the memory to a foreign function
        success = DoSomethingWithObject(ffi_obj.ptr, &ffi_obj.size);
    

相关问题