在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 = "*"
答案 0 :(得分:3)
理想情况下,您可以使用std::alloc::alloc
,因为您可以在布局中指定所需的对齐方式:
pub unsafe fn alloc(layout: Layout) -> *mut u8
主要的缺点是你需要知道对齐,即使你释放了分配。
通常的做法是使用Vec
作为简单的分配机制,但在使用它时需要小心。
Vec
解散为组件部分,则需要执行此操作
shrink_to_fit
来确保这两个值相同。Vec
即可删除。请注意空Vec
不有空指针!:
fn main() {
let v: Vec<u8> = Vec::new();
println!("{:p}", v.as_ptr());
// => 0x1
}
对于您的具体情况,我建议您使用capacity
的{{1}},而不是自己跟踪第二个变量。您注意到在第一次通话后忘记更新Vec
,因此我非常确定代码始终会失败。它很烦人,因为它需要是一个可变参考,所以你不能完全摆脱它。
此外,pcb_buffer
代替extend
,reserve
空间。
也不能保证第一次通话期间所需的尺寸与第二次通话期间所需的尺寸相同。你可以做某种循环,但是你必须担心会发生无限循环。
答案 1 :(得分: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);