Blittable types are data types in the Microsoft .NET Framework that have an identical presentation in memory for both managed and unmanaged code. Understanding the difference between blittable and non-blittable types can aid in using COM Interop or P/Invoke, two techniques for interoperability in .NET applications.
A memory copy operation is sometimes referred to as block transfer, shortened to bit blit (and dedicated hardware to make such a transfer is called a blitter). Blittable is a .NET-specific term expressing whether it is legal to copy an object using a block transfer.
Interoperability can be bidirectional sharing of data and methods between unmanaged code and managed .NET code. .NET provides two ways of interoperating between the two: COM Interop and P/Invoke. Though the methodology is different, in both cases marshalling (conversion between representations of data, formats for calling functions and formats for returning values) must take place. COM Interop deals with this conversion between managed code and COM objects, whereas P/Invoke handles interactions between managed code and Win32 code. The concept of blittable and non-blittable data types applies to both—specifically to the problem of converting data between managed and unmanaged memory. This marshalling is performed by the interop marshaller, which is invoked automatically by the CLR when needed.
A blittable type is a data type that does not require special attention from the interop marshaler because by default it has a common representation in managed and unmanaged memory. By pinning the data in memory, the garbage collector will be prevented from moving it, allowing it to be shared in-place with the unmanaged application.[1] This means that both managed and unmanaged code will alter the memory locations of these types in a consistent manner, and much less effort is required by the marshaler to maintain data integrity. The following are some examples of blittable types available in the .NET Framework:[2]
System.Byte
System.SByte
System.Int16
System.UInt16
System.Int32
System.UInt32
System.Int64
System.UInt64
System.IntPtr
System.UIntPtr
System.Single
System.Double
Additionally, one-dimensional arrays of these types (including unsafe fixed buffers) as well as complex types containing only instance fields (which includes readonly fields) of these types are blittable. The presence of static or const fields that are non-blittable does not cause the type to become non-blittable, because such fields play no part in marshalling. Complex types (that is structs or classes) must also have instance field layout of Sequential applied using the [StructLayout] attribute in order to be considered blittable by the .NET marshaler. Structs have this attribute applied automatically by the compiler, but it must explicitly be added to a class definition to make an otherwise non-blittable class blittable.
If a type is not one of the blittable types, then it is classified as non-blittable. The reason a type is considered non-blittable is that for one representation in managed memory, it may have several potential representations in unmanaged memory or vice versa. Alternatively, there may be exactly one representation for the type in both managed and unmanaged memory. It is also often the case that there simply is no representation on one side or the other. The following are some commonly used non-blittable types in the .NET Framework:[2]
System.Boolean
System.Char
System.Object
System.String
There are many more blittable and non-blittable types, and user-defined types may fit in either category depending on how they are defined (MSDN).
This very restrictive notion of blittable types appears to limit the usefulness of the interoperability services provided by .NET, but this is not so. While blittable types allow a straightforward definition of interoperable types, various ways exist to explicitly define how a non-blittable type should be converted by the interop marshaler.[3] [4] For example, in the .NET languages there are many attributes which can be applied to fields in types, to types themselves and to method parameters to indicate to the marshaler how to handle those particular data. These attributes have various purposes, such as detailing the packing or alignment of a type, specifying offsets of fields in a type, specifying array or string representations, controlling parameter-passing style for function calls, specifying memory management techniques, and more. If none of the attributes or other tools that are provided in the framework are adequate, fine-grained control is provided by the ability to implement the ICustomMarshaler
interface and manually perform the conversion of data in both directions. Understanding what constitutes a blittable type allows a developer to identify situations where intervention is and is not required for a type to be correctly marshaled. In this way, less time is wasted on over-specification of types or function calls.