如何在本机代码中传递数组数组

Posted

技术标签:

【中文标题】如何在本机代码中传递数组数组【英文标题】:How do I pass array of arrays in native code 【发布时间】:2018-06-15 12:28:13 【问题描述】:

我想将一个 UTF8 编码字符串数组传递到本机代码中。我是为一个字符串做的(因为它只是一个字节数组),但现在我想传递一个字符串数组。

因为我不知道数组的大小,所以我应该传递对它的引用。所以我写了一个结构体,它应该包含数组指针和它的长度:

[StructLayout(LayoutKind.Sequential)]
struct StringRef

    [MarshalAs(UnmanagedType.LPArray)]
    public byte[] Bytes;
    public uint Length;

但是当我运行它时它失败了:

无法封送“StringRef”类型的字段“Bytes”:托管/非托管类型组合无效(数组字段必须与 ByValArray 或 SafeArray 配对)。

这是完整的代码示例。

托管代码:

public class RustInterop

    [StructLayout(LayoutKind.Sequential)]
    struct StringRef
    
        [MarshalAs(UnmanagedType.LPArray)]
        public byte[] Bytes;
        public uint Length;
    

    [DllImport("rust.dll")]
    private static extern int println(StringRef[] array, uint count);

    public static int Println(params string[] strings)
    
        var array = strings.Select(str => Encoding.UTF8.GetBytes(str)).Select(bytes => new StringRef
        
            Bytes = bytes,
            Length = (uint)bytes.Length
        ).ToArray();
        return println(array, (uint) array.Length);
    

非托管代码:

#[repr(C)]
pub struct StringRef 
    bytes: *const u8,
    length: usize


#[no_mangle]
pub extern fn println(array: *const StringRef, count: usize) -> i32 
    let array_slice = unsafe  std::slice::from_raw_parts(array, count) ;
    for str in array_slice 
        let slice = unsafe  std::slice::from_raw_parts(str.bytes, str.length) ;
        let str = std::str::from_utf8(slice);
        match str 
            Ok(str) => 
                println!("", str);
            ,
            Err(_) => return -1
        ;
    
    1

【问题讨论】:

您必须手动整理所有内容。 @xanatos 我希望它是一个答案,但我仍然希望它可以通过属性以某种方式完成。 【参考方案1】:

你能做的最好的就是:

[StructLayout(LayoutKind.Sequential)]
struct StringRef

    public IntPtr Bytes;
    public int Length;


[DllImport("CPlusPlusSide.dll")]
private static extern int println(StringRef[] array, int count);

public static int Println(params string[] strings)

    var utf8s = Array.ConvertAll(strings, x => Encoding.UTF8.GetBytes(x));
    var handles = new GCHandle[utf8s.Length];
    var refs = new StringRef[utf8s.Length];

    try
    
        for (int i = 0; i < handles.Length; i++)
        
            try
            
            
            finally
            
                handles[i] = GCHandle.Alloc(utf8s[i], GCHandleType.Pinned);
            

            refs[i] = new StringRef
            
                Bytes = handles[i].AddrOfPinnedObject(),
                Length = (int)utf8s[i].Length
            ;
        

        return println(refs, refs.Length);
    
    finally
    
        for (int i = 0; i < handles.Length; i++)
        
            if (handles[i].IsAllocated)
            
                handles[i].Free();
            
        
    

请注意,我已经将各种uint 更改为int...最后它们具有相同的sizeof(),所以这里没有问题。

用这个 C 代码测试过(我没有 rust 编译器):

typedef struct StringRef

    char* bytes;
    unsigned int length;
 StringRef;

__declspec(dllexport) int __stdcall println(StringRef* array, unsigned int count)

    for (unsigned int i = 0; i < count; i++)
    
        printf("%.*s\n", array[i].length, array[i].bytes);
    

    return 0;

【讨论】:

我写了几乎相同的代码github.com/Pzixel/CsRustInterop/blob/master/cs/ConsoleApp/…。很遗憾,marshaller 如此愚蠢以至于无法处理LPArray。你的代码提醒我,我必须把finally 子句放在那里。 @AlexZhukovskiy 最大的不同是我将 utf8 内存“固定”到位,而不是到处复制。每个字符串少了一个复制操作。 是的,我注意到了。我不知道GCHandle 所以你的例子比我在提问时预期的要好:) 您可以使用 BSTR 代替此自定义 StringRef 并在 C# 中声明该函数,如下所示:private static extern int println([MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.BStr)] IntPtr[] array, int count) 它可以工作,但您必须分配一个 IntPtr,手动编写 BSTR,传递 IntPtr-4 和取消分配 IntPtr,所以我不确定它是否更好......

以上是关于如何在本机代码中传递数组数组的主要内容,如果未能解决你的问题,请参考以下文章

JNI维数组:将二维数组从java传递给c

如何在 C# 中编组结构数组?

AccessViolationException将数组传递给本机函数

如何将 cli::array 从本机代码转换为本机数组?

如何在本机反应中为数组的每个元素分配相同的值。如何在本机反应中制作密钥对数组

如何在托管代码(C#)中从本机代码(C++)获取字符串数组