Customize structure marshaling

Sometimes the default marshaling rules for structures aren't exactly what you need. The .NET runtimes provide a few extension points for you to customize your structure's layout and how fields are marshaled.

Customizing structure layout

.NET provides the System.Runtime.InteropServices.StructLayoutAttribute attribute and the System.Runtime.InteropServices.LayoutKind enumeration to allow you to customize how fields are placed in memory. The following guidance will help you avoid common issues.

✔️ CONSIDER using LayoutKind.Sequential whenever possible.

✔️ DO only use LayoutKind.Explicit in marshaling when your native struct also has an explicit layout, such as a union.

❌ AVOID using LayoutKind.Explicit when marshaling structures on non-Windows platforms if you need to target runtimes before .NET Core 3.0. The .NET Core runtime before 3.0 doesn't support passing explicit structures by value to native functions on Intel or AMD 64-bit non-Windows systems. However, the runtime supports passing explicit structures by reference on all platforms.

Customizing boolean field marshaling

Native code has many different boolean representations. On Windows alone, there are three ways to represent boolean values. The runtime doesn't know the native definition of your structure, so the best it can do is make a guess on how to marshal your boolean values. The .NET runtime provides a way to indicate how to marshal your boolean field. The following examples show how to marshal .NET bool to different native boolean types.

Boolean values default to marshaling as a native 4-byte Win32 BOOL value as shown in the following example:

public struct WinBool
{
    public bool b;
}
struct WinBool
{
    public BOOL b;
};

If you want to be explicit, you can use the UnmanagedType.Bool value to get the same behavior as above:

public struct WinBool
{
    [MarshalAs(UnmanagedType.Bool)]
    public bool b;
}
struct WinBool
{
    public BOOL b;
};

Using the UnmanagedType.U1 or UnmanagedType.I1 values below, you can tell the runtime to marshal the b field as a 1-byte native bool type.

public struct CBool
{
    [MarshalAs(UnmanagedType.U1)]
    public bool b;
}
struct CBool
{
    public bool b;
};

On Windows, you can use the UnmanagedType.VariantBool value to tell the runtime to marshal your boolean value to a 2-byte VARIANT_BOOL value:

public struct VariantBool
{
    [MarshalAs(UnmanagedType.VariantBool)]
    public bool b;
}
struct VariantBool
{
    public VARIANT_BOOL b;
};

Note

VARIANT_BOOL is different than most bool types in that VARIANT_TRUE = -1 and VARIANT_FALSE = 0. Additionally, all values that aren't equal to VARIANT_TRUE are considered false.

Customizing array field marshaling

.NET also includes a few ways to customize array marshaling.

By default, .NET marshals arrays as a pointer to a contiguous list of the elements:

public struct DefaultArray
{
    public int[] values;
}
struct DefaultArray
{
    int* values;
};

If you're interfacing with COM APIs, you may have to marshal arrays as SAFEARRAY* objects. You can use the System.Runtime.InteropServices.MarshalAsAttribute and the UnmanagedType.SafeArray value to tell the runtime to marshal an array as a SAFEARRAY*:

public struct SafeArrayExample
{
    [MarshalAs(UnmanagedType.SafeArray)]
    public int[] values;
}
struct SafeArrayExample
{
    SAFEARRAY* values;
};

If you need to customize what type of element is in the SAFEARRAY, then you can use the MarshalAsAttribute.SafeArraySubType and MarshalAsAttribute.SafeArrayUserDefinedSubType fields to customize the exact element type of the SAFEARRAY.

If you need to marshal the array in-place, you can use the UnmanagedType.ByValArray value to tell the marshaler to marshal the array in-place. When you're using this marshaling, you also must supply a value to the MarshalAsAttribute.SizeConst field for the number of elements in the array so the runtime can correctly allocate space for the structure.

public struct InPlaceArray
{
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
    public int[] values;
}
struct InPlaceArray
{
    int values[4];
};

Note

.NET doesn't support marshaling a variable length array field as a C99 Flexible Array Member.

Customizing string field marshaling

.NET also provides a wide variety of customizations for marshaling string fields.

By default, .NET marshals a string as a pointer to a null-terminated string. The encoding depends on the value of the StructLayoutAttribute.CharSet field in the System.Runtime.InteropServices.StructLayoutAttribute. If no attribute is specified, the encoding defaults to an ANSI encoding.

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct DefaultString
{
    public string str;
}
struct DefaultString
{
    char* str;
};
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct DefaultString
{
    public string str;
}
struct DefaultString
{
    char16_t* str; // Could also be wchar_t* on Windows.
};

If you need to use different encodings for different fields or just prefer to be more explicit in your struct definition, you can use the UnmanagedType.LPStr or UnmanagedType.LPWStr values on a System.Runtime.InteropServices.MarshalAsAttribute attribute.

public struct AnsiString
{
    [MarshalAs(UnmanagedType.LPStr)]
    public string str;
}
struct AnsiString
{
    char* str;
};
public struct UnicodeString
{
    [MarshalAs(UnmanagedType.LPWStr)]
    public string str;
}
struct UnicodeString
{
    char16_t* str; // Could also be wchar_t* on Windows.
};

If you want to marshal your strings using the UTF-8 encoding, you can use the UnmanagedType.LPUTF8Str value in your MarshalAsAttribute.

public struct UTF8String
{
    [MarshalAs(UnmanagedType.LPUTF8Str)]
    public string str;
}
struct UTF8String
{
    char* str;
};

Note

Using UnmanagedType.LPUTF8Str requires either .NET Framework 4.7 (or later versions) or .NET Core 1.1 (or later versions). It isn't available in .NET Standard 2.0.

If you're working with COM APIs, you may need to marshal a string as a BSTR. Using the UnmanagedType.BStr value, you can marshal a string as a BSTR.

public struct BString
{
    [MarshalAs(UnmanagedType.BStr)]
    public string str;
}
struct BString
{
    BSTR str;
};

When using a WinRT-based API, you may need to marshal a string as an HSTRING. Using the UnmanagedType.HString value, you can marshal a string as a HSTRING.

public struct HString
{
    [MarshalAs(UnmanagedType.HString)]
    public string str;
}
struct BString
{
    HSTRING str;
};

If your API requires you to pass the string in-place in the structure, you can use the UnmanagedType.ByValTStr value. Do note that the encoding for a string marshaled by ByValTStr is determined from the CharSet attribute. Additionally, it requires that a string length is passed by the MarshalAsAttribute.SizeConst field.

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct DefaultString
{
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 4)]
    public string str;
}
struct DefaultString
{
    char str[4];
};
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct DefaultString
{
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 4)]
    public string str;
}
struct DefaultString
{
    char16_t str[4]; // Could also be wchar_t[4] on Windows.
};

Customizing decimal field marshaling

If you're working on Windows, you might encounter some APIs that use the native CY or CURRENCY structure. By default, the .NET decimal type marshals to the native DECIMAL structure. However, you can use a MarshalAsAttribute with the UnmanagedType.Currency value to instruct the marshaler to convert a decimal value to a native CY value.

public struct Currency
{
    [MarshalAs(UnmanagedType.Currency)]
    public decimal dec;
}
struct Currency
{
    CY dec;
};

Marshal System.Object

On Windows, you can marshal object-typed fields to native code. You can marshal these fields to one of three types:

By default, an object-typed field will be marshaled to an IUnknown* that wraps the object.

public struct ObjectDefault
{
    public object obj;
}
struct ObjectDefault
{
    IUnknown* obj;
};

If you want to marshal an object field to an IDispatch*, add a MarshalAsAttribute with the UnmanagedType.IDispatch value.

public struct ObjectDispatch
{
    [MarshalAs(UnmanagedType.IDispatch)]
    public object obj;
}
struct ObjectDispatch
{
    IDispatch* obj;
};

If you want to marshal it as a VARIANT, add a MarshalAsAttribute with the UnmanagedType.Struct value.

public struct ObjectVariant
{
    [MarshalAs(UnmanagedType.Struct)]
    public object obj;
}
struct ObjectVariant
{
    VARIANT obj;
};

The following table describes how different runtime types of the obj field map to the various types stored in a VARIANT:

.NET Type VARIANT Type
byte VT_UI1
sbyte VT_I1
short VT_I2
ushort VT_UI2
int VT_I4
uint VT_UI4
long VT_I8
ulong VT_UI8
float VT_R4
double VT_R8
char VT_UI2
string VT_BSTR
System.Runtime.InteropServices.BStrWrapper VT_BSTR
object VT_DISPATCH
System.Runtime.InteropServices.UnknownWrapper VT_UNKNOWN
System.Runtime.InteropServices.DispatchWrapper VT_DISPATCH
System.Reflection.Missing VT_ERROR
(object)null VT_EMPTY
bool VT_BOOL
System.DateTime VT_DATE
decimal VT_DECIMAL
System.Runtime.InteropServices.CurrencyWrapper VT_CURRENCY
System.DBNull VT_NULL