首页 文章

如何在C#中将结构转换为字节数组?

提问于
浏览
68

如何在C#中将结构转换为字节数组?

我已经定义了这样的结构:

public struct CIFSPacket
{
    public uint protocolIdentifier; //The value must be "0xFF+'SMB'".
    public byte command;

    public byte errorClass;
    public byte reserved;
    public ushort error;

    public byte flags;

    //Here there are 14 bytes of data which is used differently among different dialects.
    //I do want the flags2. However, so I'll try parsing them.
    public ushort flags2;

    public ushort treeId;
    public ushort processId;
    public ushort userId;
    public ushort multiplexId;

    //Trans request
    public byte wordCount;//Count of parameter words defining the data portion of the packet.
    //From here it might be undefined...

    public int parametersStartIndex;

    public ushort byteCount; //Buffer length
    public int bufferStartIndex;

    public string Buffer;
}

在我的main方法中,我创建了一个实例并为其赋值:

CIFSPacket packet = new CIFSPacket();
packet.protocolIdentifier = 0xff;
packet.command = (byte)CommandTypes.SMB_COM_NEGOTIATE;
packet.errorClass = 0xff;
packet.error = 0;
packet.flags = 0x00;
packet.flags2 = 0x0001;
packet.multiplexId = 22;
packet.wordCount = 0;
packet.byteCount = 119;

packet.Buffer = "NT LM 0.12";

现在我想通过套接字发送这个数据包 . 为此,我需要将结构转换为字节数组 . 我该怎么做?

我的完整代码如下 .

static void Main(string[] args)
{

  Socket MyPing = new Socket(AddressFamily.InterNetwork,
  SocketType.Stream , ProtocolType.Unspecified ) ;


  MyPing.Connect("172.24.18.240", 139);

    //Fake an IP Address so I can send with SendTo
    IPAddress IP = new IPAddress(new byte[] { 172,24,18,240 });
    IPEndPoint IPEP = new IPEndPoint(IP, 139);

    //Local IP for Receiving
    IPEndPoint Local = new IPEndPoint(IPAddress.Any, 0);
    EndPoint EP = (EndPoint)Local;

    CIFSPacket packet = new CIFSPacket();
    packet.protocolIdentifier = 0xff;
    packet.command = (byte)CommandTypes.SMB_COM_NEGOTIATE;
    packet.errorClass = 0xff;
    packet.error = 0;
    packet.flags = 0x00;
    packet.flags2 = 0x0001;
    packet.multiplexId = 22;
    packet.wordCount = 0;
    packet.byteCount = 119;

    packet.Buffer = "NT LM 0.12";

    MyPing.SendTo(It takes byte array as parameter);
}

代码片段是什么?

13 回答

  • 1

    这很容易,使用编组 .

    Top of file

    using System.Runtime.InteropServices
    

    Function

    byte[] getBytes(CIFSPacket str) {
        int size = Marshal.SizeOf(str);
        byte[] arr = new byte[size];
    
        IntPtr ptr = Marshal.AllocHGlobal(size);
        Marshal.StructureToPtr(str, ptr, true);
        Marshal.Copy(ptr, arr, 0, size);
        Marshal.FreeHGlobal(ptr);
        return arr;
    }
    

    并将其转换回来:

    CIFSPacket fromBytes(byte[] arr) {
        CIFSPacket str = new CIFSPacket();
    
        int size = Marshal.SizeOf(str);
        IntPtr ptr = Marshal.AllocHGlobal(size);
    
        Marshal.Copy(arr, 0, ptr, size);
    
        str = (CIFSPacket)Marshal.PtrToStructure(ptr, str.GetType());
        Marshal.FreeHGlobal(ptr);
    
        return str;
    }
    

    在您的结构中,您需要将它放在字符串之前

    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 100)]
    public string Buffer;
    

    并确保SizeConst与您最大的字符串一样大 .

    你应该读这个:http://msdn.microsoft.com/en-us/library/4ca6d5z7.aspx

  • 0

    如果你真的希望它是快速的,你可以使用CopyMemory使用不安全的代码 . CopyMemory的速度提高了约5倍(例如800MB的数据通过编组复制需要3秒,而只需要通过CopyMemory复制.6s) . 此方法确实限制您仅使用实际存储在struct blob本身中的数据,例如数字或固定长度的字节数组 .

    [DllImport("kernel32.dll", EntryPoint = "CopyMemory", SetLastError = false)]
        private static unsafe extern void CopyMemory(void *dest, void *src, int count);
    
        private static unsafe byte[] Serialize(TestStruct[] index)
        {
            var buffer = new byte[Marshal.SizeOf(typeof(TestStruct)) * index.Length];
            fixed (void* d = &buffer[0])
            {
                fixed (void* s = &index[0])
                {
                    CopyMemory(d, s, buffer.Length);
                }
            }
    
            return buffer;
        }
    
  • 106

    看看这些方法:

    byte [] StructureToByteArray(object obj)
    {
        int len = Marshal.SizeOf(obj);
    
        byte [] arr = new byte[len];
    
        IntPtr ptr = Marshal.AllocHGlobal(len);
    
        Marshal.StructureToPtr(obj, ptr, true);
    
        Marshal.Copy(ptr, arr, 0, len);
    
        Marshal.FreeHGlobal(ptr);
    
        return arr;
    }
    
    void ByteArrayToStructure(byte [] bytearray, ref object obj)
    {
        int len = Marshal.SizeOf(obj);
    
        IntPtr i = Marshal.AllocHGlobal(len);
    
        Marshal.Copy(bytearray,0, i,len);
    
        obj = Marshal.PtrToStructure(i, obj.GetType());
    
        Marshal.FreeHGlobal(i);
    }
    

    这是我在谷歌上发现的另一个线程的无耻副本!

    Update :有关详细信息,请查看source

  • 5

    使用较少内存分配的Vicent代码的变体:

    public static byte[] GetBytes<T>(T str)
    {
        int size = Marshal.SizeOf(str);
    
        byte[] arr = new byte[size];
    
        GCHandle h = default(GCHandle);
    
        try
        {
            h = GCHandle.Alloc(arr, GCHandleType.Pinned);
    
            Marshal.StructureToPtr<T>(str, h.AddrOfPinnedObject(), false);
        }
        finally
        {
            if (h.IsAllocated)
            {
                h.Free();
            }
        }
    
        return arr;
    }
    
    public static T FromBytes<T>(byte[] arr) where T : struct
    {
        T str = default(T);
    
        GCHandle h = default(GCHandle);
    
        try
        {
            h = GCHandle.Alloc(arr, GCHandleType.Pinned);
    
            str = Marshal.PtrToStructure<T>(h.AddrOfPinnedObject());
    
        }
        finally
        {
            if (h.IsAllocated)
            {
                h.Free();
            }
        }
    
        return str;
    }
    

    我使用 GCHandle 到"pin"内存,然后直接使用 h.AddrOfPinnedObject() 的地址 .

  • 0

    由于主要答案是使用CIF中没有(或不再)可用的CIFSPacket类型,我编写了正确的方法:

    static byte[] getBytes(object str)
        {
            int size = Marshal.SizeOf(str);
            byte[] arr = new byte[size];
            IntPtr ptr = Marshal.AllocHGlobal(size);
    
            Marshal.StructureToPtr(str, ptr, true);
            Marshal.Copy(ptr, arr, 0, size);
            Marshal.FreeHGlobal(ptr);
    
            return arr;
        }
    
        static T fromBytes<T>(byte[] arr)
        {
            T str = default(T);
    
            int size = Marshal.SizeOf(str);
            IntPtr ptr = Marshal.AllocHGlobal(size);
    
            Marshal.Copy(arr, 0, ptr, size);
    
            str = (T)Marshal.PtrToStructure(ptr, str.GetType());
            Marshal.FreeHGlobal(ptr);
    
            return str;
        }
    

    经过测试,他们的工作 .

  • 0

    你可以使用Marshal(StructureToPtr,ptrToStructure)和Marshal.copy,但这是依赖于plataform的 .


    序列化包括自定义序列化的功能 .

    public virtual void GetObjectData(SerializationInfo info, StreamingContext context)
    Protected Sub New(ByVal info As SerializationInfo, ByVal context As StreamingContext)
    

    SerializationInfo包括序列化每个成员的函数 .


    BinaryWriter和BinaryReader还包含保存/加载到字节数组(流)的方法 .

    请注意,您可以从字节数组创建MemoryStream或从MemoryStream创建字节数组 .

    您可以在结构上创建方法Save和方法New:

    Save(Bw as BinaryWriter)
       New (Br as BinaryReader)
    

    然后选择要保存/加载到流的成员 - >字节数组 .

  • 0

    这可以非常直接地完成 .

    使用 [StructLayout(LayoutKind.Explicit)] 显式定义结构

    int size = list.GetLength(0);
    IntPtr addr = Marshal.AllocHGlobal(size * sizeof(DataStruct));
    DataStruct *ptrBuffer = (DataStruct*)addr;
    foreach (DataStruct ds in list)
    {
        *ptrBuffer = ds;
        ptrBuffer += 1;
    }
    

    此代码只能在不安全的上下文中编写 . 当你完成它时,你必须释放 addr .

    Marshal.FreeHGlobal(addr);
    
  • 22

    我想出了一种可以转换 any struct 的不同方法,而无需修复长度的麻烦,但是生成的字节数组会有更多的开销 .

    这是一个示例 struct

    [StructLayout(LayoutKind.Sequential)]
    public class HelloWorld
    {
        public MyEnum enumvalue;
        public string reqtimestamp;
        public string resptimestamp;
        public string message;
        public byte[] rawresp;
    }
    

    如您所见,所有这些结构都需要添加固定长度属性 . 这通常会占用比所需更多的空间 . 请注意, LayoutKind.Sequential 是必需的,因为我们希望反射总是在拉动 FieldInfo 时为我们提供相同的顺序 . 我的灵感来自 TLV Type-Length-Value . 我们来看看代码:

    public static byte[] StructToByteArray<T>(T obj)
    {
        using (MemoryStream ms = new MemoryStream())
        {
            FieldInfo[] infos = typeof(T).GetFields(BindingFlags.Public | BindingFlags.Instance);
            foreach (FieldInfo info in infos)
            {
                BinaryFormatter bf = new BinaryFormatter();
                using (MemoryStream inms = new MemoryStream()) {
    
                    bf.Serialize(inms, info.GetValue(obj));
                    byte[] ba = inms.ToArray();
                    // for length
                    ms.Write(BitConverter.GetBytes(ba.Length), 0, sizeof(int));
    
                    // for value
                    ms.Write(ba, 0, ba.Length);
                }
            }
    
            return ms.ToArray();
        }
    }
    

    上面的函数只是使用 BinaryFormatter 来序列化未知大小的原始 object ,我只是跟踪大小并将其存储在输出 MemoryStream 中 .

    public static void ByteArrayToStruct<T>(byte[] data, out T output)
    {
        output = (T) Activator.CreateInstance(typeof(T), null);
        using (MemoryStream ms = new MemoryStream(data))
        {
            byte[] ba = null;
            FieldInfo[] infos = typeof(T).GetFields(BindingFlags.Public | BindingFlags.Instance);
            foreach (FieldInfo info in infos)
            {
                // for length
                ba = new byte[sizeof(int)];
                ms.Read(ba, 0, sizeof(int));
    
                // for value
                int sz = BitConverter.ToInt32(ba, 0);
                ba = new byte[sz];
                ms.Read(ba, 0, sz);
    
                BinaryFormatter bf = new BinaryFormatter();
                using (MemoryStream inms = new MemoryStream(ba))
                {
                    info.SetValue(output, bf.Deserialize(inms));
                }
            }
        }
    }
    

    当我们想要将它转换回原来的 struct 时,我们只需将长度读回并直接将其转储回 BinaryFormatter ,然后将其转回 struct .

    这两个函数是通用的,应该适用于任何 struct ,我已经在我的 C# 项目中测试了上面的代码,我有一个服务器和一个客户端,连接并通过 NamedPipeStream 进行通信,我将 struct 作为字节数组从一个转发到另一个并将它转换回来 .

    我相信我的方法可能会更好,因为它不会修复 struct 本身的长度,并且对于结构中的每个字段,唯一的开销只是 int . 在 BinaryFormatter 生成的字节数组中也有一些微小的开销,但除此之外,并不多 .

  • 2

    我将看看BinaryReader和BinaryWriter类 . 我最近不得不将数据序列化为字节数组(并返回),并且在我基本上自己重写之后才发现这些类 .

    http://msdn.microsoft.com/en-us/library/system.io.binarywriter.aspx

    该页面上也有一个很好的例子 .

  • 12

    看起来像某个外部库的预定义(C级)结构 . 元帅是你的朋友 . 校验:

    http://geekswithblogs.net/taylorrich/archive/2006/08/21/88665.aspx

    对于首发如何处理有了这个 . 请注意,您可以 - 使用属性 - 定义字节布局和字符串处理等内容 . 实际上,非常好的方法 .

    BinaryFormatter Nor MemoryStream都没有为此完成 .

  • 0

    @Abdel Olakara回答在.net 3.5中不起作用,应修改如下:

    public static void ByteArrayToStructure<T>(byte[] bytearray, ref T obj)
        {
            int len = Marshal.SizeOf(obj);
            IntPtr i = Marshal.AllocHGlobal(len);
            Marshal.Copy(bytearray, 0, i, len);
            obj = (T)Marshal.PtrToStructure(i, typeof(T));
            Marshal.FreeHGlobal(i);
        }
    
  • 1
    Header header = new Header();
            Byte[] headerBytes = new Byte[Marshal.SizeOf(header)];
            Marshal.Copy((IntPtr)(&header), headerBytes, 0, headerBytes.Length);
    

    这应该很快就能解决问题吧?

  • 20

    此示例仅适用于纯blittable类型,例如,可以直接在C中记忆的类型 .

    示例 - 众所周知的64位结构

    [StructLayout(LayoutKind.Sequential)]  
    public struct Voxel
    {
        public ushort m_id;
        public byte m_red, m_green, m_blue, m_alpha, m_matid, m_custom;
    }
    

    定义与此完全相同,结构将自动打包为64位 .

    现在我们可以创建体素的体积:

    Voxel[,,] voxels = new Voxel[16,16,16];
    

    并将它们全部保存为字节数组:

    int size = voxels.Length * 8; // Well known size: 64 bits
    byte[] saved = new byte[size];
    GCHandle h = GCHandle.Alloc(voxels, GCHandleType.Pinned);
    Marshal.Copy(h.AddrOfPinnedObject(), saved, 0, size);
    h.Free();
    // now feel free to save 'saved' to a File / memory stream.
    

    但是,由于OP想要知道如何转换结构本身,我们的Voxel结构可以有以下方法 ToBytes

    byte[] bytes = new byte[8]; // Well known size: 64 bits
    GCHandle h = GCHandle.Alloc(this, GCHandleType.Pinned);
    Marshal.Copy(hh.AddrOfPinnedObject(), bytes, 0, 8);
    h.Free();
    

相关问题