Zařazování typů

Marshalling je proces transformace typů, když potřebují křížit mezi spravovaným a nativním kódem.

Seřazování je potřeba, protože typy ve spravovaném a nespravovaném kódu se liší. Ve spravovaném Stringkódu máte například , zatímco v nespravovaných světových řetězcích mohou být Unicode ("široké"), non-Unicode, null-terminated, ASCII atd. Ve výchozím nastavení se subsystém P/Invoke pokusí provést správnou věc na základě výchozího chování popsaného v tomto článku. V takových situacích, kdy potřebujete další kontrolu, ale můžete použít atribut MarshalAs k určení očekávaného typu na nespravované straně. Pokud například chcete, aby se řetězec odeslal jako řetězec ANSI s ukončenou hodnotou null, můžete to udělat takto:

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

Pokud použijete System.Runtime.CompilerServices.DisableRuntimeMarshallingAttribute atribut na sestavení, pravidla v následující části se nevztahují. Informace o tom, jak jsou hodnoty .NET při použití tohoto atributu vystaveny nativnímu kódu, najdete v tématu zařazování zablokovaného modulu runtime.

Výchozí pravidla pro zařazování běžných typů

Obecně platí, že modul runtime se pokusí provést "správnou věc" při zařazování, aby vyžadoval nejnižší množství práce od vás. Následující tabulky popisují, jak je každý typ ve výchozím nastavení při použití v parametru nebo poli. Celočíselné a znakové typy C99/C++11 s pevnou šířkou se používají k zajištění správnosti následující tabulky pro všechny platformy. Můžete použít libovolný nativní typ, který má stejné požadavky na zarovnání a velikost jako tyto typy.

Tato první tabulka popisuje mapování pro různé typy, pro které je seřaďování stejné pro přiřazování nespravovaného kódu i pro řazení polí.

Klíčové slovo jazyka C# Typ .NET Nativní 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 Buď char nebo char16_t v závislosti na CharSet volání nespravovaného kódu nebo struktuře. Viz dokumentace ke znakové sadě.
System.Char Buď char* nebo char16_t* v závislosti na CharSet volání nespravovaného kódu nebo struktuře. Viz dokumentace ke znakové sadě.
nint System.IntPtr intptr_t
nuint System.UIntPtr uintptr_t
Typy ukazatelů .NET (např. void*) void*
Typ odvozený z System.Runtime.InteropServices.SafeHandle void*
Typ odvozený z System.Runtime.InteropServices.CriticalHandle void*
bool System.Boolean Typ Win32 BOOL
decimal System.Decimal COM DECIMAL – struktura
Delegát .NET Ukazatel nativní funkce
System.DateTime Typ Win32 DATE
System.Guid Typ Win32 GUID

Pokud řazování zařazujete jako parametr nebo strukturu, má několik kategorií řazení jiné výchozí hodnoty.

Typ .NET Nativní typ (parametr) Nativní typ (pole)
Pole .NET Ukazatel na začátek pole nativních reprezentací prvků pole. Nepovoleno bez atributu [MarshalAs]
Třída s LayoutKindSequentialExplicit Ukazatel na nativní reprezentaci třídy Nativní reprezentace třídy

Následující tabulka obsahuje výchozí pravidla zařazování, která jsou pouze pro Windows. Na jiných platformách než Windows nemůžete tyto typy zařašovat.

Typ .NET Nativní typ (parametr) Nativní typ (pole)
System.Object VARIANT IUnknown*
System.Array Rozhraní MODELU COM Nepovoleno bez atributu [MarshalAs]
System.ArgIterator va_list Nepovoleno
System.Collections.IEnumerator IEnumVARIANT* Nepovoleno
System.Collections.IEnumerable IDispatch* Nepovoleno
System.DateTimeOffset int64_t představující počet ticků od půlnoci 1. ledna 1601 int64_t představující počet ticků od půlnoci 1. ledna 1601

Některé typy lze zařaďovat pouze jako parametry, nikoli jako pole. Tyto typy jsou uvedeny v následující tabulce:

Typ .NET Nativní typ (pouze parametr)
System.Text.StringBuilder Buď char* nebo char16_t* v závislosti na CharSet volání nespravovaného kódu. Viz dokumentace ke znakové sadě.
System.ArgIterator va_list (jenom ve Windows x86/x64/arm64)
System.Runtime.InteropServices.ArrayWithOffset void*
System.Runtime.InteropServices.HandleRef void*

Pokud tyto výchozí hodnoty nedělají přesně to, co potřebujete, můžete přizpůsobit, jak se parametry zařaďují. Článek o zařazování parametrů vás provede přizpůsobením způsobu zařazování různých typů parametrů.

Výchozí zařazování ve scénářích modelu COM

Při volání metod pro objekty MODELU COM v .NET modul runtime .NET změní výchozí pravidla zařazování tak, aby odpovídala běžné sémantice modelu COM. Následující tabulka uvádí pravidla, která moduly runtime .NET používají ve scénářích MODELU COM:

Typ .NET Nativní typ (volání metody MODELU COM)
System.Boolean VARIANT_BOOL
StringBuilder LPWSTR
System.String BSTR
Typy delegátů _Delegate* v rozhraní .NET Framework. Zakázáno v .NET Core a .NET 5+.
System.Drawing.Color OLECOLOR
Pole .NET SAFEARRAY
System.String[] SAFEARRAYz BSTR

Zařazování tříd a struktur

Dalším aspektem zařazování typů je předání struktury nespravované metodě. Například některé nespravované metody vyžadují strukturu jako parametr. V těchto případech potřebujete vytvořit odpovídající strukturu nebo třídu ve spravované části světa, abyste ji mohli použít jako parametr. Stačí ale jenom definovat třídu, musíte také instrukci instruovat, jak mapovat pole ve třídě na nespravovanou strukturu. Tady se StructLayout atribut stane užitečným.

[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);
}

Předchozí kód ukazuje jednoduchý příklad volání do GetSystemTime() funkce. Zajímavý bit je na řádku 4. Atribut určuje, že pole třídy by měla být namapována postupně na strukturu na druhé (nespravované) straně. To znamená, že pojmenování polí není důležité, pouze jejich pořadí je důležité, protože musí odpovídat nespravované struktuře, jak je znázorněno v následujícím příkladu:

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

Někdy výchozí zařazování struktury nedělá to, co potřebujete. Článek Přizpůsobení struktury seřazováním vás naučí, jak přizpůsobit způsob zařazování struktury.