6

I have byte array:

byte[] alldata = new byte[1024];

then I need to convert this data to UInt32:

UInt32[] block32 = new UInt32[(int)Math.Ceiling((double)alldata.Length / 4)];
Buffer.BlockCopy(alldata, 0, block32, 0, alldata.Length);

after this I need to convert block32 back to byte array.

The question is whether I can have the block32 array just be a 32-bit reference array of my byte array to avoid converting to UInt32 and back?

Mike Perrenoud
  • 66,820
  • 29
  • 157
  • 232
Pablo
  • 28,133
  • 34
  • 125
  • 215
  • *Technically* you can hack this together by reinterpret casting the reference. But *please* don't. – CodesInChaos Jan 07 '13 at 11:43
  • Your question at the end doesnt make much sense.. you want a "32 bit reference to a byte array".. whats wrong with a `byte*`? – Simon Whitehead Jan 07 '13 at 11:43
  • @Simon Whitehead: I have some cryptographic functions that accepting `UInt32` array as an input. However, once encrypted I need to convert it back to byte array to send it over serial port. – Pablo Jan 07 '13 at 11:45
  • In C/C++ it's as easy as `crypt((uint32_t *)block8, 2, key)` – Pablo Jan 07 '13 at 11:46
  • 1
    One problem with low level techniques (Pointers, `BlockCopy`,...) is that they use native endianness, and thus produce different results on different systems. If that's not a problem, just use `BlockCopy` or pointers. Don't try using a byte array as an integer array. – CodesInChaos Jan 07 '13 at 11:47
  • 1
    In (unsafe) C# it's just as easy as in C, but the `crypto` function needs to take a `UInt32*`, just like in C. Reinterpret-casting `byte[]` to `UInt32[]` in C# is similar to reinterpret casting `Vector` to `Vector` i.e. a really bad idea. – CodesInChaos Jan 07 '13 at 11:50
  • Vector and byte array are not same... – Pablo Jan 07 '13 at 11:53
  • @Pablo They're quite similar in some aspects. An array in .Net doesn't contain just the elements, it also contains the array size and the element type (and few other fields, that are in all objects). – svick Jan 07 '13 at 12:39
  • **Will you do anything with the `uint[]` except BlockCopy?** – Ken Kin Jan 07 '13 at 13:06
  • @Ken Kin: yes, will encrypt, as I already mentioned above. – Pablo Jan 07 '13 at 13:07

3 Answers3

12

Your question isn't entirely clear - but a reference is only ever to a single object, and an array object "knows" its real type... you can't just treat a uint[] as if it were a byte[] or vice versa.

Now what you could do is hide that behind another object. You could have a single byte array which is shared between an ByteArrayView and an UInt32ArrayView where those classes each know how to address a single underlying byte array. You'd have to write those classes yourself though - I don't believe they exist anywhere in the existing framework.

You could potentially create an abstract class or interface which those disparate classes would implement, too, e.g.

class ByteArrayView : IArrayView<byte>
class UInt32ArrayView : IArrayView<uint>

(Or just implement IList<T> appropriately, of course...)

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
1

I've reworked an answer I found here from Omer Mor. This basically uses unsafe code to temporarily change the type the array thinks it is.

On the plus side, you don't need to rewrite any of your other code to use unsafe pointers or wrapper classes. On the downside, this is not only unsafe code, but nasty unsafe code that messes with the type system.

static class ArrayCaster
{
    [StructLayout(LayoutKind.Explicit)]
    private struct Union
    {
        [FieldOffset(0)]
        public Byte[] Bytes;
        [FieldOffset(0)]
        public UInt32[] UInt32s;
    }

    static unsafe int UInt32Id
    {
        get
        {
            fixed (UInt32* pUints = new UInt32[1])
            {
                var pBytes = (Byte*)pUints;
                return *(int*)(pBytes - 8);
            }
        }
    }

    static unsafe void SetSizeAndId(
        byte[] bytes,
        int newSize, int newId,
        out int oldSize, out int oldId)
    {
        fixed (Byte* pBytes = bytes)
        {
            int* size = (int*)(pBytes - 4);
            oldSize = *size;
            *size = newSize;
            int* id = (int*)(pBytes - 8);
            oldId = *id;
            *id = newId;
        }
    }

    public static void WithBytesAsUInt32s(
        Byte[] bytes, Action<UInt32[]> withUints)
    {
        if (bytes.Length % sizeof(UInt32) != 0) throw new ArgumentException();

        Union u = new Union { Bytes = bytes };
        int origSize, origId;
        SetSizeAndId(bytes, bytes.Length / sizeof(UInt32), UInt32Id,
                     out origSize, out origId);
        try
        {
            withUints(u.UInt32s);
        }
        finally
        {
            SetSizeAndId(bytes, origSize, origId, out origSize, out origId);
        }
    }
}

In your code:

byte allData = new byte[1024];
ArrayCaster.WithBytesAsUInt32s(allData, uintArray => Encrypt(unitArray));

Edit: On the offchance that your encrypt/decrypt methods take an explicit length as well as the UInt32 array, you can cut out the unsafe code completely; just using the Union works fine except (a) the debugger inspector gets confused, and (b) the length doesn't update. If you can tell your methods to only use a quarter of the array, you're golden.

Community
  • 1
  • 1
Rawling
  • 49,248
  • 7
  • 89
  • 127
  • If I understand the code correctly, you're relying on implementation details of the CLR quite a lot. As such, I would try to avoid using code like this, if possible, since it may not work on other platforms (e.g. Mono) or future versions of the CLR. – svick Jan 07 '13 at 12:35
0

As you already mentioned you will use the uint[] for encryption.

Because of some reasons, you copy arrays with BlockCopy. But you want to access byte[] like an uint[], without converting or casting the entire array.

Then, what's something not enough? Is there something in C# can do this?

After think for one more hour, I guess what you want it for, should be the indexer.

Here's the code. It's simple.

public partial class UIntIndexer {
    static int ComputeElementCount(int size, int bytesCount) {
        var r=~0;
        return Math.DivRem(bytesCount, size, out r)+(r>0?1:0);
    }

    static int ComputeIndex(int index) {
        return sizeof(uint)*index;
    }

    public UIntIndexer(byte[] array) {
        m_Length=ComputeElementCount(sizeof(uint), (m_Array=array).Length);
    }

    public uint this[int index] {
        set {
            var startIndex=ComputeIndex(index);
            var bytes=BitConverter.GetBytes(value);
            var count=m_Length>1+index?sizeof(uint):m_Array.Length-startIndex;
            Buffer.BlockCopy(bytes, 0, m_Array, startIndex, count);
        }

        get {
            var startIndex=ComputeIndex(index);

            if(m_Length>1+index)
                return BitConverter.ToUInt32(m_Array, startIndex);
            else {
                var bytes=new byte[sizeof(uint)];
                Buffer.BlockCopy(m_Array, startIndex, bytes, 0, m_Array.Length-startIndex);
                return BitConverter.ToUInt32(bytes, 0);
            }
        }
    }

    public int Length {
        get {
            return m_Length;
        }
    }

    byte[] m_Array;
    int m_Length;
}

If thread safety should be concerned, you might need to keep the the source array synchronized. Following is the code for test it:

var alldata=new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
var block32=new byte[alldata.Length];
var uintIndexer=new UIntIndexer(block32);
Buffer.BlockCopy(alldata, 0, block32, 0, alldata.Length);

Debug.Print("uintIndexer.Length={0}", uintIndexer.Length);

for(var i=uintIndexer.Length; i-->0; )
    Debug.Print("uintIndexer[{0}]={1}", i, uintIndexer[i]);
Ken Kin
  • 4,503
  • 3
  • 38
  • 76