如何在本机代码中传递数组数组
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,所以我不确定它是否更好......以上是关于如何在本机代码中传递数组数组的主要内容,如果未能解决你的问题,请参考以下文章
AccessViolationException将数组传递给本机函数