Dela via


Typ av marshalling

Marshalling är processen för att transformera typer när de behöver korsa mellan hanterad och intern kod.

Marshalling krävs eftersom typerna i den hanterade och ohanterade koden skiljer sig åt. I hanterad kod har du till exempel en string, medan ohanterade strängar kan vara .NET-kodning string (UTF-16), kodning av ANSI-kodsida, UTF-8, null-terminerad, ASCII osv. Som standard försöker P/Invoke-undersystemet att agera korrekt baserat på standardbeteende, som beskrivs i den här artikeln. För de situationer där du behöver extra kontroll kan du dock använda attributet MarshalAs för att ange vilken typ som förväntas på den ohanterade sidan. Om du till exempel vill att strängen ska skickas som en null-avslutad UTF-8-sträng kan du göra så här:

[LibraryImport("somenativelibrary.dll")]
static extern int MethodA([MarshalAs(UnmanagedType.LPUTF8Str)] string parameter);

// or

[LibraryImport("somenativelibrary.dll", StringMarshalling = StringMarshalling.Utf8)]
static extern int MethodB(string parameter);

Om du använder System.Runtime.CompilerServices.DisableRuntimeMarshallingAttribute attributet för sammansättningen gäller inte reglerna i följande avsnitt. Information om hur .NET-värden exponeras för inbyggd kod när det här attributet tillämpas finns i avsnittet om inaktiverad runtime-marshalling.

Standardregler för sortering av vanliga typer

I allmänhet försöker runtime-miljön göra det "rätta" när den serialiserar för att du ska behöva göra så lite arbete som möjligt. I följande tabeller beskrivs hur varje typ ordnas som standard när den används i en parameter eller ett fält. Heltals- och teckentyperna C99/C++11 med fast bredd används för att säkerställa att följande tabell är korrekt för alla plattformar. Du kan använda alla inbyggda typer som har samma justerings- och storlekskrav som dessa typer.

Den här första tabellen beskriver mappningarna för typer för vilka marshallingen är densamma för både P/Invoke och field marshalling.

C#-nyckelord .NET-typ Ursprunglig typ
byte System.Byte uint8_t
sbyte System.SByte int8_t
short System.Int16 int16_t
ushort System.UInt16 uint16_t
int System.Int32 int32_t
uint System.UInt32 uint32_t
long System.Int64 int64_t
ulong System.UInt64 uint64_t
char System.Char Antingen char eller char16_t beroende på kodningen av P/Invoke eller strukturen. Se teckenuppsättningsdokumentationen.
System.Char Antingen char* eller char16_t* beroende på kodningen av P/Invoke eller strukturen. Se dokumentationen om teckenuppsättningen.
nint System.IntPtr intptr_t
nuint System.UIntPtr uintptr_t
.NET-pekartyper (till exempel void*) void*
Typ härledd från System.Runtime.InteropServices.SafeHandle void*
Typ härledd från System.Runtime.InteropServices.CriticalHandle void*
bool System.Boolean Win32-typ BOOL
decimal System.Decimal COM-struct DECIMAL
.NET-ombud Inbyggd funktionspekare
System.DateTime Win32-typ DATE
System.Guid Win32-typ GUID

Några kategorier av marshalling har olika standardvärden om du marshallar som parameter eller struktur.

.NET-typ Ursprunglig typ (parameter) Inbyggd typ (fält)
.NET-matris En pekare till början av en matris med inbyggda representationer av matriselementen. Tillåts inte utan ett [MarshalAs] attribut
En klass med LayoutKind av Sequential eller Explicit En pekare till den inbyggda representationen av klassen Den inbyggda representationen av klassen

Följande tabell innehåller förvalda regler för marshalling som enbart gäller för Windows. På plattformar som inte är Windows kan du inte konvertera dessa typer.

.NET-typ Ursprunglig typ (parameter) Inbyggd typ (fält)
System.Object VARIANT IUnknown*
System.Array COM-gränssnitt Tillåts inte utan ett [MarshalAs] attribut
System.ArgIterator va_list Tillåts inte
System.Collections.IEnumerator IEnumVARIANT* Tillåts inte
System.Collections.IEnumerable IDispatch* Tillåts inte
System.DateTimeOffset int64_t representerar antalet fästingar sedan midnatt den 1 januari 1601 int64_t representerar antalet fästingar sedan midnatt den 1 januari 1601

Vissa typer kan bara ordnas som parametrar och inte som fält. Dessa typer visas i följande tabell:

.NET-typ Inbyggd typ (endast parameter)
System.Text.StringBuilder Antingen char* eller char16_t* beroende på CharSet P/Invoke. Se dokumentationen om teckenuppsättning.
System.ArgIterator va_list (endast på Windows x86/x64/arm64)
System.Runtime.InteropServices.ArrayWithOffset void*
System.Runtime.InteropServices.HandleRef void*

Om dessa standardvärden inte gör exakt vad du vill kan du anpassa hur parametrarna är ordnade. Artikeln om parameter marshalling visar hur du kan anpassa hur olika parametertyper hanteras.

Förvald marshalling i COM-scenarier

När du anropar metoder för COM-objekt i .NET ändrar .NET-körningen standardreglerna för marshalling så att de matchar vanliga COM-semantik. I följande tabell visas de regler som .NET-runtimes använder i COM-scenarier:

.NET-typ Inbyggd typ (COM-metodanrop)
System.Boolean VARIANT_BOOL
StringBuilder LPWSTR
System.String BSTR
Delegattyper _Delegate* i .NET Framework. Tillåts inte i .NET Core och .NET 5+.
System.Drawing.Color OLECOLOR
.NET-matris SAFEARRAY
System.String[] SAFEARRAY av BSTRs

Rangeringsklasser och strukturer

En annan aspekt av typmarshallning är hur man överför en struktur till en oövervakad metod. Vissa av de ohanterade metoderna kräver till exempel en struct som parameter. I dessa fall måste du skapa en motsvarande struct eller en klass i en hanterad del av världen för att använda den som en parameter. Men det räcker inte att bara definiera klassen. Du måste också instruera marshallern hur fälten i klassen ska mappas till den ohanterade structen. Här blir attributet StructLayout användbart.

using System;
using System.Runtime.InteropServices;

Win32Interop.GetSystemTime(out Win32Interop.SystemTime systemTime);

Console.WriteLine(systemTime.Year);

internal static partial class Win32Interop
{
    [LibraryImport("kernel32.dll")]
    internal static partial void GetSystemTime(out SystemTime systemTime);

    [StructLayout(LayoutKind.Sequential)]
    internal ref struct SystemTime
    {
        public ushort Year;
        public ushort Month;
        public ushort DayOfWeek;
        public ushort Day;
        public ushort Hour;
        public ushort Minute;
        public ushort Second;
        public ushort Millisecond;
    }
}

Föregående kod visar ett enkelt exempel på anrop till GetSystemTime() funktionen. Den intressanta biten är på rad 13. Attributet anger att fälten i klassen ska mappas sekventiellt till structen på den andra (ohanterade) sidan. Det innebär att namngivning av fälten inte är viktigt, bara deras ordning är viktig, eftersom den måste motsvara den ohanterade struct som visas i följande exempel:

typedef struct _SYSTEMTIME {
  WORD wYear;
  WORD wMonth;
  WORD wDayOfWeek;
  WORD wDay;
  WORD wHour;
  WORD wMinute;
  WORD wSecond;
  WORD wMilliseconds;
} SYSTEMTIME, *PSYSTEMTIME, *LPSYSTEMTIME;

Ibland gör standard-marshalling för din struktur inte det du behöver. I artikeln Anpassning av strukturmarshaling lär du dig hur du anpassar hur din struktur överförs.