Reputation: 10025
I want to pass an array of UTF8 encoded strings into native code. I did it for one single string (as it's just a byte array), but now I want to pass an array of strings.
Because I don't know the size of array, I should pass reference to it. So I write a struct that should contain array pointer and its length:
[StructLayout(LayoutKind.Sequential)]
struct StringRef
{
[MarshalAs(UnmanagedType.LPArray)]
public byte[] Bytes;
public uint Length;
}
But when I run it it fails:
Cannot marshal field 'Bytes' of type 'StringRef': Invalid managed/unmanaged type combination (Array fields must be paired with ByValArray or SafeArray).
Here is complete code sample.
Managed code:
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);
}
}
Unmanaged code:
#[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
}
Upvotes: 1
Views: 992
Reputation: 111890
The best you can do is this:
[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();
}
}
}
}
Note that I've changed the various uint
to int
... In the end they have the same sizeof()
, so no problems here.
Tested with this C code (I don't have a rust compiler):
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;
}
Upvotes: 1