2015-11-07 19 views
12

Sto imparando Rust mentre cerco di trovare un'alternativa all'interop C# con C/C++.Come restituire un array di strutture da Rust a C#

Come è possibile scrivere il codice Rust come il codice C sotto? Questo è il mio codice Rust finora, senza la possibilità di marshall esso:

pub struct PackChar { id: u32, val_str: String, } 

#[no_mangle] 
pub extern fn get_packs_char(size: u32) -> Vec<PackChar> { 

    let mut out_vec = Vec::new(); 

    for i in 0 .. size { 
     let int_0 = '0' as u32; 
     let last_char_val = int_0 + i % (126 - int_0); 
     let last_char = char::from_u32(last_char_val).unwrap(); 
     let buffer = format!("abcdefgHi{}", last_char); 

     let pack_char = PackChar { 
      id: i, 
      val_str: buffer, 
     }; 

     out_vec.push(pack_char); 
    } 

    out_vec 
} 

Il codice di cui sopra tenta di riprodurre il seguente codice C, che sono in grado di interoperare con così com'è.

void GetPacksChar(int size, PackChar** DpArrPnt) 
{ 
    int TmpStrSize = 10; 
    *DpArrPnt = (PackChar*)CoTaskMemAlloc(size * sizeof(PackChar)); 
    PackChar* CurPackPnt = *DpArrPnt; 
    char dummyString[]= "abcdefgHij"; 
    for (int i = 0; i < size; i++,CurPackPnt++) 
    { 
     dummyString[TmpStrSize-1] = '0' + i % (126 - '0'); 
     CurPackPnt->IntVal = i; 
     CurPackPnt->buffer = strdup(dummyString); 
    } 
} 

Questo codice C potrebbe accedere tramite importazione DLL in C# come questo:

[Dllimport("DllPath", CallingConvention = CallingConvention.Cdecl)] 
public static extern void GetPacksChar(uint length, PackChar** ArrayStructs) 

PackChar* MyPacksChar; 
GetPacksChar(10, &MyPacksChar); 
PackChar* CurrentPack = MyPacksChar; 
var contLst = new List<PackChar>(); 
for (uint i = 0; i < ArrL; i++, CurrentPack++) 
    contlist.Add(new PackChar() { 
     IntVal = CurrentPack->IntVal, buffer = contLst->buffer 
    }); 
+12

Non conosco molto l'interoperabilità C#, ma una cosa è sempre vera per l'utilizzo di FFI con qualsiasi linguaggio esterno: non si devono usare tipi specifici di Ruggine come 'Vec ' nelle funzioni 'extern'. Gli unici tipi di tipi che possono essere usati in tali funzioni sono tipi primitivi come interi, float e puntatori e strutture '# [repr (C)]' che consistono di questi tipi. –

risposta

2

Facciamo una scomposizione in alle diverse esigenze che il codice della ruggine deve soddisfare:

  1. La DLL deve esporre una funzione con il nome corretto GetPacksChar. Questo perché lo si dichiara con il nome GetPacksChar da C# e i nomi devono corrispondere.
  2. La funzione richiede la convenzione di chiamata corretta, in questo caso extern "C". Questo perché dichiari la funzione come CallingConvention = CallingConvention.Cdecl da C#, che corrisponde alla convenzione di chiamata extern "C" in Rust.
  3. La funzione richiede la firma corretta, in questo caso l'equivalente di ruggine di uno uint e un PackChar** e la restituzione di nulla. Corrisponde alla firma della funzione fn (u32, *mut *mut PackChar).
  4. La dichiarazione di PackChar deve corrispondere tra C# e Ruggine. Lo esaminerò qui sotto.
  5. La funzione deve replicare il comportamento della funzione C originale. Lo esaminerò qui sotto.

La parte più facile sarà dichiarando la funzione di Rust:

#[no_mangle] 
pub extern "C" fn GetPacksChar(length: u32, array_ptr: *mut *mut PackChar) {} 

Poi abbiamo bisogno di affrontare PackChar. In base a come viene usata nel codice C#, sembra che dovrebbe essere dichiarato:

#[repr(C)] 
pub struct PackChar { 
    pub IntVal: i32, 
    pub buffer: *mut u8, 
} 

Rompere questo in giù, #[repr(C)] dice al compilatore Rust di organizzare PackChar in memoria allo stesso modo un compilatore C sarebbe, che è importante dal momento che stai dicendo a C# che sta chiamando in C. IntVal e buffer vengono entrambi utilizzati da C# e dalla versione C originale. IntVal è dichiarato come int nella versione C, quindi utilizziamo i32 nella versione di Rust e buffer viene trattato come una matrice di byte in C, quindi utilizziamo uno *mut u8 in Rust.

Si noti che la definizione di PackChar in C# deve corrispondere la dichiarazione in C/Ruggine, quindi:

public struct PackChar { 
    public int IntVal; 
    public char* buffer; 
} 

Ora tutto quello che resta è quello di riprodurre il comportamento originale della funzione C a Rust:

#[no_mangle] 
pub extern "C" fn GetPacksChar(len: u32, array_ptr: *const *mut PackChar) { 
    static DUMMY_STR: &'static [u8] = b"abcdefgHij\0"; 

    // Allocate space for an array of `len` `PackChar` objects. 
    let bytes_to_alloc = len * mem::size_of::<PackChar>(); 
    *array_ptr = CoTaskMemAlloc(bytes_to_alloc) as *mut PackChar; 

    // Convert the raw array of `PackChar` objects into a Rust slice and 
    // initialize each element of the array. 
    let mut array = slice::from_raw_parts(len as usize, *array_ptr); 
    for (index, pack_char) in array.iter_mut().enumerate() { 
     pack_char.IntVal = index; 
     pack_char.buffer = strdup(DUMMY_STR as ptr); 
     pack_char.buffer[DUMMY_STR.len() - 1] = b'0' + index % (126 - b'0'); 
    } 
} 

punti importanti da sopra:

  • Dobbiamo includere manualmente il ter nullo carattere minating (\0) in DUMMY_STR perché è pensato per essere una stringa C.
  • Chiamiamo CoTaskMemAlloc() e strdup(), che sono entrambe le funzioni C. strdup() è nello libc crate e probabilmente lo si può trovare in the ole32-sys crate.
  • La funzione è dichiarata come unsafe perché dobbiamo fare una serie di cose non sicure, come chiamare le funzioni C e fare str::from_raw_parts().

Spero che questo aiuti!

Problemi correlati