C# 不安全值类型数组到字节数组的转换

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/621493/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-04 10:38:08  来源:igfitidea点击:

C# unsafe value type array to byte array conversions

c#arrayspointerstype-conversionunsafe

提问by Ville Koskinen

I use an extension method to convert float arrays into byte arrays:

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

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

I understand that an array is a pointer to memory associated with information on the type and number of elements. Also, it seems to me that there is no way of doing a conversion from and to a byte array without copying the data as above.

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

Have I understood this? Would it even be impossible to write IL to create an array from a pointer, type and length without copying data?

我明白了吗?在不复制数据的情况下,甚至不可能编写 IL 从指针、类型和长度创建数组吗?

EDIT:Thanks for the answers, I learned some fundamentals and got to try out new tricks!

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

After initially accepting Davy Landman's answer I found out that while his brilliant StructLayout hack does convert byte arrays into float arrays, it does not work the other way around. To demonstrate:

在最初接受戴维兰德曼的回答后,我发现虽然他出色的 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]);
}

It seems that the CLR sees both arrays as having the same length. If the struct is created from float data, the byte array's length is just too short.

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

采纳答案by Guffa

Yes, the type information and data is in the same memory block, so that is impossible unless you overwrite the type information in a float array to fool the system that it's byte array. That would be a really ugly hack, and could easily blow up...

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

Here's how you can convert the floats without unsafe code if you like:

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

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

回答by Davy Landman

This question is the reverse of What is the fastest way to convert a float[] to a byte[]?.

这个问题与将 float[] 转换为 byte[] 的最快方法什么相反.

I've answered with a union kind of hackto skip the whole copying of the data. You could easily reverse this (length = length *sizeof(Double).

我已经回答了一个联合类型的黑客来跳过整个数据复制。您可以轻松地将其反转(长度 = 长度 *sizeof(Double))。

回答by sisve

I've written something similar for quick conversion between arrays. It's basically an ugly proof-of-concept more than a handsome solution. ;)

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

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

回答by Mash

Well - if you still interested in that hack - check out this modified code - it works like a charm and costs ~0 time, but it may not work in future since it's a hack allowing to gain full access to the whole process address space without trust requirements and unsafe marks.

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

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

回答by Omer Mor

You can use a really ugly hack to temporary change your array to byte[] using memory manipulation.

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

This is really fast and efficient as it doesn't require cloning the data and iterating on it.

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

I tested this hack in both 32 & 64 bit OS, so it should be portable.

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

The source + sample usage is maintained at https://gist.github.com/1050703, but for your convenience I'll paste it here as well:

源代码 + 示例用法在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));
        }
    }
}

And the usage is:

用法是:

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

回答by Yaguang

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

this may help you to convert an object to a byte array.

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

回答by Omer Mor

You should check my answer to a similar question: What is the fastest way to convert a float[] to a byte[]?.

您应该查看我对类似问题的回答:将 float[] 转换为 byte[] 的最快方法是什么?.

In it you'll find portable code (32/64 bit compatible) to let you view a float array as a byte array or vice-versa, without copying the data. It's the fastest way that I know of to do such thing.

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

If you're just interested in the code, it's maintained at https://gist.github.com/1050703.

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