views:

5162

answers:

5

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.

+2  A: 

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;
}
Guffa
And if you're interested in the suggested hack, check out the implementation in my answer below:http://stackoverflow.com/questions/621493/c-unsafe-value-type-array-to-byte-array-conversions/3577227#3577227
Omer Mor
+3  A: 

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

Davy Landman
+1  A: 

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;
    }
}
Simon Svensson
+1  A: 

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;
    }
Mash
+1  A: 

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.

Here is how you do it:

public static class ArrayCaster
{
    [StructLayout(LayoutKind.Explicit)]
    private struct Union
    {
        [FieldOffset(0)] public byte[] bytes;
        [FieldOffset(0)] public float[] floats;
    }
    private static readonly int byteId;
    private static readonly int floatId;

    static ArrayCaster()
    {
        byteId = getByteId();
        floatId = getFloatId();
    }

    public static void AsByteArray(this float[] floats, Action<byte[]> action)
    {
        if(floats == null)
        {
            action(null);
            return;
        }
        if(floats.Length == 0)
        {
            action(new byte[0]);
            return;
        }

        var union = new Union {floats = floats};

        union.bytes.fixArray(union.floats.Length * sizeof(float), byteId);
        try
        {
            action(union.bytes);
        }
        finally
        {
            union.bytes.fixArray(union.bytes.Length / sizeof(float), floatId);
        }
    }

    private static unsafe void fixArray(this byte[] bytes, int newSize, int newId)
    {
        fixed (byte* pBytes = bytes)
        {
            var pSize = (int*)(pBytes - 4);
            var pId = (int*)(pBytes - 8);

            *pSize = newSize;
            *pId = newId;
        }
    }

    private static unsafe int getByteId()
    {
        fixed (byte* pBytes = new byte[1])
        {
            return *(int*)(pBytes - 8);
        }
    }

    private static unsafe int getFloatId()
    {
        fixed (float* pFloats = new float[1])
        {
            var pBytes = (byte*) pFloats;
            return *(int*)(pBytes - 8);
        }
    }
}

And the usage is:

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