Nota
O acesso a esta página requer autorização. Pode tentar iniciar sessão ou alterar os diretórios.
O acesso a esta página requer autorização. Pode tentar alterar os diretórios.
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 gerido, por exemplo, temos um string, enquanto as strings não geridas podem ser codificadas em .NET string (UTF-16), codificação de Página de Códigos ANSI, UTF-8, terminadas em nulo, ASCII, etc. Por defeito, 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 UTF-8 terminada em nulo, você pode fazê-lo da seguinte forma:
[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);
Se aplicar o atributo System.Runtime.CompilerServices.DisableRuntimeMarshallingAttribute ao assemblage, as regras na secção a seguir não se aplicarão. Para informações sobre como os valores de .NET são expostos ao código nativo quando este atributo é aplicado, veja marshalling de runtime desativado.
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 tipos para os quais o marshalling é o mesmo para P/Invoke e marshalling de campo.
Importante
Ao chamar uma função C que usa long, use CLong ou CULong (.NET 6+) em vez de C# long. Para detalhes e soluções alternativas para versões .NET anteriores, veja Considerações sobre tipos de dados multiplataforma.
Observação
O tipo wchar_t é UTF-16 (2 bytes) no Windows mas é definido pelo compilador noutras plataformas — tipicamente UTF-32 (4 bytes) no Linux e macOS. Por causa disso, wchar_t* é difícil de usar como uma única ABI multiplataforma. Quando desenha uma API nativa multiplataforma, prefere char* com um contrato de codificação claramente definido (por exemplo, UTF-8) em vez de wchar_t*.
Observação
As strings nativas char*, utilizam a codificação que a biblioteca ou plataforma define. Quando chama uma função C que toma char*, faça corresponder a codificação esperada escolhendo a opção correta de marshalling de strings, como StringMarshalling.Utf8 para UTF-8, StringMarshalling.Utf16 para UTF-16 ou StringMarshalling.Custom para outras codificações.
| 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 char ou char16_t dependendo da codificação do P/Invoke ou da estrutura de dados. Consulte a documentação do charset. |
System.Char |
Ou char* ou char16_t* dependendo da codificação do P/Invoke ou da estrutura de dados. Consulte a documentação do charset. |
|
nint |
System.IntPtr |
intptr_t |
nuint |
System.UIntPtr |
uintptr_t |
.NET Tipos de ponteiros (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 |
| .NET Delegate (delegado do .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) |
|---|---|---|
| Array .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 seguinte inclui as regras padrão de marshalling que são aplicáveis apenas ao Windows. Em plataformas que não são Windows, não se podem organizar estes 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* ou char16_t* dependendo do CharSet do 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 interligação de parâmetros orienta o leitor sobre como personalizar a forma como diferentes tipos de parâmetros são transferidos.
Empacotamento padrão em cenários COM
Quando está a chamar métodos em objetos COM em .NET, o runtime .NET altera as regras de marshalling padrão para corresponder à semântica comum do COM. A tabela seguinte lista as regras que os runtimes .NET utilizam 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. Proibido em .NET Core e .NET 5+. |
System.Drawing.Color |
OLECOLOR |
| Array .NET | SAFEARRAY |
System.String[] |
SAFEARRAY de BSTRs |
Classes e estruturas de agrupamento
Outro aspeto do tipo marshalling é como passar uma struct para um método não gerido. 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; é necessário instruir o marshaller sobre como mapear os campos da classe para a estrutura não gerida. Aqui o StructLayout atributo torna-se útil.
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;
}
}
O código anterior mostra um exemplo simples de chamada para GetSystemTime() função. A parte interessante está na linha 13. 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, *LPSYSTEMTIME;
À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.