Копирование байтового массива в различные поля в классе / структуре в C # - PullRequest
6 голосов
/ 11 августа 2011

В приведенном ниже примере кода C # у меня есть байтовый массив, который был прочитан из сокета.Я хочу разобрать данные в различные поля 'exampleClass' (первые 8 байтов в 64-битную переменную 'field1', следующие 4 байта в 32-битную переменную 'field2' и т. Д.)

using System;
namespace CsByteCopy
{
  class Program
  {
    class ExampleClass
    {
      public UInt64 field1;
      public UInt32 field2;
      public UInt16 field3;
      public byte[] field4 = new byte[18];
    }

    static void Main(string[] args)
    {
      byte[] exampleData =
      {
        // These 8 bytes should go in 'field1'
        0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,
        // These 4 bytes should go in 'field2'
        0x08,0x09,0x0A,0x0B,
        // These 2 bytes should go in 'field3'
        0x0C,0x0D,
        // These 18 * 1 bytes should go in 'field4'
        0x0E,0x0F,0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1A,0x1B,0x1C,0x1D,0x1E,0x1F,
      };

      ExampleClass exampleClass = new ExampleClass();
      // Perform copy
    }
  }
}

Прошло много времени с тех пор, как я последний раз использовал C, но, если я правильно помню, я мог бы уйти с помощью одного вызова memcpy (), чтобы заполнить все поля в классе. Какой самый эффективный способ заполнения полей 'exampleClass' в C #?

Ответы [ 7 ]

14 голосов
/ 11 августа 2011

У вас есть много вариантов, тот, который оказывается лучшим, обычно зависит от того, что ваша программа нуждается / может обработать, и какой именно скорости вы хотите.Существует множество статей, в которых объясняются различные способы заполнения класса или структуры данными.

Двоичная сериализация - это один из способов.Для этого необходимо написать собственный сериализатор, что довольно просто.В статье MSDN о том, что показано, что это не так уж сложно.

Вы можете написать приватный конструктор, который принимает массив байтов и использует BinaryReader или Класс BitConverter для чтения из байтового массива (вам нужно будет обернуть его в MemoryStream для BinaryReader) или просто преобразовать секции байтового массива в нужные вам значения (BitConverter).В случае BitConverter вам также потребуется использовать Buffer.BlockCopy, чтобы скопировать оставшиеся данные байтового массива в поле байтового массива в вашем классе

Третий способ, также обычно самый быстрый, - этопреобразовать ваш класс в структуру и использовать небезопасный код для преобразования массива байтов в эту структуру.Примерно так:

unsafe struct ExampleClass
{
   public ulong field1;
   public uint field2
   public ushort field3
   public fixed byte field4[18];

   public static ExampleClass ReadStruct(byte[] data)
   {
       fixed (byte* pb = &data[0])
       {
           return *(ExampleClass*)pb;
       }
   }
}

Конечно, приведенный выше код действителен, только если вы можете использовать небезопасный код.Кроме того, преобразование класса в структуру также может быть не тем, что вы ищете.В большинстве случаев структуры передаются по значению в функции, поэтому вызывающие методы копируют всю структуру (в данном случае 32 байта) вместо передачи ссылки (4 или 8 байтов, в зависимости от архитектуры ЦП), и может снизить эффективность вашей программы.Существуют исключения для структур, передаваемых по значению, и вы можете использовать для этого свою любимую поисковую систему.

Если вы не можете использовать небезопасный код, есть также Marshal.PtrToStructure, который будет делать то же, что и вышекод, но примерно в 10 раз медленнее.Вам также потребуется использовать атрибут MarshalAs, чтобы указать размер массива, вместо использования фиксированного ключевого слова (что небезопасно).В этот момент вы также можете использовать BinaryReader / BitConverter, поскольку он будет быстрее класса маршала.

3 голосов
/ 11 августа 2011

Другой вариант, если вы можете использовать структуру, это маршалировать байтовый массив непосредственно в структуру.

struct ExampleStruct
{
    public UInt64 field1;
    public UInt32 field2;
    public UInt16 field3;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 18)]
    public byte[] field4;
}

И получить для этого структуру:

var handle = GCHandle.Alloc(exampleData, GCHandleType.Pinned);
var structure = Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof (ExampleStruct));
handle.Free();

И structure будет содержать ваши данные.

3 голосов
/ 11 августа 2011

Я думаю, что вы хотите это:

Здесь, изменив буфер в поле4, вы также измените другие 3, которые являются конкретными типами.

[StructLayout(LayoutKind.Explicit, Size=8)]
struct UValue
{
   [FieldOffset(0)]
   public fixed byte field4[18]; // 18 bytes long

   [FieldOffset(0)]
   public uint64 field1;

   [FieldOffset(8)]
   public Uint32 field2

   [FieldOffset(12)]
   public Uint16 field3

}

Возможно, вы захотите это:

Здесь, изменяя буфер в fieldload, вы меняете другие, как указано выше, но в конце есть еще один буфер байтов.

[StructLayout(LayoutKind.Explicit, Size=8)]
struct UValue
{
   [FieldOffset(0)]
   public fixed byte fieldload[38]; // modify this to modify others

   [FieldOffset(0)]
   public uint64 field1;

   [FieldOffset(8)]
   public Uint32 field2

   [FieldOffset(12)]
   public Uint16 field3

   [FieldOffset(14)]
   public fixed byte field4[18]; // 18 bytes long
}
1 голос
/ 01 мая 2015

А теперь что-то совершенно другое ...

Это на самом деле не отвечает на вопрос ОП; вместо этого это то, что я подготовил, чтобы обеспечить способ отображения структуры C # поверх байтового массива и позволить отдельным полям в базовом байтовом массиве быть как прочитанными, так и записанными. Он использует небезопасный код и получает указатель на байтовый массив, а затем преобразует его в указатель на структуру, которая отображает поля.

Это может быть не так уж и эффективно, но имеет преимущество в том, что обеспечивает символическое отображение полей без использования «магических чисел» для длин и смещений полей. Но учтите, что это не сработает для обмена данными с системой, в которой используется порядок с прямым порядком байтов вместо представления данных с прямым порядком байтов.

Методы доступа к полю реализованы как методы расширения. См. TestMethod () для примера того, как их использовать.

Это было вдохновлено ответом Кристофера Уокера - если вы находите это полезным, пожалуйста, дайте ему голос.

using System;
using System.Runtime.InteropServices;

namespace ConsoleApplication1
{
   [StructLayout(LayoutKind.Sequential, Pack = 1)]
   unsafe struct ExampleStruct
   {
      internal const int CField4Length = 18;

      public UInt64 Field1;
      public UInt32 Field2;
      public UInt16 Field3;
      public fixed byte Field4[CField4Length];
   }

   static unsafe class ExampleStructExtensionMethods
   {
      public static UInt64 GetField1(this byte[] byteArray)
      {
         fixed (byte* byteArrayPointer = &byteArray[0])
         {
            return (*(ExampleStruct*)byteArrayPointer).Field1;
         }
      }

      public static UInt32 GetField2(this byte[] byteArray)
      {
         fixed (byte* byteArrayPointer = &byteArray[0])
         {
            return (*(ExampleStruct*)byteArrayPointer).Field2;
         }
      }

      public static UInt16 GetField3(this byte[] byteArray)
      {
         fixed (byte* byteArrayPointer = &byteArray[0])
         {
            return (*(ExampleStruct*)byteArrayPointer).Field3;
         }
      }

      public static byte[] GetField4(this byte[] byteArray)
      {
         fixed (byte* byteArrayPointer = &byteArray[0])
         {
            byte[] field4 = new byte[ExampleStruct.CField4Length];
            for (int i = 0; i < ExampleStruct.CField4Length; i++)
               field4[i] = (*(ExampleStruct*)byteArrayPointer).Field4[i];

            return field4;
         }
      }

      public static void SetField1(this byte[] byteArray, UInt64 field1)
      {
         fixed (byte* byteArrayPointer = &byteArray[0])
         {
            (*(ExampleStruct*)byteArrayPointer).Field1 = field1;
         }
      }

      public static void SetField2(this byte[] byteArray, UInt32 field2)
      {
         fixed (byte* byteArrayPointer = &byteArray[0])
         {
            (*(ExampleStruct*)byteArrayPointer).Field2 = field2;
         }
      }

      public static void SetField3(this byte[] byteArray, UInt16 field3)
      {
         fixed (byte* byteArrayPointer = &byteArray[0])
         {
            (*(ExampleStruct*)byteArrayPointer).Field3 = field3;
         }
      }

      public static void SetField4(this byte[] byteArray, byte[] field4)
      {
         if (field4.Length != ExampleStruct.CField4Length)
            throw new ArgumentException("Byte array must have length 18", "field4");

         fixed (byte* byteArrayPointer = &byteArray[0])
         {
            for (int i = 0; i < ExampleStruct.CField4Length; i++)
               (*(ExampleStruct*)byteArrayPointer).Field4[i] = field4[i];
         }
      }
   }

   class TestProgram
   {
      byte[] exampleData =
      {
        // These 8 bytes should go in 'field1'
        0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,
        // These 4 bytes should go in 'field2'
        0x08,0x09,0x0A,0x0B,
        // These 2 bytes should go in 'field3'
        0x0C,0x0D,
        // These 18 * 1 bytes should go in 'field4'
        0x0E,0x0F,0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1A,0x1B,0x1C,0x1D,0x1E,0x1F,
      };

      public void TestMethod()
      {
         UInt64 field1 = exampleData.GetField1();
         UInt32 field2 = exampleData.GetField2();
         UInt16 field3 = exampleData.GetField3();
         byte[] field4 = exampleData.GetField4();

         exampleData.SetField1(++field1);
         exampleData.SetField2(++field2);
         exampleData.SetField3(++field3);
         exampleData.SetField4(new byte[ExampleStruct.CField4Length] 
           { 0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x42 });
      }
   }
}
1 голос
/ 11 августа 2011

Забудьте об эффективности и сделайте ваш код легким в обслуживании и читабельным.При необходимости выполните профилирование и улучшение.

Что не так с использованием класса BitConverter.

field1 = BitConverter.ToInt64(exampleData, 0)
field2 = BitConverter.ToInt32(exampleData, 8)
field3 = BitConverter.ToInt16(exampleData, 12)
Array.Copy(exampleData, 14, field4, 0, 18)
0 голосов
/ 25 августа 2018

Это может быть использовано, чтобы упорядочить массив байтов и повернуть порядок байтов. Удобно для сетевых сообщений, передаваемых из C. Оберните свои структуры в классе, чтобы передать их по ссылке.

using System;
using System.Runtime.InteropServices;
using System.Reflection;
using System.Net;

namespace ConsoleApp1
{
    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    struct MarshalMe
    {
        private UInt16 _param1;
        private UInt32 _param2;
        private UInt16 _param3;
        private UInt16 _param4;
        public ushort Param1 { get => _param1;  }
        public uint Param2 { get => _param2;  }
        public ushort Param3 { get => _param3; }
        public ushort Param4 { get => _param4; }
    }

    class Program
    {

        static void Main(string[] args)
        {

            byte[] bytes = new byte[] {0x00, 0x03, 0x00, 0x00,  0x00, 0x04, 0x00, 0x05, 0x00, 0x00 };

            var metoo = rotateStruct<MarshalMe>(stamp<MarshalMe>(bytes));

            Console.WriteLine("{0}-{1}-{2}", metoo.Param1, metoo.Param2, metoo.Param3);

            Console.ReadKey();
        }

        private static T stamp<T>(byte[] bytes)
        {
            var handle = GCHandle.Alloc(bytes, GCHandleType.Pinned);
            var structure = Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(T));
            handle.Free();

            return (T)structure;
        }

        private static T rotateStruct<T>(object value)
        {
            FieldInfo[] myFieldInfo;
            Type myType = typeof(T);
            // Get the type and fields of FieldInfoClass.
            myFieldInfo = myType.GetFields(BindingFlags.NonPublic | BindingFlags.Instance);

            foreach (var s in myFieldInfo)
            {
                if (s.FieldType.Name == "UInt16"){
                    UInt16 u16 = (ushort)s.GetValue(value);
                    u16 = (ushort)IPAddress.HostToNetworkOrder((short)u16);
                    s.SetValue(value,u16 );
                }
                else if(s.FieldType.Name == "UInt32")
                {
                    UInt32 u32 = (uint)s.GetValue(value);
                    u32 = (uint)IPAddress.HostToNetworkOrder((int)u32);
                    s.SetValue(value, (object)u32);
                }
                else if (s.FieldType.Name == "UInt64")
                {
                    UInt64 u64 = (ushort)s.GetValue(value);
                    u64 = (ulong)IPAddress.HostToNetworkOrder((long)u64);
                    s.SetValue(value, u64);
                }

            }

            return (T)value;
        }      

    }
}
0 голосов
/ 11 ноября 2017

Чтобы использовать классы вместо struct, вы можете использовать что-то вроде следующего.Я использовал различные элементы вышеупомянутых ответов и собрал их вместе для класса высокой производительности.Если вам не нравится все ручное кодирование методов Serialize / Deserialize, вы можете довольно легко написать генератор кода, который будет перебирать все поля / свойства и генерировать соответствующие методы.Вот код:

public interface ISerializableClass
{
    int SerializableClassSize { get; }
    StreamInflateTest Deserialize(byte[] buffer, int startOffset);
    byte[] Serialize(byte[] buffer, int startOffset);
}


public class   StreamInflateTest : ISerializableClass
{
    private const int _classSize = 10;
    public float Float32Value { get; set; }
    public Int32 Int32Value { get; set; }
    public byte Byte8Value { get; set; }
    public bool IsOk0 { get; set; }
    public bool IsOk1 { get; set; }
    public bool IsOk2 { get; set; }
    public bool IsOk3 { get; set; }
    public bool IsOk4 { get; set; }

    public StreamInflateTest()
    {
    }

    public int SerializableClassSize { get { return _classSize; } }
    public StreamInflateTest(byte[] buffer, int startOffset)
    {
        Deserialize(buffer, startOffset);
    }

    public unsafe StreamInflateTest Deserialize(byte[] buffer, int startOffset)
    {
        fixed (byte* pb = &buffer[startOffset])
        {
            Float32Value = *(float*)pb;
            Int32Value = *(int*)(pb + 4);
            Byte8Value = pb[8];
            BitField8 bitfld = new BitField8(pb[9]);
            IsOk0 = bitfld.Bit0;
            IsOk1 = bitfld.Bit1;
            IsOk2 = bitfld.Bit2;
            IsOk3 = bitfld.Bit3;
            IsOk4 = bitfld.Bit4;
        }

        return this;
    }
    public unsafe byte[] Serialize(byte[] buffer, int startOffset)
    {
        fixed (byte* pb = &buffer[startOffset])
        {
            *(float*)pb = Float32Value;
            *(int*)(pb + 4) = Int32Value;
            pb[8] = Byte8Value;
            BitField8 bitfld = new BitField8(0)
            {
                Bit0 = IsOk0,
                Bit1 = IsOk1,
                Bit2 = IsOk2,
                Bit3 = IsOk3,
                Bit4 = IsOk4
            };
            pb[9] = bitfld.Value;
        }

        return buffer;
    }
}

public struct BitField8
{
    public byte Value;

    public BitField8(byte value)
    {
        Value = value;
    }

    public bool Bit0
    {
        get { return (Value & 0x01) != 0; }
        set
        {
            if (value)
                Value |= 0x01;
            else
                Value = (byte)(Value & 0xFE);  // clear the bit
        }
    }
    public bool Bit1
    {
        get { return (Value & 0x02) != 0; }
        set
        {
            if (value)
                Value |= 0x02;
            else
                Value = (byte)(Value & 0xFD);  // clear the bit
        }
    }
    public bool Bit2
    {
        get { return (Value & 0x04) != 0; }
        set
        {
            if (value)
                Value |= 0x04;
            else
                Value = (byte)(Value & 0xFB);  // clear the bit
        }
    }
    public bool Bit3
    {
        get { return (Value & 0x08) != 0; }
        set
        {
            if (value)
                Value |= 0x08;
            else
                Value = (byte)(Value & 0xF7);  // clear the bit
        }
    }
    public bool Bit4
    {
        get { return (Value & 0x10) != 0; }
        set
        {
            if (value)
                Value |= 0x10;
            else
                Value = (byte)(Value & 0xEF);  // clear the bit
        }
    }
    public bool Bit5
    {
        get { return (Value & 0x20) != 0; }
        set
        {
            if (value)
                Value |= 0x20;
            else
                Value = (byte)(Value & 0xDF);  // clear the bit
        }
    }
    public bool Bit6
    {
        get { return (Value & 0x40) != 0; }
        set
        {
            if (value)
                Value |= 0x40;
            else
                Value = (byte)(Value & 0xBF);  // clear the bit
        }
    }
    public bool Bit7
    {
        get { return (Value & 0x80) != 0; }
        set
        {
            if (value)
                Value |= 0x80;
            else
                Value = (byte)(Value & 0x7F);  // clear the bit
        }
    }

    public bool Set(bool value, int bitNo)
    {
        if (bitNo > 7 || bitNo < 0)
            throw new ArgumentOutOfRangeException();

        if (value)
            Value |= (byte)(0x01 << bitNo);
        else
            Value = (byte)(Value & ~(0x01 << bitNo));  // clear the bit

        return value;
    }
    public bool Get(int bitNo)
    {
        if (bitNo > 7 || bitNo < 0)
            throw new ArgumentOutOfRangeException();

        return ((Value >> bitNo) & 0x01) != 0;
    }
    public bool this[int bitNo]
    {
        get { return Get(bitNo); }
        set { Set(value, bitNo); }
    }
}
...