rust - ¿Cuál es la forma correcta de asignar datos para pasar a una llamada FFI?
(2)
Lo ideal sería utilizar alloc::heap::allocated
porque puede especificar la alineación deseada:
pub unsafe fn allocate(size: usize, align: usize) -> *mut u8
Sin embargo, hay dos desventajas al usarlo:
- Debe conocer la alineación , incluso cuando libera la asignación.
- Es inestable.
Es una práctica común utilizar un Vec
como un mecanismo de asignación fácil y estable, pero debe tener cuidado al usarlo como tal.
- Asegúrese de que sus unidades sean correctas. ¿El parámetro "longitud" es el número de elementos o el número de bytes ?
- Si disuelve el
Vec
en componentes, necesita- rastrear la longitud y la capacidad . Algunas personas usan
shrink_to_fit
para garantizar que esos dos valores sean los mismos. - Evite cruzar las corrientes : esa memoria fue asignada por Rust y debe ser liberada por Rust. Convierta de nuevo en un
Vec
para ser descartado.
- rastrear la longitud y la capacidad . Algunas personas usan
¡Ten en cuenta que un
Vec
vacío no tiene un puntero NULL !:fn main() { let v: Vec<u8> = Vec::new(); println!("{:p}", v.as_ptr()); // => 0x1 }
Para su caso específico, podría sugerir utilizar la capacity
del Vec
lugar de seguir la segunda variable usted mismo. Notará que olvidó actualizar pcb_buffer
después de la primera llamada, por lo que estoy bastante seguro de que el código siempre fallará. Es molesto porque necesita ser una referencia mutable para que no pueda alejarse por completo.
Además, en lugar de extend
el Vec
, puede reserve
espacio.
Tampoco hay garantía de que el tamaño requerido durante la primera llamada sea el mismo que el requerido durante la segunda llamada. Podrías hacer algún tipo de ciclo, pero luego tienes que preocuparte de que ocurra un ciclo infinito.
Después de discutir / conocer la forma correcta de llamar a un FFI de Windows-API desde Rust , jugué un poco más y me gustaría verificar mi comprensión.
Tengo una API de Windows que se llama dos veces. En la primera llamada, devuelve el tamaño del búfer que necesitará para su parámetro de salida real. Luego, se llama una segunda vez con un buffer de tamaño suficiente. Actualmente estoy usando un Vec
como un tipo de datos para este búfer (ver ejemplo a continuación).
El código funciona pero me pregunto si esta es la manera correcta de hacerlo o si sería mejor utilizar una función como alloc::heap::allocate
para reservar directamente algo de memoria y luego usar transmute
para convertir el resultado del FFI vuelve. De nuevo, mi código funciona, pero trato de mirar un poco detrás de la escena.
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());
}
Dependencias:
[dependencies]
advapi32-sys = "0.2"
winapi = "0.2"
widestring = "*"
Esto es lo que se me ocurrió.
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)) };
}
}
El objeto FFI se crea utilizando un vector u8
para que el tamaño esté en bytes. Esto podría generalizarse para usar un tipo arbitrario en lugar de u8
pero tenga en cuenta la advertencia de Shepmaster sobre la distinción entre el número de bytes y la cantidad de elementos.
Aquí hay un ejemplo del uso de 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);