Agrupamento de tipo

Marshalling é o processo de transformação de tipos quando eles precisam cruzar entre código gerenciado e nativo.

O marshalling é necessário porque os tipos no código gerenciado e não gerenciado são diferentes. No código gerenciado, por exemplo, você tem um String, enquanto no mundo não gerenciado as cadeias de caracteres podem ser Unicode ("wide"), non-Unicode, null-terminated, ASCII, etc. Por padrão, o subsistema P/Invoke tenta fazer a coisa certa com base no comportamento padrão, descrito neste artigo. No entanto, para aquelas situações em que você precisa de controle extra, você pode empregar o atributo MarshalAs para especificar qual é o tipo esperado no lado não gerenciado. Por exemplo, se você quiser que a cadeia de caracteres seja enviada como uma cadeia de caracteres ANSI terminada em nulo, poderá fazê-lo da seguinte forma:

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

Se você aplicar o System.Runtime.CompilerServices.DisableRuntimeMarshallingAttribute atributo ao assembly, as regras na seção a seguir não se aplicarão. Para obter informações sobre como os valores .NET são expostos ao código nativo quando esse atributo é aplicado, consulte Empacotamento de tempo de execução desabilitado.

Regras supletivas para o agrupamento de tipos comuns

Geralmente, o tempo de execução tenta fazer a "coisa certa" ao organizar para exigir o mínimo de trabalho de você. As tabelas a seguir descrevem como cada tipo é organizado por padrão quando usado em um parâmetro ou campo. Os tipos de inteiro e caractere de largura fixa C99/C++11 são usados para garantir que a tabela a seguir esteja correta para todas as plataformas. Você pode usar qualquer tipo nativo que tenha os mesmos requisitos de alinhamento e tamanho que esses tipos.

Esta primeira tabela descreve os mapeamentos para vários tipos para os quais o empacotamento é o mesmo para P/Invoke e marshalling de campo.

Palavra-chave C# Tipo .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 Ou charchar16_t dependendo do CharSet P/Invoke ou estrutura. Consulte a documentação do charset.
System.Char Ou char*char16_t* dependendo do CharSet P/Invoke ou estrutura. Consulte a documentação do charset.
nint System.IntPtr intptr_t
nuint System.UIntPtr uintptr_t
Tipos de ponteiro .NET (ex. void*) void*
Tipo derivado de System.Runtime.InteropServices.SafeHandle void*
Tipo derivado de System.Runtime.InteropServices.CriticalHandle void*
bool System.Boolean Tipo Win32 BOOL
decimal System.Decimal Estrutura COM DECIMAL
Delegado .NET Ponteiro de função nativo
System.DateTime Tipo Win32 DATE
System.Guid Tipo Win32 GUID

Algumas categorias de empacotamento têm padrões diferentes se você estiver organizando como um parâmetro ou estrutura.

Tipo .NET Tipo nativo (parâmetro) Tipo nativo (campo)
Matriz .NET Um ponteiro para o início de uma matriz de representações nativas dos elementos da matriz. Não permitido sem um [MarshalAs] atributo
Uma classe com um LayoutKind de Sequential ou Explicit Um ponteiro para a representação nativa da classe A representação nativa da classe

A tabela a seguir inclui as regras de empacotamento padrão que são somente para Windows. Em plataformas que não sejam Windows, não é possível organizar esses tipos.

Tipo .NET Tipo nativo (parâmetro) Tipo nativo (campo)
System.Object VARIANT IUnknown*
System.Array Interface COM Não permitido sem um [MarshalAs] atributo
System.ArgIterator va_list Não permitido
System.Collections.IEnumerator IEnumVARIANT* Não permitido
System.Collections.IEnumerable IDispatch* Não permitido
System.DateTimeOffset int64_t representando o número de carrapatos desde a meia-noite de 1º de janeiro de 1601 int64_t representando o número de carrapatos desde a meia-noite de 1º de janeiro de 1601

Alguns tipos só podem ser organizados como parâmetros e não como campos. Esses tipos estão listados na tabela a seguir:

Tipo .NET Tipo nativo (somente parâmetro)
System.Text.StringBuilder Ou char*char16_t* dependendo do CharSet P/Invoke. Consulte a documentação do charset.
System.ArgIterator va_list (apenas no Windows x86/x64/arm64)
System.Runtime.InteropServices.ArrayWithOffset void*
System.Runtime.InteropServices.HandleRef void*

Se esses padrões não fizerem exatamente o que você deseja, você poderá personalizar como os parâmetros são organizados. O artigo de empacotamento de parâmetros orienta você sobre como personalizar como diferentes tipos de parâmetros são organizados.

Empacotamento padrão em cenários COM

Quando você está chamando métodos em objetos COM no .NET, o tempo de execução do .NET altera as regras de empacotamento padrão para corresponder à semântica COM comum. A tabela a seguir lista as regras que os tempos de execução do .NET usam em cenários COM:

Tipo .NET Tipo nativo (chamadas de método COM)
System.Boolean VARIANT_BOOL
StringBuilder LPWSTR
System.String BSTR
Tipos de delegados _Delegate* no .NET Framework. Não permitido no .NET Core e no .NET 5+.
System.Drawing.Color OLECOLOR
Matriz .NET SAFEARRAY
System.String[] SAFEARRAY de BSTRs

Classes e estruturas de agrupamento

Outro aspeto do tipo marshalling é como passar em uma struct para um método não gerenciado. Por exemplo, alguns dos métodos não gerenciados exigem um struct como parâmetro. Nesses casos, você precisa criar uma struct correspondente ou uma classe na parte gerenciada do mundo para usá-la como parâmetro. No entanto, apenas definir a classe não é suficiente, você também precisa instruir o marshaller como mapear campos na classe para a struct não gerenciada. Aqui o StructLayout atributo torna-se útil.

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

O código anterior mostra um exemplo simples de chamada para GetSystemTime() a função. A parte interessante está na linha 4. O atributo especifica que os campos da classe devem ser mapeados sequencialmente para o struct no outro lado (não gerenciado). Isso significa que a nomenclatura dos campos não é importante, apenas sua ordem é importante, pois precisa corresponder à struct não gerenciada, mostrada no exemplo a seguir:

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

Às vezes, o marshalling padrão para sua estrutura não faz o que você precisa. O artigo Customizing structure marshalling ensina como personalizar como sua estrutura é organizada.