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

标签 rust ffi

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 = "*"

最佳答案

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

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

主要缺点是您需要了解对齐方式,即使您释放分配也是如此。

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

  1. 确保您的单位正确——“长度”参数是项目的数量还是字节的数量?
  2. 如果将 Vec 分解为组件部分,则需要
    1. 跟踪长度容量。有些人使用 shrink_to_fit以确保这两个值相同。
    2. 避免跨越流 - 内存由 Rust 分配并且必须由 Rust 释放。将其转换回要删除的 Vec
  3. 注意一个空的 Vec 有一个 NULL 指针!:

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

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

此外,您可以只使用 reserve 而不是扩展 Vec空间。

也无法保证第一次调用期间所需的大小与第二次调用期间所需的大小相同。您可以执行某种循环,但您必须担心会发生无限循环。

关于rust - 分配数据以传递给 FFI 调用的正确方法是什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39550856/

相关文章:

c++ - flutter :即使 shouldRepaint() 返回 true,自定义画家也不会重新绘制

rust - 从 FFI 返回的切片创建 OsStr(ing) 的正确方法是什么?

ios - 将静态库(Rust .a)导入到 iOS 中的 Flutter 项目

c - 如何在没有 c2hs 或其他工具的情况下为此结构创建可存储实例?

rust - 在实现返回可变引用的迭代器时,如何修复 “cannot infer an appropriate lifetime for autoref”?

rust - 如何存储仅引用 `::new()` 中创建的字符串的字段?

rust - 如何在不按回车的情况下从标准输入中读取一个字符?

rust - to_string() 的稳定替代方案是什么

rust - 在发布时使用最新版本的依赖关系是一种好习惯吗?

haskell - 将混合可存储向量传递给 C 函数