2015-11-07 61 views
12

我在學習Rust,試圖找到C/C++與C#互操作的替代方案。如何將Rust的數組返回到C#

如何編寫Rust代碼,如下面的C代碼?這是我的防鏽代碼到目前爲止,沒有選項馬歇爾它:

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都是正確的:不應該在'extern'函數中使用特定於Rust的類型,如'Vec '。可以在這種函數中使用的唯一類型是基本類型,如整型,浮點型和指針,以及由這些類型組成的'#[repr(C)]'結構。 –

回答

2

讓我們打破這種分解成你的防鏽代碼需要滿足各種要求:

  1. 該DLL需要公開一個正確名稱的函數GetPacksChar。這是因爲您使用C#中的名稱GetPacksChar聲明它,並且名稱必須匹配。
  2. 該功能需要正確的調用約定,在這種情況下爲extern "C"。這是因爲您從C#中將函數聲明爲CallingConvention = CallingConvention.Cdecl,該函數與Rust中的extern "C"調用約定相匹配。
  3. 該功能需要正確的簽名,在這種情況下,將鏽等效爲uintPackChar**,並且不返回任何內容。這匹配功能簽名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)]告訴鏽編譯器在內存中安排PackChar以同樣的方式一個C編譯器,這是非常重要的因爲你告訴C#它正在調用C。IntValbuffer都是從C#和原始C版本使用的。 IntVal在C版本中聲明爲int,所以我們在Rust版本中使用i32,並且buffer被視爲C中的字節數組,因此我們在Rust中使用*mut u8

注意,在C#中的PackChar定義應該在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'); 
    } 
} 

從上面的要點:

  • 我們必須手動包括空之三(\0DUMMY_STR,因爲它意味着是一個C字符串。
  • 我們稱之爲CoTaskMemAlloc()strdup(),它們都是C函數。 strdup()libc crate,你可能在the ole32-sys crate找到。
  • 該函數聲明爲unsafe,因爲我們必須執行一些不安全的事情,例如調用C函數並執行str::from_raw_parts()

希望有幫助!