Sdílet prostřednictvím


Zařazování typů

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

Marshalling 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 nespravované řetězce mohou být kódování .NET string (UTF-16), kódování znakové stránky ANSI, UTF-8, 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 UTF-8 s ukončenou hodnotou null, můžete to udělat takto:

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

// or

[LibraryImport("somenativelibrary.dll", StringMarshalling = StringMarshalling.Utf8)]
static extern int MethodB(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 dostupné nativnímu kódu, najdete v tématu zakázané zařazování za běhu.

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 se každý typ ve výchozím nastavení zpracovává, když je 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 typy, pro které je zprostředkování stejné pro volání platformy i zprostředkování 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 kódování P/Invoke nebo struktury. Podívejte se na dokumentaci ke znakové sadě.
System.Char Buď char* nebo char16_t* v závislosti na kódování P/Invoke nebo struktury. Podívejte se na dokumentaci 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 s nativními reprezentacemi jeho prvků. 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 zprostředková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í 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 v rámci P/Invoke volání. 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 COM v .NET runtime .NET změní výchozí pravidla maršálování tak, aby odpovídala běžné sémantice COM. Následující tabulka uvádí pravidla, která prostředí runtime .NET používají ve scénářích COM.

Typ .NET Nativní typ (volání metod 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[] SAFEARRAY z 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. Pro definování třídy však nestačí, také musíte dát instrukci marshalleru, jak mapovat pole ve třídě na nespravovanou strukturu. Tady se StructLayout atribut stane užitečným.

using System;
using System.Runtime.InteropServices;

Win32Interop.GetSystemTime(out Win32Interop.SystemTime systemTime);

Console.WriteLine(systemTime.Year);

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

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

Předchozí kód ukazuje jednoduchý příklad volání do GetSystemTime() funkce. Zajímavá část je na řádku 13. Atribut určuje, že pole třídy by měla být postupně namapována na strukturu na druhé straně, která je nespravovaná. 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, *LPSYSTEMTIME;

Někdy výchozí mapování pro vaši strukturu nefunguje podle vašich potřeb. Článek Přizpůsobení struktury maršálováním vás naučí, jak přizpůsobit způsob maršálování struktury.