自訂結構封送處理

有時候,結構的預設封送處理規則並不是您所需要的樣子。 .NET 執行時間提供一些擴充點,供您自訂結構的版面配置,以及如何封送處理欄位。 所有案例都支援自訂結構配置,但只有在啟用執行時間封送處理的情況下,才支援自訂欄位封送處理。 如果 停用執行時間封送處理,則必須手動完成任何欄位封送處理。

自訂結構配置

.NET 提供 System.Runtime.InteropServices.StructLayoutAttribute 屬性與 System.Runtime.InteropServices.LayoutKind 列舉,讓您可以自訂欄位在記憶體中的放置方式。 下列指導方針將可協助您避免常見問題。

✔️ 考慮使用 LayoutKind.Sequential (若可行)。

✔️ 只有在原生結構也有明確的配置時,才在封送處理中使用 LayoutKind.Explicit ,例如等位。

❌如果您需要以 .NET Core 3.0 之前的執行時間為目標,請避免在非Windows平臺上封送處理結構時使用 LayoutKind.Explicit 。 3.0 之前的 .NET Core 執行時間不支援以傳值方式將明確結構傳遞至 Intel 或 AMD 64 位非Windows系統上的原生函式。 不過,執行階段支援在所有平台上以傳參考方式傳遞明確結構。

自訂布林欄位封送處理

機器碼具有許多不同的布林標記法。 單獨在Windows上,有三種方式可以代表布林值。 執行時間不知道結構的原生定義,因此最好是猜測如何封送處理布林值。 .NET 執行時間提供方法來指出如何封送處理您的布林值欄位。 下列範例示範如何將 .NET bool 封送處理至不同的原生布林值類型。

布林值預設為封送處理為原生 4 位元 Win32 BOOL 值,如下列範例所示:

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

若要更為明確,您可以使用 UnmanagedType.Bool 值來取得與上面相同的行為:

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

使用下面的 UnmanagedType.U1UnmanagedType.I1 值,您可以告訴執行階段將 b 欄位封送處理為 1 位元的原生 bool 類型。

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

在Windows,您可以使用 UnmanagedType.VariantBool 值來告知執行時間將布林值封送處理為 2 位元組 VARIANT_BOOL 值:

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

注意

VARIANT_BOOLVARIANT_TRUE = -1VARIANT_FALSE = 0 中的大部分布林值類型不同。 此外,不等於 VARIANT_TRUE 的所有值都會被視為 False。

自訂陣列欄位封送處理

.NET 也包含一些可用來自訂陣列封送處理的方式。

根據預設,.NET 會將陣列封送處理為指向連續元素清單的指標:

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

若您與 COM API 進行介面連接,您可能必須將陣列封送處理為 SAFEARRAY* 物件。 您可以使用 System.Runtime.InteropServices.MarshalAsAttributeUnmanagedType.SafeArray 值告訴執行階段將陣列封送處理為 SAFEARRAY*

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

若需要自訂 SAFEARRAY 中的元素類型,您可以使用 MarshalAsAttribute.SafeArraySubTypeMarshalAsAttribute.SafeArrayUserDefinedSubType 欄位來自訂精確的 SAFEARRAY 元素類型。

如果您需要就地封送處理陣列,您可以使用 UnmanagedType.ByValArray 值來告知封送處理器就地封送處理陣列。 當您使用此封送處理時,也必須為數組中的元素數目提供值給 MarshalAsAttribute.SizeConst 欄位,讓執行時間可以正確配置結構的空間。

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

注意

.NET 不支援將可變長度陣列欄位封送處理為 C99 彈性陣列成員。

自訂字串欄位封送處理

.NET 也為封送處理字串欄位提供各種自訂。

根據預設,.NET 會將字串封送處理為指向結尾為 Null 之字串的指標。 編碼取決於 System.Runtime.InteropServices.StructLayoutAttributeStructLayoutAttribute.CharSet 欄位的值。 若未指定任何屬性,編碼會預設為 ANSI 編碼。

[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.
};

若需要為不同的藍未使用不同的編碼或只是想要在您的結構定義中更為明確,您可以使用 System.Runtime.InteropServices.MarshalAsAttribute 屬性上的 UnmanagedType.LPStrUnmanagedType.LPWStr 值。

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.
};

若想要使用 UTF-8 編碼來封送處理您的字串,您可以使用 MarshalAsAttribute 中的 UnmanagedType.LPUTF8Str 值。

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

注意

使用 UnmanagedType.LPUTF8Str 需要 .NET Framework 4.7 (或更新版本) 或 .NET Core 1.1 (或更新版本)。 它在 .NET Standard 2.0 中不提供。

若您要處理 COM API,您可能必須將字串封送處理為 BSTR。 使用 UnmanagedType.BStr 值,您可以將字串封送處理為 BSTR

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

使用 WinRT 型 API 時,您可能需要將字串封送處理為 HSTRING。 使用 UnmanagedType.HString 值,您可以將字串封送處理為 HSTRINGHSTRING 只有具有內建 WinRT 支援的執行時間才支援封送處理。 .NET 5 已移除WinRT 支援,因此 HSTRING .NET 5 或更新版本不支援封送處理。

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

若您的 API 要求您在結構中就地傳遞字串,您可以使用 UnmanagedType.ByValTStr 值。 請注意,由 ByValTStr 封送處理之字串的編碼是由 CharSet 屬性決定的。 此外,它要求字串長度必須由 MarshalAsAttribute.SizeConst 欄位傳遞。

[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.
};

自訂十進位欄位封送處理

如果您正在處理Windows,可能會遇到一些使用原生CYCURRENCY結構的 API。 根據預設,.NET decimal 類型會封送處理為原生 DECIMAL 結構。 不過,您可以使用 MarshalAsAttribute 搭配 值來 UnmanagedType.Currency 指示封送器將值轉換成 decimal 原生 CY 值。

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

元帥 System.Object

在 Windows 上,您可以將 object 型別欄位封送處理為機器碼。 您可以將這些欄位封送處理為下列三種類型的其中一種:

根據預設,object 型別的欄位將會封送處理為會封裝物件的 IUnknown*

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

若要將物件欄位封送處理為 IDispatch*,請新增具有 UnmanagedType.IDispatch 值的 MarshalAsAttribute

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

若要將它封送處理為 VARIANT,請新增具有 UnmanagedType.Struct 值的 MarshalAsAttribute

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

下表說明 obj 的不同執行階段型別如何對應到 VARIANT 中儲存的各種型別:

.NET 類型 VARIANT 型別
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