Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Non-blittable error on a blittable type

I have this struct and this code:

[StructLayout(LayoutKind.Sequential, Pack = 8)]
private class xvid_image_t
{
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
    public int[] stride;

    // [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
    // public IntPtr[] plane;
}

public int decore()
{
    xvid_image_t myStruct = new xvid_image_t();
    myStruct.stride = new int[4]; // can be commented out - same result
    GCHandle.Alloc(myStruct, GCHandleType.Pinned);

    // ...
}

When I try to run it I get an ArgumentException saying:

Object contains non-primitive or non-blittable data

After reading this MSDN page saying

The following complex types are also blittable types:

  • One-dimensional arrays of blittable types, such as an array of integers. However, a type that contains a variable array of blittable types is not itself blittable.

  • Formatted value types that contain only blittable types (and classes if they are marshaled as formatted types). For more information about formatted value types, see Default Marshaling for Value Types.

I don't understand what I am doing wrong. I don't just want to use Marshal, but to understand this too.

So what I actually want is to know:

  1. Why?
  2. How can I resolve this?
  3. Will the solution you provide also work with the commented line in the struct?

I am using .Net 4.5 but a solution for .Net 2.0 is also needed.

like image 687
Randall Flagg Avatar asked Mar 21 '13 10:03

Randall Flagg


2 Answers

Object contains non-primitive or non-blittable data

That's the exception message you get. You are focusing on the "non-blittable" part of the message, but that's not the problem. It is the "non-primitive" part that's the issue. An array is a non-primitive data type.

The CLR is trying to keep you out of trouble here. You could pin the object but then you still have a problem, the array won't be pinned. An object isn't truly pinned when it has fields that need to be pinned as well.

And you have a bigger problem with the UnmanagedType.ByValArray, that requires a structural conversion. In other words, the layout that you need is completely different from the layout of the managed class object. Only the pinvoke marshaller can make this conversion.

You can get what you want without using the pinvoke marshaller by using fixed size buffers, using the fixed keyword. This requires using the unsafe keyword. Make it look like this:

    [StructLayout(LayoutKind.Sequential)]
    unsafe private struct xvid_image_t {
        public fixed int stride[4];
    }

Note that you have to change the declaration to a struct type. It is now a value type, you no longer need to use GCHandle to pin the value when you make it a local variable. Do make sure that whatever unmanaged code takes the structure value, usually by reference, does not store a pointer to the struct. That's going to blow up badly and utterly undiagnosably. The unsafe keyword is appropriate here. If it does store the pointer then you really do have to byte the bullet and use Marshal.AllocHGlobal() and Marshal.StructureToPtr() to ensure the pointer stays valid while the unmanaged code is using it.

like image 162
Hans Passant Avatar answered Oct 25 '22 04:10

Hans Passant


An annoying limitation of .NET is that the only array-ish things it recognizes are a stand-alone System.Array object and a System.String, both of which are reference types. It's possible for code written in C# to use a fixed array (as noted by Hans Passant), but such a type is not recognized by .NET itself, and code which uses fixed arrays is not verifiable. Additionally, fixed arrays are limited to holding primitives, and cannot be accessed by other languages such as vb.net.

Two alternatives to using a fixed array are to

  • replace the fixed array with some combination of fields which together total the proper size (using N variables in most cases, but perhaps replacing e.g. a char[4] with a UInt32, or a char[8] with a UInt64). If the array is not too large, one might define (either via cut/paste or Reflection) a set of static methods which take a struct by ref and read/write the proper element, and then create an array of delegates to call such methods.

  • replace the entire structure with an array, and then pass the first element of that array as a ref parameter. This may be even more "dangerous" than using a fixed array within a structure, but is the only way I know of in vb.net to get "pass-by-ref" semantics with a structure that contains something that really needs to be accessed as an array.

While I can understand that value-type arrays might have been considered "confusing" (especially if they were auto-boxed) there are places where they would have been the semantically-correct approach for array storage, both from the standpoint of allowing pass-by-ref semantics for COM interop and also from the standpoint of methods that are supposed to return a small number of values. For example, in System.Drawing2d, there is a method which returns the current graphics transform as a float[6]; other than by experimentation, there would be no clear way of knowing whether changes to that array after it is returned would affect, might affect, or are guaranteed not to affect anything else. If the method returned a value-type array, it would be clear that changes to the returned array cannot affect anything else. Nonetheless, whether or not value-type arrays would have been a useful part of the Framework, the fact remains that whether for good or bad reasons no such thing exists.

like image 43
supercat Avatar answered Oct 25 '22 06:10

supercat