Marshalling dos tipos
Marshalling é o processo de transformar tipos quando precisam atravessar entre código nativo e gerenciado.
O marshalling é necessário porque os tipos são diferentes, no código gerenciado e não gerenciado. No código gerenciado, por exemplo, você tem uma string
, enquanto as cadeias de caracteres não gerenciadas podem ter uma codificação string
do .NET (UTF-16), uma codificação de Página de Código ANSI, UTF-8, com terminação nula, ASCII etc. Por padrão, o subsistema P/Invoke tenta fazer a coisa certa com base no comportamento padrão, descrito neste artigo. Contudo, nas situações em que você precisa de controle extra, pode utilizar o atributo MarshalAs para especificar qual é o tipo esperado no lado não gerenciado. Por exemplo, se quiser que a cadeia de caracteres seja enviada como uma cadeia de caracteres UTF-8 com terminação nula, você pode fazer o seguinte:
[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);
Se você aplicar o atributo System.Runtime.CompilerServices.DisableRuntimeMarshallingAttribute
ao assembly, as regras na seção a seguir não serão aplicadas. Para obter informações sobre como os valores do .NET são expostos ao código nativo quando esse atributo é aplicado, confira marshalling de runtime desabilitado.
Regras padrão para tipos comuns de marshalling
Geralmente, o runtime tenta fazer a "coisa certa" ao realizar marshal para exigir a menor quantidade de trabalho de você. As tabelas a seguir descrevem como cada tipo tem o marshal realizado padrão quando usado em um parâmetro ou campo. Os tipos de caracteres e números inteiros de largura fixa C99/C++11 são usados para garantir que a tabela a seguir esteja correta para todas as plataformas. Use 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 marshalling é o mesmo para ambos P/Invoke e o marshalling do 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 |
char ou char16_t , dependendo da codificação do P/Invoke ou da estrutura. Confira a documentação do conjunto de caracteres. |
System.Char |
char* ou char16_t* , dependendo a codificação do P/Invoke ou da estrutura. Confira a documentação do conjunto de caracteres. |
|
nint |
System.IntPtr |
intptr_t |
nuint |
System.UIntPtr |
uintptr_t |
Tipos de ponteiro do .NET (por exemplo, void* ) |
void* |
|
Tipo derivado de System.Runtime.InteropServices.SafeHandle |
void* |
|
Tipo derivado de System.Runtime.InteropServices.CriticalHandle |
void* |
|
bool |
System.Boolean |
Tipo BOOL Win32 |
decimal |
System.Decimal |
Struct DECIMAL COM |
Representante do .NET | Ponteiro de função nativo | |
System.DateTime |
Tipo DATE Win32 |
|
System.Guid |
Tipo GUID Win32 |
Algumas categorias de marshalling têm padrões diferentes se você estiver realizando marshaling 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 é permitida sem um atributo [MarshalAs] |
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 marshalling padrão que são somente do Windows. Em plataformas que não são Windows, você não pode realizar marshal desses tipos.
Tipo .NET | Tipo nativo (parâmetro) | Tipo nativo (campo) |
---|---|---|
System.Object |
VARIANT |
IUnknown* |
System.Array |
Interface COM | Não é permitida sem um atributo [MarshalAs] |
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 tiques desde a meia-noite de 1º de janeiro de 1601 |
int64_t representando o número de tiques desde a meia-noite de 1º de janeiro de 1601 |
Alguns tipos só podem ter o marshal realizado como parâmetros e não como campos. Esses tipos estão listados na tabela a seguir:
Tipo .NET | Tipo nativo (parâmetro somente) |
---|---|
System.Text.StringBuilder |
char* ou char16_t* dependendo do CharSet do P/Invoke. Confira a documentação do conjunto de caracteres. |
System.ArgIterator |
va_list (no Windows x86/x64/arm64 somente) |
System.Runtime.InteropServices.ArrayWithOffset |
void* |
System.Runtime.InteropServices.HandleRef |
void* |
Se esses padrões não fizerem exatamente o que você deseja, personalize como os parâmetros têm o marshal realizado. O artigo sobre marshalling do parâmetro explica como personalizar a forma como os tipos de parâmetros diferentes têm o marshal realizado.
Marshalling padrão em cenários COM
Quando você chama métodos em objetos COM no .NET, o runtime do .NET altera a regras de marshalling padrão para corresponder à semântica de COM. A tabela a seguir lista as regras que os runtimes 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 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 BSTR s |
Marshalling de classes e structs
Outro aspecto do marshalling de tipos é como passar um struct para um método não gerenciado. Por exemplo, alguns dos métodos não gerenciados requerem um struct como parâmetro. Nesses casos, você precisa criar um struct correspondente ou uma classe na parte gerenciada do mundo para usar como parâmetro. No entanto, definir a classe não é suficiente; também é necessário ensinar o marshaller a mapear campos na classe para o struct não gerenciado. Aqui, o atributo StructLayout
se torna útil.
[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);
}
O código anterior mostra um exemplo simples de chamar para a função GetSystemTime()
. A parte interessante está na linha 4. O atributo especifica que os campos da classe devem ser mapeados em sequência até o struct no outro lado (não gerenciado). Isso significa que os nomes dos campos não são importantes, apenas sua ordem é importante, já que precisa corresponder ao struct não gerenciado, mostrado no exemplo abaixo:
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 Personalizando o marshalling de estrutura ensina como personalizar a forma como é feito o marshalling de sua estrutura.