Question

I've recently encountered a situation where I need to create a generic method to read a datatype out of a byte array.

I've created the following class:


public class DataStream
{
    public int Offset { get; set; }

    public byte[] Data { get; set; }

    public T Read<T>() where T : struct
    {
        unsafe
        {
            int dataLen = Marshal.SizeOf( typeof( T ) );
            IntPtr dataBlock = Marshal.AllocHGlobal( dataLen );


            Marshal.Copy( Data, Offset, dataBlock, dataLen );


            T type = *( ( T* )dataBlock.ToPointer() );

            Marshal.FreeHGlobal( dataBlock );

            Offset += dataLen;

            return type;
        }
    }
}

Now, de-allocation issues aside, this code doesn't compile with this message:

Cannot take the address of, get the size of, or declare a pointer to a managed type ('T')

Which, seems strange because you should be able to do the above operations based on the where T : struct constraint on the method.

If this code is horribly incorrect, is there any simple way to take a series of bytes and cast them into a 'T' type?

Thanks!

Was it helpful?

Solution

Instead of trying to do this via pointer manipulation, you should switch your code to use Mashal.PtrToStructure. This method is specifically designed for this scenario.

OTHER TIPS

Since answer has already been given, let me just explain why your original code didn't work for you:

Which, seems strange because you should be able to do the above operations based on the where T : struct constraint on the method.

Not really. You can have raw pointers to unmanaged types. This is defined as follows in the C# language spec (18.2):

Unlike references (values of reference types), pointers are not tracked by the garbage collector — the garbage collector has no knowledge of pointers and the data to which they point. For this reason a pointer is not permitted to point to a reference or to a struct that contains references, and the referent type of a pointer must be an unmanaged-type. An unmanaged-type is any type that isn’t a reference-type and doesn’t contain reference-type fields at any level of nesting. In other words, an unmanaged-type is one of the following:

  • sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, decimal, or bool.
  • Any enum-type.
  • Any pointer-type.
  • Any user-defined struct-type that contains fields of unmanaged-types only.

So there are quite a few restrictions, and for a generic method, T:struct may or may not conform to them for any particular instantiation, so construct like T* is illegal. It would be nice to have a special generic type parameter constraint to cover unmanaged types, but as it stands, there isn't one in the CLR.

At one point I wrote this article explaining how to do exactly that, but many times faster than the Marshal.PtrToStructure. The code sample uses dynamic code generation to copy the generic type T to/from bit stream.

Assuming:

  • A Sequential or Explicit Structure (otherwise very bad idea)
  • Correct data sizes (you should pre-check & throw)

unsafe TStruct BytesToStructure<TStruct>(byte[] data) where TStruct : struct
{
    fixed (byte* dataPtr = data)
        return (TStruct)Marshal.PtrToStructure(new IntPtr(dataPtr), typeof(TStruct));
}

unsafe byte[] StructureToBytes<TStruct>(TStruct st) where TStruct : struct
{
    var bytes = new byte[Marshal.SizeOf(st)];
    fixed (byte* ptr = bytes) Marshal.StructureToPtr(st, new IntPtr(ptr), true);
    return bytes;
}

I wrote this a while back to do the same thing: http://www.codeproject.com/Articles/33713/Generic-BinaryReader-and-BinaryWriter-Extensions

You'll have to add a C++/CLI project to your Visual Studio solution though.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top