How do I return a byte array obtained in a generic class having the constraint Structure as a generic value?

Viewed 165

I have some difficulty with returning a generic.

I have a class named Scalar. Its purpose is to deal with numeric values only. Obviously that's not truly enforcable without throwing runtime exceptions. The best I am able to do is using the Structure constraint. So far, so acceptable.

In the class I work with a stream which contains bytes representing such generics. These can be sequences of type Byte, Int32, UInt64 etc. They are all of the same data type, and they represent numbers.

Consider a function Item within this class, in which I call a function, which reads from that stream. The result is stored in a byte array. I know the array's bound because of evaluating and storing it at construction time, using Marshal.SizeOf. However, I am struggling at returning this byte array as datatype T.

'SizeOf:
Imports System.Runtime.InteropServices.Marshal

Public Class Scalar(Of T As Structure)
    Private gtValue As T
    Private giSize As Int32

    Public Sub New(Value As T)
        gtValue = Value
        giSize = SizeOf(gtValue)
    End Sub

    Public Function Value() As T
        Return gtValue
    End Function

    Public Function Item() As T
        'The return value stems from a stream. It is packed into a bytes array
        'of appropriate size (8 B for Int64, Double, 1 B for Byte etc.). The
        'Byte array is in little endian order.
        Dim abItem(0 To giSize - 1) As Byte     'Result of Stream function.

        'How do I return abItem as T?
    End Function
End Class

Of course, I could run through the array and compose the number itself byte by byte, then returning it as a non-generic datatype, say as an Int64:

    Dim iElement As Int64 = 0
    For i = 0 To giIndexLenBytes - 1
        'Shift prior content 8 bits to the left and add new (unsigned) byte.
        iElement <<= 8
        iElement += abItem(i)
    Next
    Return iElement

I could also use BitConverter and a Select block to obtain the number as well, but that would be even clumsier and in the end not a tad more helpful:

    Dim iElement As Int64
    Select Case gtValue.GetType.ToString
        Case "Byte"
            iElement = abItem(0)
        Case "Integer"
            iElement = BitConverter.ToInt32(abItem, 0)
        Case "Long"
            iElement = BitConverter.ToInt64(abItem, 0)

            'etc...
    End Select

Obviously, I can obtain the number in several ways.

However, is there a possibility to assign the once obtained number to a returnable T variable, so that the generic class is useful for the user of it?

2 Answers

This may not be the most efficient way, but it's safe, without resorting to anything crazy:

Public Function CreateStructureFromByteArray(Of T As Structure)(bytes() As Byte) As T
    Dim arrayPointer As IntPtr = Marshal.AllocHGlobal(bytes.Length)
    Dim result As T = Nothing
    Try
        Marshal.Copy(bytes, 0, arrayPointer, bytes.Length)
        result = Marshal.PtrToStructure(Of T)(arrayPointer)
    Finally
        Marshal.FreeHGlobal(arrayPointer)
    End Try
    Return result
End Function

Note that the assumption here is that the input byte array contains data using the same endianness as the current system. If that's not the case, you'd need to reverse the array before calling Marshal.PtrToStructure.


On a side note, I had thought of trying to use a structure with an explicit layout to do the conversion, like this:

<StructLayout(LayoutKind.Explicit)>
Structure EvilUnion(Of T As Structure)
    <FieldOffset(0)> Public Byte1 As Byte
    <FieldOffset(1)> Public Byte2 As Byte
    <FieldOffset(2)> Public Byte3 As Byte
    <FieldOffset(3)> Public Byte4 As Byte
    <FieldOffset(0)> Public Struct As T
End Structure

(Since arrays are reference types, you'd have to have separate fields for each byte, like that, which would be a pain. Not only would the code in your CreateStructureFromByteArray function need to set the value of each byte field individually, but it would also have to check to make sure that the generic type isn't longer than 4, or however many byte fields you put in it.)

It might have been worth it, just for the better performance, but unfortunately (and unsurprisingly) you can't use the StructLayoutAttribute on a generic structure. At that point, you'd be forced to create a separate explicit layout structure for each type, and then you're back to your original problem.

Here's an efficient way but it uses the undocumented keyword __makeref. And it's c#. Sorry :(

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TOut FromBytes<TOut>(byte[] bytes) where TOut : struct
{
    unsafe
    {
        TOut result = default(TOut);

        TypedReference resultRef = __makeref(result);
        byte* resultPtr = (byte*)*((IntPtr*)&resultRef);

        TypedReference curValueRef = __makeref(bytes[0]);
        byte* curValuePtr = (byte*)*((IntPtr*)&curValueRef);

        for (int i = 0; i < Marshal.SizeOf<TOut>(); ++i)
        {
            resultPtr[i] = curValuePtr[i];
        }

        return result;
    }
}

Test code:

public static void Main()
{
    var tests = new List<byte[]>
    {
        new byte[] { 1, 0, 0, 0 },
        new byte[] { 2, 0, 0, 0 },
        new byte[] { 0, 1, 0, 0 },
        new byte[] { 0, 2, 0, 0 },
        new byte[] { 0, 0, 1, 0 },
        new byte[] { 0, 0, 2, 0 },
    };
    foreach (var bytes in tests)
    {
        int integer = FromBytes<int>(bytes);
        long longInteger = FromBytes<long>(bytes);

        Console.WriteLine("{0}...{1}", integer, longInteger);
    }
}

Output:

1...1
2...2
256...256
512...512
65536...65536
131072...131072
Related