Condividi tramite


Marshalling dei tipi

Il marshalling è il processo di trasformazione di tipi quando devono transitare tra codice gestito e codice nativo.

Il marshalling è necessario perché i tipi nel codice gestito e non gestito sono diversi. Nel codice gestito, ad esempio, si dispone di un string, mentre le stringhe non gestite possono essere codifica .NET string (UTF-16), codifica della tabella codici ANSI, UTF-8, terminazione null, ASCII e così via. Per impostazione predefinita, il sottosistema P/Invoke tenta di eseguire l'operazione corretta in base al comportamento predefinito, descritto in questo articolo. Tuttavia, per queste situazioni in cui è necessario un controllo aggiuntivo, è possibile usare l'attributo MarshalAs per specificare il tipo previsto sul lato non gestito. Ad esempio, se si vuole che la stringa venga inviata come stringa UTF-8 con terminazione Null, è possibile eseguire questa operazione come segue:

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

Applicando l'attributo System.Runtime.CompilerServices.DisableRuntimeMarshallingAttribute all'assembly, le regole nella sezione seguente non si applicano. Per informazioni su come i valori .NET vengono esposti al codice nativo quando viene applicato questo attributo, vedere Marshalling di runtime disabilitato.

Regole predefinite per il marshalling di tipi comuni

In genere, il runtime tenta di eseguire la "cosa giusta" quando si esegue il marshalling per richiedere la quantità minima di lavoro da parte dell'utente. Le tabelle seguenti descrivono come ogni tipo viene gestito per impostazione predefinita quando viene utilizzato come parametro o campo. I tipi di caratteri e integer a larghezza fissa C99/C++11 vengono usati per garantire che la tabella seguente sia corretta per tutte le piattaforme. È possibile usare qualsiasi tipo nativo con gli stessi requisiti di allineamento e dimensioni di questi tipi.

Questa prima tabella descrive i mapping per i vari tipi per i quali il marshalling è lo stesso sia per P/Invoke che per il marshalling dei campi.

Parola chiave C# Tipo di .NET Tipo nativo
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 char oppure char16_t a seconda della codifica del P/Invoke o della struttura. Vedere la documentazione del set di caratteri.
System.Char char* oppure char16_t* a seconda della codifica del P/Invoke o della struttura. Vedere la documentazione del set di caratteri.
nint System.IntPtr intptr_t
nuint System.UIntPtr uintptr_t
Tipi di puntatore .NET (ad esempio void*) void*
Tipo derivato da System.Runtime.InteropServices.SafeHandle void*
Tipo derivato da System.Runtime.InteropServices.CriticalHandle void*
bool System.Boolean Tipo Win32 BOOL
decimal System.Decimal Struct COM DECIMAL
Delegato .NET Puntatore a funzione nativa
System.DateTime Tipo Win32 DATE
System.Guid Tipo Win32 GUID

Alcune categorie di marshalling hanno impostazioni predefinite diverse se si esegue il marshalling come parametro o come struttura.

Tipo di .NET Tipo nativo (parametro) Tipo nativo (campo)
Matrice .NET Un puntatore all'inizio di un array che contiene le rappresentazioni native degli elementi. Non consentito senza un [MarshalAs] attributo
Classe con un LayoutKind di Sequential o Explicit Puntatore alla rappresentazione nativa della classe Rappresentazione nativa della classe

La tabella seguente include le regole di marshalling predefinite valide solo per Windows. Nelle piattaforme non Windows non è possibile effettuare il marshalling di questi tipi.

Tipo di .NET Tipo nativo (parametro) Tipo nativo (campo)
System.Object VARIANT IUnknown*
System.Array Interfaccia COM Non consentito senza un [MarshalAs] attributo
System.ArgIterator va_list Non consentito
System.Collections.IEnumerator IEnumVARIANT* Non consentito
System.Collections.IEnumerable IDispatch* Non consentito
System.DateTimeOffset int64_t che rappresenta il numero di tick dalla mezzanotte del 1° gennaio 1601 int64_t che rappresenta il numero di tick dalla mezzanotte del 1° gennaio 1601

È possibile eseguire il marshalling di alcuni tipi solo come parametri e non come campi. Questi tipi sono elencati nella tabella seguente:

Tipo di .NET Tipo nativo (solo parametro)
System.Text.StringBuilder O char* o char16_t* a seconda di CharSet del P/Invoke. Vedere la documentazione del set di caratteri.
System.ArgIterator va_list (solo in Windows x86/x64/arm64)
System.Runtime.InteropServices.ArrayWithOffset void*
System.Runtime.InteropServices.HandleRef void*

Se queste impostazioni predefinite non eseguono esattamente le operazioni desiderate, è possibile personalizzare la modalità di marshalling dei parametri. L'articolo sul marshalling dei parametri illustra come personalizzare il marshalling dei diversi tipi di parametri.

Marshalling predefinito negli scenari COM

Quando si chiamano metodi su oggetti COM in .NET, il runtime .NET modifica le regole di marshalling predefinite in modo che corrispondano alla semantica COM comune. La tabella seguente elenca le regole usate dai runtime .NET negli scenari COM:

Tipo di .NET Tipo nativo (chiamate al metodo COM)
System.Boolean VARIANT_BOOL
StringBuilder LPWSTR
System.String BSTR
Tipi delegati _Delegate* in .NET Framework. Non consentito in .NET Core e .NET 5+.
System.Drawing.Color OLECOLOR
Matrice .NET SAFEARRAY
System.String[] SAFEARRAY di BSTR

Marshalling di classi e strutture

Un altro aspetto del marshalling dei tipi è come passare una struttura a un metodo non gestito. Ad esempio, alcuni dei metodi non gestiti richiedono uno struct come parametro. In questi casi, è necessario creare uno struct o una classe corrispondente in una parte gestita del mondo per usarlo come parametro. Tuttavia, la semplice definizione della classe non è sufficiente, è anche necessario indicare al marshaller come eseguire il mapping dei campi nella classe allo struct non gestito. Qui l'attributo StructLayout diventa utile.

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

Il codice precedente mostra un semplice esempio di chiamata in GetSystemTime() funzione. La parte interessante è alla riga 4. L'attributo specifica che i campi della classe devono essere mappati in sequenza allo struct sull'altro lato (non gestito). Ciò significa che la denominazione dei campi non è importante, ma solo il relativo ordine è importante, perché deve corrispondere allo struct non gestito, illustrato nell'esempio seguente:

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

In alcuni casi, il marshalling predefinito per la tua struttura non soddisfa le tue esigenze. L'articolo Personalizzazione del marshalling della struttura illustra come personalizzare come viene eseguito il marshalling della struttura.