分配要传递给FFI调用的数据的正确方法是什么?



在讨论/学习了从Rust调用windows api的FFI的正确方法后,我进一步使用了它,并想再次检查我的理解。

我有一个被调用两次的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 = "*"

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

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

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

使用Vec作为一种简单的分配机制是常见的做法,但是当您这样使用它时需要小心。

  1. 确保您的单位是正确的-"长度"参数是项的数量还是字节的数量?
  2. 如果您将Vec溶解为组成部分,则需要
    1. 跟踪长度容量。有些人使用shrink_to_fit来确保这两个值相同。
    2. 避免交叉流——内存是由Rust分配的,必须由Rust释放。将其转换回Vec以被丢弃。
  • 注意,空的Vec有NULL指针!:

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

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

    此外,您可以只使用reserve空间而不是extend来填充Vec

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

    最新更新