用数组编译结构的C#编组

时间:2014-12-03 21:47:12

标签: c# arrays struct runtime marshalling

我们说我有一个类似于

的结构
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的值。我已经四处寻找方法来完成这项工作,但到目前为止我所能找到的都是类似的例子,其中结构数组的大小是提前知道的。难道没有办法做到这一点吗?

3 个答案:

答案 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等。