Question

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?

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(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.

Was it helpful?

Solution

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

OTHER TIPS

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

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.

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

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

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

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

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

You should check my answer to a similar question: What is the fastest way to convert a float[] to a 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.

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

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.

    [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;
    }
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top