【问题标题】:How to convert a structure to a byte array in C#?如何在 C# 中将结构转换为字节数组?
【发布时间】:2011-03-17 18:27:06
【问题描述】:

如何在 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);
}

代码 sn-p 是什么?

【问题讨论】:

  • 最后一行修正MyPing.Send(以字节数组为参数);是 Send 而不是 SendTo……
  • 嗨Petar,我没听懂你...
  • 接受您之前问题的一些答案可能会很好。
  • 我怀疑对您期望的输出更具体一点会有所帮助;有很多方法可以将其转换为 byte[]... 我们可能可以对其中的大部分做出一些假设,即您想要字段的字段顺序网络字节顺序固定大小表示 - 但是呢?字符串?
  • 如果选择 Marshall 选项,请注意 Grand Endian 和 Little Endian 以及大约 32 位 / 64 位。

标签: c# struct


【解决方案1】:

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

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

那个页面上也有一个很好的例子。

【讨论】:

    【解决方案2】:

    看起来像是一些外部库的预定义(C 级)结构。元帅是你的朋友。检查:

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

    对于初学者如何处理这个问题。请注意,您可以 - 使用属性 - 定义字节布局和字符串处理等内容。非常好的方法,实际上。

    BinaryFormatter 和 MemoryStream 都不是为此而设计的。

    【讨论】:

      【解决方案3】:

      看看这些方法:

      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);
      }
      

      这是我在谷歌上找到的另一个帖子的无耻副本!

      更新:更多详情,请查看source

      【讨论】:

      • 我已经使用 Marshalling 将结构转换为字节数组,现在如何检查是否从套接字获得响应?如何检查?
      • @Alastair,我错过了!谢谢你的指点。我已经更新了我的答案。
      • 此选项取决于平台 - 注意 Grand Endian 和 Little Endian 以及大约 32 位 / 64 位。
      • @Abdel,-1 不见了 :)
      • 执行 Alloc,将中间位包装在 try 中,然后将 Free 放入 finally 中是否有意义?事情似乎不太可能失败,但如果失败了,内存是否会被释放?
      【解决方案4】:

      这很容易,使用编组。

      文件顶部

      using System.Runtime.InteropServices
      

      功能

      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

      【讨论】:

      • 谢谢文塞特。 GetBytes() 应该在发送 byte[] 后调用??和 frombytes() 方法正在发送字节?我有点糊涂哥们?
      • GetBytes 从您的结构转换为数组。 FromBytes 将字节转换回您的结构。从函数签名中可以看出这一点。
      • @Swapnil 这是另一个问题,您应该单独提出。您应该考虑完成一些关于套接字的 CE 教程。只需搜索 Google。
      • 在您的 fromBytes 方法中,无需两次分配 CIFSPacket。 Marshal.SizeOf 很乐意将 Type 作为参数,而 Marshal.PtrToStructure 会分配一个新的托管对象。
      • 请注意,在某些情况下,函数 «StructureToPtr» 会引发异常。这可以通过将«false»而不是«true»传递给Marshal.StructureToPtr(str, ptr, false);来解决。但需要提到的是,我使用的是封装到泛型的函数,不过……
      【解决方案5】:

      您可以使用 Marshal (StructureToPtr, ptrToStructure) 和 Marshal.copy,但这取决于平台。


      序列化包括自定义序列化的函数。

      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)
      

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

      【讨论】:

        【解决方案6】:

        这可以非常简单地完成。

        [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);
        

        【讨论】:

        • 在对固定大小的集合执行显式排序操作时,您可能应该使用数组和 for 循环。数组是因为它是固定大小的,而 for 循环是因为 foreach 不能保证按照您期望的顺序排列,除非您知道列表类型的底层实现及其枚举数,并且它永远不会改变。例如,可以将枚举器定义为从末尾开始并向后移动。
        【解决方案7】:

        如果您真的希望它在 Windows 上快速,您可以使用带有 CopyMemory 的不安全代码来实现。 CopyMemory 大约快 5 倍(例如,通过编组复制 800MB 的数据需要 3 秒,而通过 CopyMemory 复制只需 0.6 秒)。此方法确实限制您仅使用实际存储在 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;
            }
        

        【讨论】:

        • 提醒那些正在阅读此答案的人。这不是跨平台友好的(它仅使用 Windows kernel32.dll)。但话又说回来,它是在 2014 年写的。:)
        • 加上要求结构是顺序的。
        • 但是,如果在 Windows 上,这实际上会更快吗?
        【解决方案8】:

        @Abdel Olakara 回答 donese 在 .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);
            }
        

        【讨论】:

          【解决方案9】:
                  Header header = new Header();
                  Byte[] headerBytes = new Byte[Marshal.SizeOf(header)];
                  Marshal.Copy((IntPtr)(&header), headerBytes, 0, headerBytes.Length);
          

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

          【讨论】:

          • GCHandle 版本要好得多。
          【解决方案10】:

          由于主要答案是使用 CIFSPacket 类型,它在 C# 中不可用(或不再可用),所以我编写了正确的方法:

              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;
              }
          

          经过测试,它们有效。

          【讨论】:

            【解决方案11】:

            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 来“固定”内存,然后直接使用h.AddrOfPinnedObject() 的地址。

            【讨论】:

            • 应该删除where T : struct,否则它会抱怨T被通过不是non-nullable type
            • GCHandle.Alloc 如果结构具有不可 blittable 数据,则将失败,例如一个数组
            • @joe 你是对的。代码是为给定的结构编写的,它只包含 blittable 类型和string
            【解决方案12】:

            这里的例子只适用于纯 blittable 类型,例如,可以直接在 C 中 memcpy 的类型。

            示例 - 众所周知的 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 想知道如何转换结构本身,我们的体素结构可以有以下方法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();
            

            【讨论】:

              【解决方案13】:

              我想出了一种不同的方法,可以转换 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 时始终为我们提供相同的顺序。我的灵感来自TLVType-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 本身的长度,唯一的开销只是结构中每个字段的 intBinaryFormatter 生成的字节数组内部也有一点点开销,但除此之外并不多。

              【讨论】:

              • 一般来说,当人们试图处理这些东西时,他们也会关心序列化性能。理论上,任何结构数组都可以重新解释为字节数组,而无需进行昂贵的序列化和复制。
              【解决方案14】:

              我知道这已经很晚了,但是在 C# 7.3 中,您可以对非托管结构或其他任何非托管结构(int、bool 等)执行此操作:

              public static unsafe byte[] ConvertToBytes<T>(T value) where T : unmanaged {
                      byte* pointer = (byte*)&value;
              
                      byte[] bytes = new byte[sizeof(T)];
                      for (int i = 0; i < sizeof(T); i++) {
                          bytes[i] = pointer[i];
                      }
              
                      return bytes;
                  }
              

              然后像这样使用:

              struct MyStruct {
                      public int Value1;
                      public int Value2;
                      //.. blah blah blah
                  }
              
                  byte[] bytes = ConvertToBytes(new MyStruct());
              

              【讨论】:

                【解决方案15】:

                这里几乎所有的答案都使用Marshal.StructureToPtr,这可能对 P/Invoke 有好处,但速度很慢,甚至并不总是代表值的实际原始内容。 @Varscott128 的答案要好得多,但它也包含显式的字节复制,这不是必需的。

                对于非托管结构(没有托管引用的结构),您只需重新解释分配的结果数组,这样一个简单的赋值就可以解决问题(即使对于巨大的结构也有效):

                .NET(核心)解决方案:

                如果您可以使用Unsafe 类,那么解决方案真的很简单。 unsafe 修饰符是必需的,因为sizeof(T)

                public static unsafe byte[] SerializeValueType<T>(in T value) where T : unmanaged
                {
                    byte[] result = new byte[sizeof(T)];
                    Unsafe.As<byte, T>(ref result[0]) = value;
                    return result;
                }
                
                // Note: Validation is omitted for simplicity
                public static T DeserializeValueType<T>(byte[] data) where T : unmanaged
                    => return Unsafe.As<byte, T>(ref data[0]);
                
                

                .NET 框架/标准解决方案:

                public static unsafe byte[] SerializeValueType<T>(in T value) where T : unmanaged
                {
                    byte[] result = new byte[sizeof(T)];
                    fixed (byte* dst = result)
                        *(T*)dst = value;
                    return result;
                }
                
                // Note: Validation is omitted for simplicity
                public static unsafe T DeserializeValueType<T>(byte[] data) where T : unmanaged
                {
                    fixed (byte* src = data)
                        return *(T*)src;
                }
                

                查看带有验证的完整代码here

                备注:

                OP 的示例包含一个string,它是一个引用类型,因此上述解决方案不能用于此。如果由于某种原因你不能使用泛型方法,事情就会变得更加复杂,特别是对于.NET Framework(但非泛型大小计算也是在核心平台上的pain)。如果性能无关紧要,那么您可以按照其他几个答案的建议恢复到Marshal.SizeOfStructureToPtr,或者随意使用我的library 中的BinarySerializer.SerializeValueType 方法,我也为上面的示例链接了该方法(@ 987654326@).

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2015-11-18
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多