Επεξεργασία

Κοινή χρήση μέσω


Customize structure marshalling

Sometimes the default marshalling 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 marshalled. Customizing structure layout is supported for all scenarios, but customizing field marshalling is only supported for scenarios where runtime marshalling is enabled. If runtime marshalling is disabled, then any field marshalling must be done manually.

Note

This article doesn't cover customizing marshalling for source-generated interop. If you're using source-generated interop for P/Invokes or COM, see customizing marshalling.

Customize 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 marshalling when your native struct also has an explicit layout, such as a union.

❌ AVOID using classes to express complex native types through inheritance.

❌ AVOID using LayoutKind.Explicit when marshalling 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 marshalling

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 marshalling 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 marshalling

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

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

public struct DefaultArray
{
    public int[] values;
}
struct DefaultArray
{
    int32_t* 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 marshaller to marshal the array in-place. When you're using this marshalling, 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 marshalling a variable length array field as a C99 Flexible Array Member.

Customizing string field marshalling

.NET also provides a wide variety of customizations for marshalling 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. HSTRING marshalling is only supported on runtimes with built-in WinRT support. WinRT support was removed in .NET 5, so HSTRING marshalling is not supported in .NET 5 or newer.

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 marshalled 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 marshalling

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 marshaller to convert a decimal value to a native CY value.

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

Unions

A union is a data type that can contain different types of data atop the same memory. It's a common form of data in the C language. A union can be expressed in .NET using LayoutKind.Explicit. It's recommended to use structs when defining a union in .NET. Using classes can cause layout issues and produce unpredictable behavior.

struct device1_config
{
    void* a;
    void* b;
    void* c;
};
struct device2_config
{
    int32_t a;
    int32_t b;
};
struct config
{
    int32_t type;

    union
    {
        device1_config dev1;
        device2_config dev2;
    };
};
public unsafe struct Device1Config
{
    void* a;
    void* b;
    void* c;
}

public struct Device2Config
{
    int a;
    int b;
}

public struct Config
{
    public int Type;

    public _Union Anonymous;

    [StructLayout(LayoutKind.Explicit)]
    public struct _Union
    {
        [FieldOffset(0)]
        public Device1Config Dev1;

        [FieldOffset(0)]
        public Device2Config Dev2;
    }
}

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 marshalled 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