分配数据以传递给 FFI 调用的正确方法是啥?

Posted

技术标签:

【中文标题】分配数据以传递给 FFI 调用的正确方法是啥?【英文标题】:What is the right way to allocate data to pass to an FFI call?分配数据以传递给 FFI 调用的正确方法是什么? 【发布时间】:2017-01-25 19:49:33 【问题描述】:

在discussing/learning about the correct way to call a FFI of the Windows-API from Rust 之后,我玩得更远了,想再次检查一下我的理解。

我有一个被调用两次的 Windows API。在第一次调用中,它返回实际输出参数所需的缓冲区大小。然后,使用足够大小的缓冲区再次调用它。我目前正在使用 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 = "*"

【问题讨论】:

切线相关:Allocating an object for C / FFI library calls. 【参考方案1】:

理想情况下,您应该使用std::alloc::alloc,因为您可以在布局中指定所需的对齐方式:

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

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

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

    确保您的单位正确 - “长度”参数是 items 的数量还是 bytes 的数量? 如果将Vec 分解为组件,则需要
      跟踪长度容量。有些人使用shrink_to_fit 来确保这两个值相同。 避免跨流 - 内存是由 Rust 分配的,必须由 Rust 释放。将其转换回 Vec 以被删除。

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

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

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

此外,您可以只使用reserve 空格,而不是extendVec

也不能保证第一次调用所需的尺寸与第二次调用所需的尺寸相同。你可以做一些循环,但是你不得不担心会发生无限循环。

【讨论】:

感谢您的回答。所以,我想我最好坚持使用 Vec 的稳定解决方案。我将 Vec 的内容更改为 winnt::CHAR 以便项目数和长度之间没有差异。我想这会让事情变得更容易。 我对你的回答有两个问题: - 你说我必须将内存转换回 Vec 以被删除是什么意思。 - 我不确定我可以使用容量而不是我的第二个变量,因为这个变量也作为输出参数传递给 FFI。 Windows API 将覆盖这个变量的内容,我想用我的 Vec 的容量来做这件事不是一个好主意。还是我在这方面误会了你? @Norbert 我澄清了一点。转换回 Vec 仅在您已分解 Vec 开始时;你的情况似乎没有。对于后者,我的意思是使用 capacity 的值而不是 0,但您必须有另一个变量作为参考传递。【参考方案2】:

这是我想出来的。

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)) ;
    

FFI 对象是使用u8 向量创建的,因此大小以字节为单位。这可以概括为使用任意类型而不是 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);

【讨论】:

以上是关于分配数据以传递给 FFI 调用的正确方法是啥?的主要内容,如果未能解决你的问题,请参考以下文章

模拟文件数据以测试表单的正确方法是啥?

节点 ffi napi,调用返回 true 但指针中没有结果

记录大数据以使用 Hadoop 组织和存储它并使用 Hive 查询它的正确方法是啥?

C语言规定,调用一个函数时,实参变量和形参变量之间的数据传递方式是啥

调用 std::to_chars 的正确方法是啥?

将多个分层数据模板分配给 TreeView ItemTemplate 的正确方法是啥?