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 string
istnieje ciąg , a ciągi niezarządzane mogą być kodowaniem platformy .NET string
(UTF-16), kodowaniem strony kodowej ANSI, kodowaniem strony kodowej UTF-8, zakończonymi wartościami null, 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 został wysłany jako ciąg utF-8 zakończony o wartości null, możesz to zrobić w następujący sposób:
[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);
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.
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ą LayoutKind Sequential 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:
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[] |
SAFEARRAY z 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.
[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);
}
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.