2015-11-07 1 views
12

Я изучаю Rust, когда пытаюсь найти альтернативу interop C# с C/C++.Как вернуть массив структур от Rust до C#

Как можно писать код Rust как код C ниже? Это мой Rust код до сих пор, без опции Маршалла документа:

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 
} 

Код выше пытается воспроизвести следующий C код, который я нахожусь в состоянии взаимодействовать с как есть.

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); 
    } 
} 

Этот C код может быть доступен через импорт DLL в C# как это:

[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

Я не знаю много о C# interop, но одно всегда верно для использования FFI с любым внешним языком: вы не должны использовать Rust-специфичные типы, такие как 'Vec ' в 'extern'. Единственными видами типов, которые могут использоваться в таких функциях, являются примитивные типы, такие как целые числа, поплавки и указатели, и структуры '# [repr (C)]', которые состоят из этих типов. –

ответ

2

Давайте разберем это вниз в различные требования, что ваш код Rust должен соответствовать:

  1. DLL должна выставить функцию с правильным именем GetPacksChar. Это потому, что вы объявляете его с именем GetPacksChar из C#, и имена должны совпадать.
  2. Функция нуждается в правильном соглашении о вызове, в данном случае extern "C". Это потому, что вы объявляете функцию как CallingConvention = CallingConvention.Cdecl из C#, которая соответствует соглашению о вызове extern "C" в Rust.
  3. Функция нуждается в правильной сигнатуре, в данном случае принимая эквивалент Rusta uint и PackChar** и ничего не возвращает. Это соответствует сигнатуре функции fn (u32, *mut *mut PackChar).
  4. Объявление от PackChar должно соответствовать C# и Rust. Я рассмотрю это ниже.
  5. Функция должна воспроизводить поведение исходной функции C. Я рассмотрю это ниже.

Простейшим часть будет объявить функцию в Русте:

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

Далее нам нужно обратиться PackChar. Исходя из того, как он используется в C# код, он выглядит, как он должен быть объявлен:

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

Ломая это вниз, #[repr(C)] говорит Rust компилятор расположить PackChar в памяти так же, С компилятор, который имеет важное значение так как вы говорите C#, что он звонит в C. IntVal и buffer оба используются с C# и исходной версией C. IntVal объявлен как int в версии C, поэтому мы используем i32 в версии Rust, а buffer рассматривается как массив байтов на C, поэтому мы используем *mut u8 в Rust.

Обратите внимание, что определение PackChar в C# должны соответствовать декларации в C/Руст, так:

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

Теперь все, что осталось воспроизвести исходное поведение функции C в Русте:

#[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'); 
    } 
} 

Важные моменты из вышеизложенного:

  • Мы должны вручную включать нулевую Termina ting character (\0) в DUMMY_STR, потому что это должна быть строка C.
  • Мы называем CoTaskMemAlloc() и strdup(), которые являются одновременно функциями C. strdup() находится в libc crate, и вы можете найти его в the ole32-sys crate.
  • Функция объявлена ​​как unsafe, потому что нам нужно сделать несколько небезопасных вещей, например, вызвать функции C и выполнить str::from_raw_parts().

Надеюсь, что это поможет!