如何从Rust返回结构向量到C#? [英] How to return a vector of structs from Rust to C#?
问题描述
如何像下面的C代码一样编写Rust代码?到目前为止,这是我的Rust代码,无法选择将其封送:
How is it possible to write Rust code like the C code below? This is my Rust code so far, without the option to marshal it:
pub struct PackChar {
id: u32,
val_str: String,
}
#[no_mangle]
pub extern "C" 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代码我可以按原样进行操作。
The code above tries to reproduce the following C code which I am able to interoperate with as is.
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代码,如下所示:
This C code could be accessed via DLL import in C# like this:
[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
});
推荐答案
让我们将其分解为Rust的各种要求代码需要满足:
Let's break this down into the various requirements that your Rust code needs to meet:
- DLL需要公开具有正确名称
GetPacksChar $ c $的函数c>。这是因为您使用C#中的名称
GetPacksChar
进行了声明,并且名称必须匹配。 - 该函数需要正确的调用约定,在这种情况下,
外部 C
。这是因为您从C#中将函数声明为CallingConvention = CallingConvention.Cdecl
,该函数与外部 C
调用相匹配 - 该函数需要正确的签名,在这种情况下,使用与
uint
和PackChar **
,什么也不返回。这与函数签名fn(u32,* mut * mut PackChar)
匹配。 -
PackChar的声明
需要在C#和Rust之间匹配。我将在下面进行介绍。 - 该函数需要复制原始C函数的行为。我将在下面进行介绍。
- The DLL needs to expose a function with the correct name
GetPacksChar
. This is because you declare it with the nameGetPacksChar
from C# and the names must match. - The function needs the correct calling convention, in this case
extern "C"
. This is because you declare the function asCallingConvention = CallingConvention.Cdecl
from C#, which matches theextern "C"
calling convention in Rust. - The function needs the correct signature, in this case taking the Rust equivalent of a
uint
and aPackChar**
and returning nothing. This matches the function signaturefn (u32, *mut *mut PackChar)
. - The declaration of
PackChar
needs to match between C# and Rust. I'll go over this below. - The function needs to the replicate the behavior of the original C function. I'll go over this below.
最简单的部分是在Rust中声明函数:
The easiest part will be declaring the function in Rust:
#[no_mangle]
pub extern "C" fn GetPacksChar(length: u32, array_ptr: *mut *mut PackChar) {}
下一步,我们需要处理 PackChar
。根据在C#代码中的使用方式,看起来应该声明它:
Next we need to address PackChar
. Based on how it's used in the C# code, it looks like it should be declared:
#[repr(C)]
pub struct PackChar {
pub IntVal: i32,
pub buffer: *mut u8,
}
打破这一点,#[repr(C)]
告诉Rust编译器安排 PackChar
在内存中的存储方式与C编译器相同,这很重要,因为您要告诉C#它正在调用C。 IntVal
和 buffer
都在C#和原始C版本中使用。 IntVal
在C版本中被声明为 int
,因此我们使用 i32 $ Rust版本中的c $ c>和
buffer
被视为C中的字节数组,因此我们使用 * mut u8 $
Breaking this down, #[repr(C)]
tells the Rust compiler to arrange PackChar
in memory the same way a C compiler would, which is important since you're telling C# that it's calling into C. IntVal
and buffer
are both used from C# and the original C version. IntVal
is declared as an int
in the C version, so we use i32
in the Rust version, and buffer
is treated as an array of bytes in C, so we use a *mut u8
in Rust.
请注意,C#中 PackChar
的定义应与C /中的声明匹配Rust,所以:
Note that the definition of PackChar
in C# should match the declaration in C/Rust, so:
public struct PackChar {
public int IntVal;
public char* buffer;
}
现在剩下的就是重现Rust中C函数的原始行为:
Now all that's left is to reproduce the original behavior of the C function in 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');
}
}
以上重要点:
- 我们必须在
\0
) > DUMMY_STR ,因为它是C字符串。 - 我们分别调用
CoTaskMemAlloc()
和strdup()
,它们都是C函数。strdup()
在 libc箱中,您可能可以在 ole32-sys板条箱中找到。 - 该函数声明为
不安全
,因为我们必须做一些不安全的事情,例如调用C函数并执行str :: from_raw_parts()
。
- We have to manually include the null terminating character (
\0
) inDUMMY_STR
because it's meant to be a C string. - We call
CoTaskMemAlloc()
andstrdup()
, which are both C functions.strdup()
is in the libc crate, and you can probably find in the ole32-sys crate. - The function is declared as
unsafe
because we have to do a number of unsafe things, like calling C functions and doingstr::from_raw_parts()
.
希望有帮助!
这篇关于如何从Rust返回结构向量到C#?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!