rust ffi

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:

  1. Debe conocer la alineación , incluso cuando libera la asignación.
  2. 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.

  1. Asegúrese de que sus unidades sean correctas. ¿El parámetro "longitud" es el número de elementos o el número de bytes ?
  2. Si disuelve el Vec en componentes, necesita
    1. rastrear la longitud y la capacidad . Algunas personas usan shrink_to_fit para garantizar que esos dos valores sean los mismos.
    2. 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.
  3. ¡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);