将对象转换为字节数组而不进行序列化或填充

时间:2017-12-05 09:10:21

标签: c#

这似乎很容易,但我看不出它容易。我的问题是我有一个结构,我需要将它转换为字节流,没有任何额外的字节类型,填充和元数据。假设我有一个结构

[StructLayout(LayoutKind.Sequential)]
public struct MyStruct
{
    public ushort a;                 
    public uint b;  
    public uint c;
    public ushort d;
}

备注:我无法将此处的包更改为1(项目限制),因此使用以下解决方案将无法正常工作 39;添加了填充

int size = Marshal.SizeOf(typeof(T));
byte[] arr = new byte[size];
IntPtr ptr = Marshal.AllocHGlobal(size);
Marshal.StructureToPtr(str, ptr, true);
Marshal.Copy(ptr, arr, 0, size);
Marshal.FreeHGlobal(ptr);

另请注意:我不能使用二进制格式序列化,因为添加了元数据。所有我想要的只是如果a = 1 b = 2 c = 3 d = 4,以 GENERIC WAY 获得二进制格式

    arr {byte[12]}  byte[]
    [0]  1  byte
    [1]  0  byte
    [2]  2  byte
    [3]  0  byte
    [4]  0  byte
    [5]  0  byte
    [6]  3  byte
    [7]  0  byte
    [8]  0  byte
    [9]  0  byte
    [10] 4  byte
    [11] 0  byte

任何帮助?

4 个答案:

答案 0 :(得分:0)

您可以使用显式布局来准确定义数组的形成方式:

    [StructLayout(LayoutKind.Explicit)]
    public struct MyStruct
    {
        [FieldOffset(0)]
        public ushort a;
        [FieldOffset(2)]
        public uint b;
        [FieldOffset(6)]
        public uint c;
        [FieldOffset(10)]
        public ushort d;
    }

但当然它根本不是通用的

答案 1 :(得分:0)

编辑:我可能误解了这个问题;如果你的观点是你想要特定的布局 - 那么请看stack overflow的{​​{1}}提示。

method invalidSort(a : array<int>) modifies a; requires a != null; ensures sorted(a[..]); { a := new int[0]; } 可能是最简单的选择:

FieldOffset

使用:

unsafe

但是,它不是很通用。如果您需要更多通用代码:using System; using System.Runtime.InteropServices; static class P { private static unsafe void Main() { MyStruct orig = new MyStruct { a = 1, b = 2, c = 3, d = 4 }; byte[] raw = new byte[sizeof(MyStruct)]; // write the empty array to prove it is empty Console.WriteLine(BitConverter.ToString(raw)); // serialize fixed (byte* p = raw) { var typed = (MyStruct*)p; *typed = orig; } // write the serialized data Console.WriteLine(BitConverter.ToString(raw)); // deserialize MyStruct clone; fixed (byte* p = raw) { var typed = (MyStruct*)p; clone = *typed; } Console.WriteLine($"a = {clone.a}, b = {clone.b}, c = {clone.c}, d = {clone.d}"); } } 类型有许多实用方法可用于此类事务。

答案 2 :(得分:0)

如果您不介意一些手动工作,您可以编写自己的转换器并以不同方式处理每种数据类型,例如:

public static class StructSerializer
{
    public static byte[] Serialize<T>(T data) where T : struct
    {
        List<byte> result = new List<byte>();
        Type type = data.GetType();
        IEnumerable<FieldInfo> orderedFields = type.GetFields().OrderBy(f => Marshal.OffsetOf(type, f.Name).ToInt32());

        foreach (FieldInfo fieldInfo in orderedFields)
        {
            object value = fieldInfo.GetValue(data);
            MethodInfo conversion = typeof(BitConverter).GetMethod(nameof(BitConverter.GetBytes), new[]{fieldInfo.FieldType});
            if (conversion == null) continue;
            byte[] converted = (byte[])conversion.Invoke(null, new []{value});
            result.AddRange(converted);
        }

        return result.ToArray();
    }
}

你没有特别提到它,但要记住ByteOrder。您可以使用BitConverter.IsLittleEndian检查字节顺序。

答案 3 :(得分:0)

感谢所有人的宝贵回答/评论!我找到了一个解决方法,我将手动删除填充

        static byte[] getBytes<T>(T str) where T : struct
    {

        int size = Marshal.SizeOf(typeof(T));
        int Pack = str.GetType().StructLayoutAttribute.Pack;
        byte[] arr = new byte[size];
        IntPtr ptr = Marshal.AllocHGlobal(size);
        Marshal.StructureToPtr(str, ptr, true);
        Marshal.Copy(ptr, arr, 0, size);
        Marshal.FreeHGlobal(ptr);
        arr = RemovePadding<T>(arr.ToList(), str, str.GetType().StructLayoutAttribute.Pack);
        return arr;
    }
    static byte[] RemovePadding<T>(List<byte> buffer, T str, int Pack)
    {
        int largestsize = 0;
        int index = 0;
        int RowOfMemory = 0;

        //Get all fields 
        var fields = str.GetType().GetFields();

        //After MSDN
        //The alignment of the type is the size of its largest element (1, 2, 4, 8, etc., bytes) or the specified packing size, whichever is smaller.
        foreach (var item in fields)
        {
            if (Marshal.SizeOf(item.FieldType) > largestsize)
                largestsize = Marshal.SizeOf(item.FieldType);
        }
        if (largestsize < Pack) Pack = largestsize;

        //Find and remove padding from all memory rows 
        foreach (var item in fields)
        {
            int size = Marshal.SizeOf(item.FieldType);
            if (RowOfMemory != 0 && (RowOfMemory + size) > Pack)
            {
                int paddingsize = Math.Abs(Pack - RowOfMemory);
                buffer.RemoveRange(index, paddingsize);
                RowOfMemory = size % Pack;
            }
            else if ((RowOfMemory + size) < Pack)
            {
                RowOfMemory += size;
            }
            else if ((RowOfMemory + size) == Pack)
            {
                RowOfMemory = 0;
            }
            index += size;
        }
        if (RowOfMemory != 0)
        {
            int paddingsize = Math.Abs(Pack - RowOfMemory);
            buffer.RemoveRange(index, paddingsize);
        }
        return buffer.ToArray();
    }

我不会将此视为正确的答案,可能会有人找到更好的解决方案。