我们说我有一个类似于
的结构public struct MyStruct
{
public float[] a;
}
并且我想用一些自定义数组大小来实例化一个这样的结构(对于这个例子,让我们说2)。然后我将它编组成一个字节数组。
MyStruct s = new MyStruct();
s.a = new float[2];
s.a[0] = 1.0f;
s.a[1] = 2.0f;
byte[] buffer = new byte[Marshal.SizeOf(typeof(MyStruct))];
GCHandle gcHandle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
try
{
Marshal.StructureToPtr(s, gcHandle.AddrOfPinnedObject(), false);
for (int i = 0; i < buffer.Length; i++)
{
System.Console.WriteLine(buffer[i].ToString("x2"));
}
}
finally
{
gcHandle.Free();
}
这给了我在byte []中只有4个字节,它们看起来像一个指针值,而不是1.0f或2.0f的值。我已经四处寻找方法来完成这项工作,但到目前为止我所能找到的都是类似的例子,其中结构数组的大小是提前知道的。难道没有办法做到这一点吗?
答案 0 :(得分:2)
StructureToPtr
仅适用于仅包含值类型的结构(int,char,float,其他结构)。 float[]
是一个引用类型,所以你真的得到了一种指针(你不能真正使用它,因为它是一个托管指针)。如果要将阵列复制到固定内存,则必须直接在Marshal.Copy
浮点数组中使用s.a
函数之一。
这样的事情。 (我没有真正测试过它)
byte[] buffer = new byte[sizeof(float) * s.a.Length];
GCHandle gcHandle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
然后
Marshal.Copy(s.a, 0, gcHandle.AddrOfPinnedObject(), s.a.Length);
更新
我必须纠正自己。通过以这种方式声明结构,您也可以得到您想要的结果:
[StructLayout(LayoutKind.Sequential)]
public struct MyStruct
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
public float[] a;
}
如您所见,您必须在设计时修复浮点数组的大小。
答案 1 :(得分:1)
Marshal
实际上是用于本机互操作(p / invoke等),而本机语言也不允许结构成员在运行时的大小不同。 (有一个叫做灵活数组成员的技巧,它只能出现在最后......你只需要通过分配超出结构大小的额外内存来处理它。)
如果要将基元数组序列化为字节数组,只需使用Buffer.BlockCopy
。原语结构的数组更难,你可以编译一个DynamicMethod,它使用msvcrt.dll中的cpblk
MSIL指令或p / invoke memcpy
或kernel32.dll中的RtlCopyMemory
。
答案 2 :(得分:1)
在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");
// Array is a contiguous data structure, so assign the memory appended to the struct for the elements other than the first
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
{
// The handle must be freed in case of any error, since it won't be visible outside this method in this case
handle.Free();
throw;
}
return handle; // Make sure to free this handle at the end of usage!
}
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); // Since the above unmarshalling always gives us an array of Size 1
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等。