【问题标题】:C# unsafe value type array to byte array conversionsC# 不安全值类型数组到字节数组的转换
【发布时间】:2010-10-11 21:53:42
【问题描述】:

我使用扩展方法将浮点数组转换为字节数组:

public static unsafe byte[] ToByteArray(this float[] floatArray, int count)
{
    int arrayLength = floatArray.Length > count ? count : floatArray.Length;
    byte[] byteArray = new byte[4 * arrayLength];
    fixed (float* floatPointer = floatArray)
    {
        fixed (byte* bytePointer = byteArray)
        {
            float* read = floatPointer;
            float* write = (float*)bytePointer;
            for (int i = 0; i < arrayLength; i++)
            {
                *write++ = *read++;
            }
        }
    }
    return byteArray;
}

我了解数组是指向与元素类型和数量信息相关联的内存的指针。此外,在我看来,如果不复制上述数据,就无法在字节数组之间进行转换。

我明白了吗?难道不复制数据就可以编写 IL 来根据指针、类型和长度创建数组吗?

编辑:感谢您的回答,我学到了一些基础知识并开始尝试新技巧!

在最初接受 Davy Landman 的回答后,我发现虽然他出色的 StructLayout hack 确实将字节数组转换为浮点数组,但反过来却行不通。演示:

[StructLayout(LayoutKind.Explicit)]
struct UnionArray
{
    [FieldOffset(0)]
    public Byte[] Bytes;

    [FieldOffset(0)]
    public float[] Floats;
}

static void Main(string[] args)
{
    // From bytes to floats - works
    byte[] bytes = { 0, 1, 2, 4, 8, 16, 32, 64 };
    UnionArray arry = new UnionArray { Bytes = bytes };
    for (int i = 0; i < arry.Bytes.Length / 4; i++)
        Console.WriteLine(arry.Floats[i]);

    // From floats to bytes - index out of range
    float[] floats = { 0.1f, 0.2f, 0.3f };
    arry = new UnionArray { Floats = floats };
    for (int i = 0; i < arry.Floats.Length * 4; i++)
        Console.WriteLine(arry.Bytes[i]);
}

似乎 CLR 认为这两个数组具有相同的长度。如果结构是从浮点数据创建的,则字节数组的长度太短了。

【问题讨论】:

  • 除了问题,如果你使用 Math.Min(count, floatArray.Length),你的第一句话会更清楚

标签: c# arrays pointers type-conversion unsafe


【解决方案1】:

您可以使用非常丑陋的 hack 使用内存操作将数组临时更改为 byte[]。

这真的非常快速和高效,因为它不需要克隆数据并对其进行迭代。

我在 32 位和 64 位操作系统中测试了这个 hack,所以它应该是可移植的。

源代码+示例用法保持在https://gist.github.com/1050703,但为了您的方便,我也将其粘贴在这里:

public static unsafe class FastArraySerializer
{
    [StructLayout(LayoutKind.Explicit)]
    private struct Union
    {
        [FieldOffset(0)] public byte[] bytes;
        [FieldOffset(0)] public float[] floats;
    }

    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    private struct ArrayHeader
    {
        public UIntPtr type;
        public UIntPtr length;
    }

    private static readonly UIntPtr BYTE_ARRAY_TYPE;
    private static readonly UIntPtr FLOAT_ARRAY_TYPE;

    static FastArraySerializer()
    {
        fixed (void* pBytes = new byte[1])
        fixed (void* pFloats = new float[1])
        {
            BYTE_ARRAY_TYPE = getHeader(pBytes)->type;
            FLOAT_ARRAY_TYPE = getHeader(pFloats)->type;
        }
    }

    public static void AsByteArray(this float[] floats, Action<byte[]> action)
    {
        if (floats.handleNullOrEmptyArray(action)) 
            return;

        var union = new Union {floats = floats};
        union.floats.toByteArray();
        try
        {
            action(union.bytes);
        }
        finally
        {
            union.bytes.toFloatArray();
        }
    }

    public static void AsFloatArray(this byte[] bytes, Action<float[]> action)
    {
        if (bytes.handleNullOrEmptyArray(action)) 
            return;

        var union = new Union {bytes = bytes};
        union.bytes.toFloatArray();
        try
        {
            action(union.floats);
        }
        finally
        {
            union.floats.toByteArray();
        }
    }

    public static bool handleNullOrEmptyArray<TSrc,TDst>(this TSrc[] array, Action<TDst[]> action)
    {
        if (array == null)
        {
            action(null);
            return true;
        }

        if (array.Length == 0)
        {
            action(new TDst[0]);
            return true;
        }

        return false;
    }

    private static ArrayHeader* getHeader(void* pBytes)
    {
        return (ArrayHeader*)pBytes - 1;
    }

    private static void toFloatArray(this byte[] bytes)
    {
        fixed (void* pArray = bytes)
        {
            var pHeader = getHeader(pArray);

            pHeader->type = FLOAT_ARRAY_TYPE;
            pHeader->length = (UIntPtr)(bytes.Length / sizeof(float));
        }
    }

    private static void toByteArray(this float[] floats)
    {
        fixed(void* pArray = floats)
        {
            var pHeader = getHeader(pArray);

            pHeader->type = BYTE_ARRAY_TYPE;
            pHeader->length = (UIntPtr)(floats.Length * sizeof(float));
        }
    }
}

而用法是:

var floats = new float[] {0, 1, 0, 1};
floats.AsByteArray(bytes =>
{
    foreach (var b in bytes)
    {
        Console.WriteLine(b);
    }
});

【讨论】:

  • 这里有一个更新、更便携的“一年后”版本:stackoverflow.com/a/3577253/11545。 @Omer - 也许也更新这个答案?
  • 谢谢! :) +1 顺便说一句,您应该为该代码添加许可证。编码恐怖explains why
  • 你是对的。我在要点中添加了一个 FreeBSD 许可证。 SO中的代码已经有许可证。检查页脚:“在 cc-wiki 下获得许可的用户贡献需要署名”
【解决方案2】:

是的,类型信息和数据在同一个内存块中,所以这是不可能的,除非你覆盖浮点数组中的类型信息来欺骗系统它是字节数组。那将是一个非常丑陋的黑客攻击,并且很容易被炸毁......

如果您愿意,可以使用以下方法在不使用不安全代码的情况下转换浮点数:

public static byte[] ToByteArray(this float[] floatArray) {
    int len = floatArray.Length * 4;
    byte[] byteArray = new byte[len];
    int pos = 0;
    foreach (float f in floatArray) {
        byte[] data = BitConverter.GetBytes(f);
        Array.Copy(data, 0, byteArray, pos, 4);
        pos += 4;
    }
    return byteArray;
}

【讨论】:

  • 如果您对建议的 hack 感兴趣,请查看下面我的回答中的实现:stackoverflow.com/questions/621493/…
  • 使用Buffer.BlockCopy(floatArray, 0, data, 0, data.Length) 可能比使用foreachBitConverter 更快。
【解决方案3】:

这个问题与What is the fastest way to convert a float[] to a byte[]?相反。

我已回复union kind of hack 跳过整个数据复制。你可以很容易地反转这个 (length = length *sizeof(Double)。

【讨论】:

    【解决方案4】:

    我为数组之间的快速转换写了一些类似的东西。它基本上是一个丑陋的概念验证,而不是一个漂亮的解决方案。 ;)

    public static TDest[] ConvertArray<TSource, TDest>(TSource[] source)
        where TSource : struct
        where TDest : struct {
    
        if (source == null)
            throw new ArgumentNullException("source");
    
            var sourceType = typeof(TSource);
            var destType = typeof(TDest);
    
            if (sourceType == typeof(char) || destType == typeof(char))
                throw new NotSupportedException(
                    "Can not convert from/to a char array. Char is special " +
                    "in a somewhat unknown way (like enums can't be based on " +
                    "char either), and Marshal.SizeOf returns 1 even when the " +
                    "values held by a char can be above 255."
                );
    
            var sourceByteSize = Buffer.ByteLength(source);
            var destTypeSize = Marshal.SizeOf(destType);
            if (sourceByteSize % destTypeSize != 0)
                throw new Exception(
                    "The source array is " + sourceByteSize + " bytes, which can " +
                    "not be transfered to chunks of " + destTypeSize + ", the size " +
                    "of type " + typeof(TDest).Name + ". Change destination type or " +
                    "pad the source array with additional values."
                );
    
            var destCount = sourceByteSize / destTypeSize;
            var destArray = new TDest[destCount];
    
            Buffer.BlockCopy(source, 0, destArray, 0, sourceByteSize);
    
            return destArray;
        }
    }
    

    【讨论】:

      【解决方案5】:
          public byte[] ToByteArray(object o)
          {
              int size = Marshal.SizeOf(o);
              byte[] buffer = new byte[size];
              IntPtr p = Marshal.AllocHGlobal(size);
              try
              {
                  Marshal.StructureToPtr(o, p, false);
                  Marshal.Copy(p, buffer, 0, size);
              }
              finally
              {
                  Marshal.FreeHGlobal(p);
              }
              return buffer;
          }
      

      这可以帮助您将对象转换为字节数组。

      【讨论】:

        【解决方案6】:

        您应该查看我对类似问题的回答:What is the fastest way to convert a float[] to a byte[]?

        您会在其中找到可移植代码(兼容 32/64 位),让您可以将浮点数组视为字节数组或反之亦然,而无需复制数据。这是我所知道的最快的方法。

        如果您只是对代码感兴趣,请在https://gist.github.com/1050703 维护。

        【讨论】:

          【解决方案7】:

          好吧 - 如果你仍然对那个 hack 感兴趣 - 看看这个修改过的代码 - 它就像一个魅力并且花费大约 0 时间,但它可能在未来无法工作,因为它是一个允许完全访问整个过程的 hack没有信任要求和不安全标记的地址空间。

              [StructLayout(LayoutKind.Explicit)]
              struct ArrayConvert
              {
                  public static byte[] GetBytes(float[] floats)
                  {
                      ArrayConvert ar = new ArrayConvert();
                      ar.floats = floats;
                      ar.length.val = floats.Length * 4;
                      return ar.bytes;
                  }
                  public static float[] GetFloats(byte[] bytes)
                  {
                      ArrayConvert ar = new ArrayConvert();
                      ar.bytes = bytes;
                      ar.length.val = bytes.Length / 4;
                      return ar.floats;
                  }
          
                  public static byte[] GetTop4BytesFrom(object obj)
                  {
                      ArrayConvert ar = new ArrayConvert();
                      ar.obj = obj;
                      return new byte[]
                      {
                          ar.top4bytes.b0,
                          ar.top4bytes.b1,
                          ar.top4bytes.b2,
                          ar.top4bytes.b3
                      };
                  }
                  public static byte[] GetBytesFrom(object obj, int size)
                  {
                      ArrayConvert ar = new ArrayConvert();
                      ar.obj = obj;
                      ar.length.val = size;
                      return ar.bytes;
                  }
          
                  class ArrayLength
                  {
                      public int val;
                  }
                  class Top4Bytes
                  {
                      public byte b0;
                      public byte b1;
                      public byte b2;
                      public byte b3;
                  }
          
                  [FieldOffset(0)]
                  private Byte[] bytes;
                  [FieldOffset(0)]
                  private object obj;
                  [FieldOffset(0)]
                  private float[] floats;
          
                  [FieldOffset(0)]
                  private ArrayLength length;
          
                  [FieldOffset(0)]
                  private Top4Bytes top4bytes;
              }
          

          【讨论】:

          • 这些黑客正在破坏内部垃圾收集器的数据结构。它将导致与 C++ 中的 use-after-free 相同的类的间歇性崩溃、数据损坏和安全错误。 .NET 运行时绝对不支持像这样破解内部垃圾收集器数据结构。 github.com/HelloKitty/Reinterpret.Net/issues/1 对这次黑客攻击将导致的崩溃进行了长时间的讨论
          猜你喜欢
          • 2010-11-26
          • 2016-02-12
          • 2015-07-23
          • 2014-12-19
          • 2014-08-30
          • 2014-02-22
          • 2017-06-27
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多