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-terminated, ASCII osv. Som standard försöker undersystemet P/Invoke göra det rätta baserat på standardbeteendet, 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.LPStr)] 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 intern kod när det här attributet tillämpas finns i inaktiverad runtime-marshalling.

Standardregler för sortering av vanliga typer

I allmänhet försöker körningen göra det "rätta" när du samlar in för att kräva minst arbete från dig. 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 olika typer för vilka marshallingen är densamma för både P/Invoke och fält 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 dokumentationen om teckenuppsättningen.
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 sorterar som en parameter eller struktur.

.NET-typ Ursprunglig typ (parameter) Intern 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 en LayoutKind av Sequential eller Explicit En pekare till den interna representationen av klassen Den interna representationen av klassen

Följande tabell innehåller standardreglerna för marshalling som endast är Windows. På plattformar som inte är Windows kan du inte konvertera dessa typer.

.NET-typ Ursprunglig typ (parameter) Intern 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 Intern typ (endast parameter)
System.Text.StringBuilder Antingen char* eller char16_t* beroende på CharSet P/Invoke. Se dokumentationen om teckenuppsättningen.
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. Parameterns marshallingartikel beskriver hur du anpassar hur olika parametertyper är ordnade.

Standard 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 Intern typ (COM-metodanrop)
System.Boolean VARIANT_BOOL
StringBuilder LPWSTR
System.String BSTR
Delegera typer _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 structs

En annan aspekt av typen marshalling är hur du skickar in en struct till en ohanterad 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.

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

[StructLayout(LayoutKind.Sequential)]
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;
}

public static void Main(string[] args)
{
    SystemTime st = new SystemTime();
    GetSystemTime(st);
    Console.WriteLine(st.Year);
}

Föregående kod visar ett enkelt exempel på anrop till GetSystemTime() funktionen. Den intressanta biten är på rad 4. 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;

Ibland gör standard-marshalling för din struktur inte det du behöver. I artikeln Anpassa strukturs marshalling lär du dig hur du anpassar hur din struktur är ordnad.