分配数据以传递给 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
对于您的具体情况,我可能建议使用Vec
的capacity
而不是自己跟踪第二个变量。您会注意到在第一次调用后您忘记更新pcb_buffer
,所以我很确定代码总是会失败。这很烦人,因为它需要是一个可变引用,因此您无法完全摆脱它。
此外,您可以只使用reserve
空格,而不是extend
Vec
。
也不能保证第一次调用所需的尺寸与第二次调用所需的尺寸相同。你可以做一些循环,但是你不得不担心会发生无限循环。
【讨论】:
感谢您的回答。所以,我想我最好坚持使用 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 查询它的正确方法是啥?