如何从Rust返回结构向量到C#? [英] How to return a vector of structs from Rust to C#?

查看:142
本文介绍了如何从Rust返回结构向量到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:


  1. DLL需要公开具有正确名称 GetPacksChar 。这是因为您使用C#中的名称 GetPacksChar 进行了声明,并且名称必须匹配。

  2. 该函数需要正确的调用约定,在这种情况下,外部 C 。这是因为您从C#中将函数声明为 CallingConvention = CallingConvention.Cdecl ,该函数与外部 C 调用相匹配

  3. 该函数需要正确的签名,在这种情况下,使用与 uint PackChar ** ,什么也不返回。这与函数签名 fn(u32,* mut * mut PackChar)匹配。

  4. PackChar的声明需要在C#和Rust之间匹配。我将在下面进行介绍。

  5. 该函数需要复制原始C函数的行为。我将在下面进行介绍。

  1. The DLL needs to expose a function with the correct name GetPacksChar. This is because you declare it with the name GetPacksChar from C# and the names must match.
  2. The function needs the correct calling convention, in this case extern "C". This is because you declare the function as CallingConvention = CallingConvention.Cdecl from C#, which matches the extern "C" calling convention in Rust.
  3. The function needs the correct signature, in this case taking the Rust equivalent of a uint and a PackChar** and returning nothing. This matches the function signature fn (u32, *mut *mut PackChar).
  4. The declaration of PackChar needs to match between C# and Rust. I'll go over this below.
  5. 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 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) in DUMMY_STR because it's meant to be a C string.
  • We call CoTaskMemAlloc() and strdup(), 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 doing str::from_raw_parts().

希望有帮助!

这篇关于如何从Rust返回结构向量到C#?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆