Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

c#

.net

Why oh why is this not allowed:

    private static unsafe byte[] ConvertStruct<T>(T str) where T: struct
    {
        T* ptr = &str;
        int size = Marshal.SizeOf(str);
        var arr = new byte[size];
        Marshal.Copy((IntPtr) ptr, arr, 0, size);
        return arr;
    }

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

Or is the "struct" constraint not enough? In the sense that it could still be an managed struct?

Do I understand correctly that my struct might not be blittable? If so, should blittable not be a valid constraint?

like image 680
Wouter Schut Avatar asked Feb 10 '17 08:02

Wouter Schut


3 Answers

Like Damian_The_Unbeliever said, you can't make an unmanaged pointer of a managed object or of a struct that contains managed fields. And like I said in the comment, there is no generic restraint that you can implement that would ensure compile-time safety to make sure the struct would satisfy those requirements. As such, you can't make a pointer to a generic type.

There may be a workaround, however. The Marshal.StructureToPtr method will take a structure object and copy its data into an IntPtr address. You can use it in conjunction with Marshall.AllocHGlobal to possibly achieve your desired functionality:

private static byte[] ConvertStruct<T>(ref T str) where T : struct
{
    int size = Marshal.SizeOf(str);
    IntPtr arrPtr = Marshal.AllocHGlobal(size);
    Marshal.StructureToPtr(str, arrPtr, true);
    var arr = new byte[size];
    Marshal.Copy(arrPtr, arr, 0, size);
    Marshal.FreeHGlobal(arrPtr);
    return arr;
}

As Kris Vandermotten points out, the Marshal.AllocHGlobal isn't actually necessary. You can pin the array, get an IntPtr for it, and copy to the IntPtr directly:

private static unsafe byte[] ConvertStruct<T>(ref T str) where T : struct
{
    int size = Marshal.SizeOf(str);
    var arr = new byte[size];

    fixed (byte* arrPtr = arr)
    {
        Marshal.StructureToPtr(str, (IntPtr)arrPtr, true);
    }

    return arr;
}

The benefit to the first method, though, is that it doesn't require an unsafe context to work. (If you care about that kind of thing.)

like image 92
Abion47 Avatar answered Nov 08 '22 13:11

Abion47


From C# Spec, section 18.2:

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 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 is not a constructed type and contains fields of unmanaged-types only.

And there's no real way to express this via generic constraints. Even if this definition does seem substantially similar to any definition of "blittable", it's notable that that specific concept isn't defined (or even referenced) in the C# Spec. For instance, from the MSDN page we can see that "one dimensional array of a blittable type" is blittable but the above spec definition seems to preclude arrays.

like image 5
Damien_The_Unbeliever Avatar answered Nov 08 '22 14:11

Damien_The_Unbeliever


Try something like this. With marshalling you can avoid unsafe keyword

 private static byte[] ConvertStruct<T>(T str) where T : struct
    {
        int size = Marshal.SizeOf(str);//sizeof
        int ptr = (int)Marshal.AllocCoTaskMem(size);//allocate memory before past the structure there
        Marshal.StructureToPtr(str, (IntPtr)ptr, true);//alloc your structure
        byte[] res=new byte[size];//your result
        for (int i = 0; i < size; i++)
        {
            res[i] = Marshal.ReadByte((IntPtr)ptr);//read byte from memory
            ptr++;//offset
        }
        return res;
    }

Also, you'll need to set a StructLayout attribute in your structure, as it's shown below

 [StructLayout(LayoutKind.Sequential)]
[Serializable]
public struct StrStruct
{}

If your structure contains fields with umnmanaged types, use MarshalAs attribute to marshal all your fields with unmamaged types properly (ex. string as an LPWStr). If your structure is completely unmanaged, you need to marshal all of its fields with MarshalAs.

like image 1
Gramin Avatar answered Nov 08 '22 14:11

Gramin