Dela via


Anpassa strukturr marshalling

Ibland är standardreglerna för marshalling för strukturer inte precis vad du behöver. .NET-körningen innehåller några tilläggspunkter som du kan använda för att anpassa strukturens layout och hur fälten är ordnade. Anpassning av strukturlayout stöds för alla scenarier, men anpassning av fältrundning stöds endast för scenarier där körningsrundning är aktiverat. Om runtime-marshalling är inaktiverat måste alla fältrundanvisning utföras manuellt.

Kommentar

Den här artikeln beskriver inte anpassning av marshalling för källgenererad interop. Om du använder källgenererad interop för P/Invokes eller COM kan du läsa anpassa marshalling.

Anpassa strukturlayout

.NET tillhandahåller System.Runtime.InteropServices.StructLayoutAttribute attributet och System.Runtime.InteropServices.LayoutKind uppräkningen så att du kan anpassa hur fält placeras i minnet. Följande vägledning hjälper dig att undvika vanliga problem.

✔️ ÖVERVÄG att använda LayoutKind.Sequential när det är möjligt.

✔️ Använd endast LayoutKind.Explicit i marshalling när din interna struct också har en explicit layout, till exempel en union.

❌ UNDVIK att använda klasser för att uttrycka komplexa inbyggda typer genom arv.

❌ UNDVIK att använda LayoutKind.Explicit när du samlar strukturer på plattformar som inte är Windows-plattformar om du behöver rikta in dig på runtimes före .NET Core 3.0. .NET Core-körningen före 3.0 stöder inte överföring av explicita strukturer efter värde till inbyggda funktioner i Intel- eller AMD 64-bitars system som inte är Windows-system. Körningen har dock stöd för att skicka explicita strukturer med referens på alla plattformar.

Anpassa boolesk fältr marshalling

Intern kod har många olika booleska representationer. Enbart i Windows finns det tre sätt att representera booleska värden. Körningen känner inte till den interna definitionen av din struktur, så det bästa den kan göra är att gissa hur du konverterar dina booleska värden. Med .NET-körningen kan du ange hur du konverterar det booleska fältet. I följande exempel visas hur du konverterar .NET bool till olika interna booleska typer.

Booleska värden är standard för marshalling som ett ursprungligt Win32-värde BOOL på 4 byte enligt följande exempel:

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

Om du vill vara explicit kan du använda UnmanagedType.Bool värdet för att få samma beteende som ovan:

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

UnmanagedType.U1 Med hjälp av värdena nedan UnmanagedType.I1 kan du be körningen att konvertera b fältet som en intern bool typ av 1 byte.

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

I Windows kan du använda UnmanagedType.VariantBool värdet för att instruera körningen att konvertera det booleska värdet till ett värde på 2 byte VARIANT_BOOL :

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

Kommentar

VARIANT_BOOL skiljer sig från de flesta booltyper i det VARIANT_TRUE = -1 och VARIANT_FALSE = 0. Dessutom anses alla värden som inte är lika med VARIANT_TRUE vara falska.

Anpassa matrisfältsr marshalling

.NET innehåller också några sätt att anpassa matrisrappning.

Som standard konverterar .NET matriser som en pekare till en sammanhängande lista över elementen:

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

Om du interagerar med COM-API:er kan du behöva konvertera matriser som SAFEARRAY* objekt. Du kan använda System.Runtime.InteropServices.MarshalAsAttribute värdet och UnmanagedType.SafeArray för att instruera körningen att konvertera en matris som :SAFEARRAY*

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

Om du behöver anpassa vilken typ av element som finns i SAFEARRAYkan du använda fälten MarshalAsAttribute.SafeArraySubType och MarshalAsAttribute.SafeArrayUserDefinedSubType för att anpassa den exakta elementtypen för SAFEARRAY.

Om du behöver marskalka matrisen på plats kan du använda UnmanagedType.ByValArray värdet för att be marshaller att konvertera matrisen på plats. När du använder den här marshallingen måste du också ange ett värde i MarshalAsAttribute.SizeConst fältet för antalet element i matrisen så att körningen kan allokera utrymme för strukturen korrekt.

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

Kommentar

.NET stöder inte marshalling av ett matrisfält med variabel längd som en C99-medlem för flexibel matris.

Anpassa strängfältsriffning

.NET tillhandahåller också en mängd olika anpassningar för att ordna strängfält.

Som standard konverterar .NET en sträng som pekare till en null-avslutad sträng. Kodningen beror på värdet för StructLayoutAttribute.CharSet fältet i System.Runtime.InteropServices.StructLayoutAttribute. Om inget attribut anges anges kodningen som standard till en ANSI-kodning.

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

Om du behöver använda olika kodningar för olika fält eller bara föredrar att vara mer explicit i din struct-definition kan du använda UnmanagedType.LPStr värdena eller UnmanagedType.LPWStr för ett System.Runtime.InteropServices.MarshalAsAttribute attribut.

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

Om du vill konvertera strängarna med hjälp av UTF-8-kodningen kan du använda UnmanagedType.LPUTF8Str värdet i .MarshalAsAttribute

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

Kommentar

Användning UnmanagedType.LPUTF8Str kräver antingen .NET Framework 4.7 (eller senare versioner) eller .NET Core 1.1 (eller senare versioner). Den är inte tillgänglig i .NET Standard 2.0.

Om du arbetar med COM-API:er kan du behöva konvertera en sträng som en BSTR. Med hjälp av värdet UnmanagedType.BStr kan du konvertera en sträng som en BSTR.

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

När du använder ett WinRT-baserat API kan du behöva konvertera en sträng som en HSTRING. Med hjälp av värdet UnmanagedType.HString kan du konvertera en sträng som en HSTRING. HSTRING marshalling stöds endast på runtimes med inbyggt WinRT-stöd. WinRT-stöd har tagits bort i .NET 5, så HSTRING marshalling stöds inte i .NET 5 eller senare.

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

Om ditt API kräver att du skickar strängen på plats i strukturen kan du använda UnmanagedType.ByValTStr värdet. Observera att kodningen för en sträng som är kodad efter ByValTStr bestäms av CharSet attributet. Dessutom kräver det att en stränglängd skickas av fältet 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.
};

Anpassa decimalfältsr marshalling

Om du arbetar med Windows kan du stöta på vissa API:er som använder den inbyggda CY strukturen eller CURRENCY strukturen. Som standard konverterar .NET-typen decimal till den interna DECIMAL strukturen. Du kan dock använda en MarshalAsAttribute med UnmanagedType.Currency värdet för att instruera marshaller att konvertera ett decimal värde till ett internt CY värde.

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

Fackföreningar

En union är en datatyp som kan innehålla olika typer av data ovanpå samma minne. Det är en vanlig form av data på C-språket. En union kan uttryckas i .NET med .LayoutKind.Explicit Vi rekommenderar att du använder structs när du definierar en union i .NET. Användning av klasser kan orsaka layoutproblem och skapa oförutsägbara beteenden.

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

Marskalk System.Object

I Windows kan du konvertera objectfält som skrivits till intern kod. Du kan konvertera dessa fält till en av tre typer:

Som standard kommer ett object-typat fält att ordnas till ett IUnknown* som omsluter objektet.

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

Om du vill konvertera ett objektfält till ett IDispatch*lägger du till ett MarshalAsAttribute med UnmanagedType.IDispatch värdet .

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

Om du vill konvertera den som en VARIANTlägger du till en MarshalAsAttribute med UnmanagedType.Struct värdet .

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

I följande tabell beskrivs hur olika körningstyper av fältkartan obj till de olika typerna som lagras i en VARIANT:

.NET-typ VARIANTtyp
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