Marshaling typów

Marshalling to proces przekształcania typów, gdy muszą one przechodzić między kodem zarządzanym i natywnym.

Marshalling jest wymagany, ponieważ typy w zarządzanym i niezarządzanych kodzie są różne. Na przykład w kodzie zarządzanym istnieje znak String, natomiast w niezarządzanych ciągach świata mogą być unicode ("wide"), non-Unicode, null-terminated, ASCII itp. Domyślnie podsystem P/Invoke próbuje wykonać odpowiednie czynności w oparciu o domyślne zachowanie opisane w tym artykule. Jednak w takich sytuacjach, w których potrzebujesz dodatkowej kontroli, można stosować atrybut MarshalAs , aby określić oczekiwany typ po stronie niezarządzanej. Jeśli na przykład chcesz, aby ciąg był wysyłany jako ciąg ANSI zakończony wartością null, możesz to zrobić w następujący sposób:

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

Jeśli zastosujesz System.Runtime.CompilerServices.DisableRuntimeMarshallingAttribute atrybut do zestawu, reguły w poniższej sekcji nie będą stosowane. Aby uzyskać informacje na temat sposobu uwidaczniania wartości platformy .NET w kodzie natywnym po zastosowaniu tego atrybutu, zobacz wyłączone marshalling środowiska uruchomieniowego.

Domyślne reguły marshalingu typowych typów

Ogólnie rzecz biorąc, środowisko uruchomieniowe próbuje wykonać "właściwą rzecz", gdy chcesz wymagać od Ciebie najmniejszej ilości pracy. W poniższych tabelach opisano, jak każdy typ jest domyślnie rozdzielany w przypadku użycia w parametrze lub polu. Typy liczb całkowitych i znaków o stałej szerokości C99/C++11 są używane w celu zapewnienia, że poniższa tabela jest poprawna dla wszystkich platform. Można użyć dowolnego typu natywnego, który ma te same wymagania dotyczące wyrównania i rozmiaru co te typy.

W pierwszej tabeli opisano mapowania dla różnych typów, dla których marshalling jest taki sam zarówno dla P/Invoke, jak i marshallingu pól.

Słowo kluczowe języka C# Typ platformy .NET Typ natywny
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 Albo char w char16_t zależności od CharSet metody P/Invoke lub struktury. Zapoznaj się z dokumentacją zestawu znaków.
System.Char Albo char* w char16_t* zależności od CharSet metody P/Invoke lub struktury. Zapoznaj się z dokumentacją zestawu znaków.
nint System.IntPtr intptr_t
nuint System.UIntPtr uintptr_t
Typy wskaźników .NET (np. void*) void*
Typ pochodzący z System.Runtime.InteropServices.SafeHandle void*
Typ pochodzący z System.Runtime.InteropServices.CriticalHandle void*
bool System.Boolean Typ Win32 BOOL
decimal System.Decimal Struktura MODELU COM DECIMAL
Delegat platformy .NET Wskaźnik funkcji natywnej
System.DateTime Typ Win32 DATE
System.Guid Typ Win32 GUID

Kilka kategorii marshalingu ma różne wartości domyślne, jeśli jesteś marshalling jako parametr lub struktura.

Typ platformy .NET Typ natywny (parametr) Typ natywny (pole)
Tablica .NET Wskaźnik na początek tablicy natywnych reprezentacji elementów tablicy. Niedozwolone bez atrybutu [MarshalAs]
Klasa z wartością LayoutKindSequential lub Explicit Wskaźnik do natywnej reprezentacji klasy Natywna reprezentacja klasy

Poniższa tabela zawiera domyślne reguły marshalingu, które są tylko systemem Windows. Na platformach innych niż Windows nie można marshalingu tych typów.

Typ platformy .NET Typ natywny (parametr) Typ natywny (pole)
System.Object VARIANT IUnknown*
System.Array Interfejs COM Niedozwolone bez atrybutu [MarshalAs]
System.ArgIterator va_list Niedozwolone
System.Collections.IEnumerator IEnumVARIANT* Niedozwolone
System.Collections.IEnumerable IDispatch* Niedozwolone
System.DateTimeOffset int64_t reprezentujący liczbę kleszczy od północy 1 stycznia 1601 r. int64_t reprezentujący liczbę kleszczy od północy 1 stycznia 1601 r.

Niektóre typy można rozmieszać tylko jako parametry, a nie jako pola. Te typy są wymienione w poniższej tabeli:

Typ platformy .NET Typ natywny (tylko parametr)
System.Text.StringBuilder Albo char* albo char16_t* w zależności od CharSet wywołania P/Invoke. Zapoznaj się z dokumentacją zestawu znaków.
System.ArgIterator va_list (tylko w systemie Windows x86/x64/arm64)
System.Runtime.InteropServices.ArrayWithOffset void*
System.Runtime.InteropServices.HandleRef void*

Jeśli te wartości domyślne nie robią dokładnie tego, co chcesz, możesz dostosować sposób działania parametrów. W artykule dotyczącym marshalingu parametrów przedstawiono sposób dostosowywania różnych typów parametrów.

Domyślne marshaling w scenariuszach COM

Podczas wywoływania metod na obiektach COM na platformie .NET środowisko uruchomieniowe platformy .NET zmienia domyślne reguły marshalingu, aby pasować do typowych semantyki COM. W poniższej tabeli wymieniono reguły używane przez środowiska uruchomieniowe platformy .NET w scenariuszach COM:

Typ platformy .NET Typ natywny (wywołania metod COM)
System.Boolean VARIANT_BOOL
StringBuilder LPWSTR
System.String BSTR
Typy delegatów _Delegate* w programie .NET Framework. Niedozwolone w programach .NET Core i .NET 5+.
System.Drawing.Color OLECOLOR
Tablica .NET SAFEARRAY
System.String[] SAFEARRAYz s BSTR

Klasy i struktury marshalling

Innym aspektem marshalingu typu jest sposób przekazywania struktury do metody niezarządzanej. Na przykład niektóre metody niezarządzane wymagają struktury jako parametru. W takich przypadkach należy utworzyć odpowiednią strukturę lub klasę w zarządzanej części świata, aby użyć jej jako parametru. Jednak samo zdefiniowanie klasy nie jest wystarczające, należy również poinstruować marshaller, jak mapować pola w klasie na niezarządzaną strukturę. StructLayout W tym przypadku atrybut staje się przydatny.

[DllImport("kernel32.dll")]
static extern void GetSystemTime(SystemTime systemTime);

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

Poprzedni kod przedstawia prosty przykład wywoływania GetSystemTime() funkcji. Interesujący bit znajduje się w wierszu 4. Atrybut określa, że pola klasy powinny być mapowane sekwencyjnie do struktury po drugiej stronie (niezarządzane). Oznacza to, że nazewnictwo pól nie jest ważne, tylko ich kolejność jest ważna, ponieważ musi odpowiadać niezarządzanej struktury, pokazanej w poniższym przykładzie:

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

Czasami domyślne marshalling struktury nie robi tego, czego potrzebujesz. Artykuł Dostosowywanie struktury marshalling zawiera informacje na temat dostosowywania sposobu działania struktury.