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 BSTR s |
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.