【问题标题】: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 我希望它是一个答案,但我仍然希望它可以通过属性以某种方式完成。

标签: c# .net arrays pinvoke marshalling


【解决方案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,所以我不确定它是否更好......
猜你喜欢
  • 2012-03-20
  • 1970-01-01
  • 2013-04-07
  • 2011-07-03
  • 1970-01-01
  • 1970-01-01
  • 2020-08-10
  • 2011-09-01
  • 2011-07-31
相关资源
最近更新 更多