在P/Invoke中,没有直接支持您所期望的场景。我编码的技术类似于C语言中用于在结构体中存储可变长度数组的技术。
[StructLayout(LayoutKind.Sequential)]
public struct VarLenStruct
{
public int elementCount;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)]
public float[] array;
public static GCHandle GetUnmanagedStruct(VarLenStruct managedStruct)
{
if (managedStruct.elementCount < 1)
throw new ArgumentOutOfRangeException("The array size must be non-zero");
int managedBufferSize = Marshal.SizeOf(managedStruct) + Marshal.SizeOf(typeof(float)) * (managedStruct.elementCount - 1);
byte[] managedBuffer = new byte[managedBufferSize];
var handle = GCHandle.Alloc(managedBuffer, GCHandleType.Pinned);
try
{
IntPtr unmgdStructPtr = handle.AddrOfPinnedObject();
Marshal.StructureToPtr(managedStruct, unmgdStructPtr, fDeleteOld: false);
IntPtr unmgdArrAddr = unmgdStructPtr + Marshal.OffsetOf(typeof(VarLenStruct), "array").ToInt32();
Marshal.Copy(source: managedStruct.array, startIndex: 0, destination: unmgdArrAddr, length: managedStruct.elementCount);
}
catch
{
handle.Free();
throw;
}
return handle;
}
public static VarLenStruct GetManagedStruct(IntPtr unmanagedStructPtr)
{
VarLenStruct resultStruct = (VarLenStruct)Marshal.PtrToStructure(unmanagedStructPtr, typeof(VarLenStruct));
if (resultStruct.elementCount < 1)
throw new NotSupportedException("The array size must be non-zero");
Array.Resize(ref resultStruct.array, newSize: resultStruct.elementCount);
IntPtr unmgdArrAddr = unmanagedStructPtr + Marshal.OffsetOf(typeof(VarLenStruct), "array").ToInt32();
Marshal.Copy(source: unmgdArrAddr, destination: resultStruct.array, startIndex: 0, length: resultStruct.elementCount);
return resultStruct;
}
}
public static void TestVarLengthArr()
{
VarLenStruct[] structsToTest = new VarLenStruct[]{
new VarLenStruct() { elementCount = 1, array = new float[] { 1.0F } },
new VarLenStruct() { elementCount = 2, array = new float[] { 3.5F, 6.9F } },
new VarLenStruct() { elementCount = 5, array = new float[] { 1.0F, 2.1F, 3.5F, 6.9F, 9.8F } }
};
foreach (var currStruct in structsToTest)
{
var unmgdStructHandle = VarLenStruct.GetUnmanagedStruct(currStruct);
try
{
var ret = VarLenStruct.GetManagedStruct(unmgdStructHandle.AddrOfPinnedObject());
if (!ret.array.SequenceEqual(currStruct.array))
throw new Exception("Code fail!");
}
finally
{
unmgdStructHandle.Free();
}
}
}
我目前已经阻止了使用空数组,但是通过一些额外的处理,您也可以实现。您还可以在结构体构造函数中添加验证以检查array.Length == elementCount等。
Buffer.BlockCopy
,则无需处理固定缓冲区的问题。 - Ben VoigtMarshal.Copy
或者Buffer.BlockCopy
的想法,但是我担心这可能会破坏尝试使用marshaler的目的。我提供的示例结构体非常简单,在现实中,我的结构体是值类型和引用类型混杂在一起的。似乎唯一的方法是手动迭代结构体中的每个元素,并以适合该元素的方式对其进行序列化。 - Paul GrinbergMarshal.SizeOf
和Marshal.StructureToPtr
方法在整个结构体上正确工作的唯一方法。 - Fratyx