Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
Às vezes, as regras de marshalling padrão para estruturas não são exatamente o que você precisa. Os runtimes do .NET fornecem alguns pontos de extensão para você personalizar o layout de sua estrutura e como é feito o marshaling dos campos. A personalização do layout da estrutura tem suporte para todos os cenários, mas a personalização do marshalling de campo só tem suporte para cenários em que o marshalling de runtime está habilitado. Se o marshalling de runtime estiver desabilitado, qualquer marshalling de campo deverá ser feito manualmente.
Note
Este artigo não aborda a personalização do marshalling para interoperabilidade gerada pela origem. Se você estiver usando a interoperabilidade gerada pela origem para P/Invokes ou COM, consulte personalização de marshalling.
Personalizar o layout da estrutura
.NET fornece o System.Runtime.InteropServices.StructLayoutAttribute atributo e a System.Runtime.InteropServices.LayoutKind enumeração para permitir que você personalize como os campos são colocados na memória. As diretrizes a seguir ajudarão você a evitar problemas comuns.
✔️ CONSIDERE usar LayoutKind.Sequential sempre que possível.
✔️ USE LayoutKind.Explicit em marshalling apenas quando seu struct nativo também tiver um layout explícito, como uma união.
❌ EVITE usar classes para expressar tipos nativos complexos por meio de herança.
❌EVITE usar LayoutKind.Explicit ao agrupar estruturas em plataformas não Windows se precisar direcionar runtimes antes de .NET Core 3.0. O runtime do .NET Core anterior à versão 3.0 não oferece suporte à passagem de estruturas explícitas por valor para funções nativas em sistemas não Windows de 64 bits da Intel ou AMD. No entanto, o runtime suporta a passagem de estruturas explícitas por referência em todas as plataformas.
Buffers de tamanho fixo e matrizes embutidas
Se você precisar definir um buffer de tamanho fixo em uma estrutura, não use a StructLayoutAttribute.Size propriedade para alocar espaço extra. A propriedade Size controla o layout não gerenciado do tipo (o layout usado ao chamar Marshal.StructureToPtr) e afeta apenas o layout gerenciado para tipos blittable. Para tipos não blittable, o runtime aloca espaço apenas com base nos campos reais, o que pode levar à corrupção de memória se você tentar usar o espaço extra.
❌ EVITE usar StructLayoutAttribute.Size para criar buffers de tamanho fixo em tipos gerenciados.
✔️ Use System.Runtime.CompilerServices.InlineArrayAttribute para definir buffers de tamanho fixo no .NET moderno (C# 12 e versões posteriores).
O exemplo a seguir mostra a abordagem incorreta que pode levar à corrupção de memória:
// ❌ DON'T: This is dangerous and can corrupt memory
[StructLayout(LayoutKind.Explicit, Size = 100)]
class Bad100ByteBuffer
{
[FieldOffset(0)]
private byte b;
unsafe void UseBuffer()
{
fixed (byte* p = &b)
{
// Writing beyond the first byte corrupts memory
}
}
}
Em vez disso, use InlineArrayAttribute para criar um buffer de tamanho adequado:
// ✔️ DO: Use InlineArrayAttribute for fixed-size buffers
class Good100ByteBuffer
{
[InlineArray(100)]
private struct Buffer100
{
private byte _element0;
}
private Buffer100 buffer;
unsafe void UseBuffer()
{
fixed (byte* p = &buffer[0])
{
// The buffer is guaranteed to be 100 bytes
}
}
}
Para obter mais informações sobre matrizes embutidas, consulte a referência de linguagem C#.
Como personalizar o marshalling de campo booliano
O código nativo tem muitas representações boolianas diferentes. Apenas em Windows, há três maneiras de representar valores boolianos. O runtime não conhece a definição nativa de sua estrutura, portanto, o melhor que ele pode fazer é estimar como realizar marshaling de seus valores boolianos. O runtime do .NET permite indicar como realizar marshaling de seu campo booliano. Os exemplos a seguir mostram como realizar o marshaling de .NET bool para diferentes tipos Boolean nativos.
Valores booleanos padrão para realizar marshal como um valor BOOL Win32 nativo de 4 bytes conforme mostrado no exemplo a seguir:
public struct WinBool
{
public bool b;
}
struct WinBool
{
public BOOL b;
};
Se você quiser ser explícito, poderá usar o UnmanagedType.Bool valor para obter o mesmo comportamento acima:
public struct WinBool
{
[MarshalAs(UnmanagedType.Bool)]
public bool b;
}
struct WinBool
{
public BOOL b;
};
Usando os valores UnmanagedType.U1 ou UnmanagedType.I1 abaixo, você pode informar o runtime para realizar marshal do campo b como um tipo bool nativo de 1 byte.
public struct CBool
{
[MarshalAs(UnmanagedType.U1)]
public bool b;
}
struct CBool
{
public bool b;
};
No Windows, você pode usar o valor UnmanagedType.VariantBool para informar ao runtime que ele deve fazer o marshaling do seu valor Boolean para um valor VARIANT_BOOL de 2 bytes:
public struct VariantBool
{
[MarshalAs(UnmanagedType.VariantBool)]
public bool b;
}
struct VariantBool
{
public VARIANT_BOOL b;
};
Note
VARIANT_BOOL é diferente da maioria dos tipos bool nesse VARIANT_TRUE = -1 e VARIANT_FALSE = 0. Além disso, todos os valores que não são iguais a VARIANT_TRUE são considerados falsos.
Personalização do marshalling de campo de matriz
O .NET também inclui algumas formas de personalizar o marshalling de matriz.
Por padrão, o .NET realiza marshal de matrizes como um ponteiro para uma lista contígua dos elementos:
public struct DefaultArray
{
public int[] values;
}
struct DefaultArray
{
int32_t* values;
};
Se você estiver interagindo com APIs COM, talvez seja necessário realizar marshal de matrizes como objetos SAFEARRAY*. Use o valor System.Runtime.InteropServices.MarshalAsAttribute e UnmanagedType.SafeArray para informar o runtime para realizar marshal de uma matriz como uma SAFEARRAY*:
public struct SafeArrayExample
{
[MarshalAs(UnmanagedType.SafeArray)]
public int[] values;
}
struct SafeArrayExample
{
SAFEARRAY* values;
};
Se você precisar personalizar que tipo de elemento está no SAFEARRAY, poderá usar os campos MarshalAsAttribute.SafeArraySubType e MarshalAsAttribute.SafeArrayUserDefinedSubType para personalizar o tipo exato de elemento do SAFEARRAY.
Se você precisar realizar marshaling da matriz in-loco, use o valor UnmanagedType.ByValArray para dizer para o marshaller realizar marshaling da matriz in-loco. Ao usar esse marshaling, você também deve fornecer um valor para o campo MarshalAsAttribute.SizeConst com o número de elementos no array, para que o ambiente de execução possa alocar corretamente espaço para a estrutura.
public struct InPlaceArray
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
public int[] values;
}
struct InPlaceArray
{
int values[4];
};
Note
O .NET não dá suporte à realização de marshal de um campo de matriz de comprimento variável como um Membro de Matriz Flexível C99.
Personalização do marshalling de campo de cadeia de caracteres
O .NET também fornece uma ampla variedade de personalizações para realizar marshal de campos de cadeia de caracteres.
Por padrão, o .NET realiza marshal de uma cadeia de caracteres como um ponteiro para uma cadeia de caracteres terminada em nulo. A codificação depende do valor do StructLayoutAttribute.CharSet campo no System.Runtime.InteropServices.StructLayoutAttribute. Se nenhum atributo for especificado, a codificação usará como padrão uma codificação ANSI.
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct DefaultString
{
public string str;
}
struct DefaultString
{
char* str;
};
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct DefaultString
{
public string str;
}
struct DefaultString
{
char16_t* str; // Could also be wchar_t* on Windows.
};
Se você precisar usar codificações diferentes para campos diferentes ou apenas preferir ser mais explícito na definição da struct, poderá usar os valores UnmanagedType.LPStr ou UnmanagedType.LPWStr em um atributo System.Runtime.InteropServices.MarshalAsAttribute.
public struct AnsiString
{
[MarshalAs(UnmanagedType.LPStr)]
public string str;
}
struct AnsiString
{
char* str;
};
public struct UnicodeString
{
[MarshalAs(UnmanagedType.LPWStr)]
public string str;
}
struct UnicodeString
{
char16_t* str; // Could also be wchar_t* on Windows.
};
Se quiser realizar marshal de suas cadeias de caracteres usando a codificação UTF-8, use o valor UnmanagedType.LPUTF8Str em seu MarshalAsAttribute.
public struct UTF8String
{
[MarshalAs(UnmanagedType.LPUTF8Str)]
public string str;
}
struct UTF8String
{
char* str;
};
Note
O uso UnmanagedType.LPUTF8Str requer .NET Framework 4.7 (ou versões posteriores) ou .NET Core 1.1 (ou versões posteriores). Ele não está disponível no .NET Standard 2.0.
Se você estiver trabalhando com APIs COM, talvez seja necessário realizar marshal de uma cadeia de caracteres como um BSTR. Usando o valor UnmanagedType.BStr, você pode realizar marshal de uma cadeia de caracteres como um BSTR.
public struct BString
{
[MarshalAs(UnmanagedType.BStr)]
public string str;
}
struct BString
{
BSTR str;
};
Ao usar uma API baseada no WinRT, talvez seja necessário realizar marshal de uma cadeia de caracteres como um HSTRING. Usando o valor UnmanagedType.HString, você pode realizar marshal de uma cadeia de caracteres como um HSTRING.
HSTRING o marshalling só é suportado em runtimes com suporte WinRT integrado. O suporte ao WinRT foi removido no .NET 5, portanto, não há suporte para marshalling HSTRING no .NET 5 ou mais recente.
public struct HString
{
[MarshalAs(UnmanagedType.HString)]
public string str;
}
struct BString
{
HSTRING str;
};
Se a sua API exigir que você passe a string diretamente na estrutura, você pode usar o valor UnmanagedType.ByValTStr. Observe que a codificação de uma cadeia de caracteres processada por ByValTStr é determinada a partir do atributo CharSet. Além disso, requer que um comprimento de cadeia de caracteres seja passado pelo MarshalAsAttribute.SizeConst campo.
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct DefaultString
{
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 4)]
public string str;
}
struct DefaultString
{
char str[4];
};
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct DefaultString
{
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 4)]
public string str;
}
struct DefaultString
{
char16_t str[4]; // Could also be wchar_t[4] on Windows.
};
Personalização do marshalling de campo decimal
Se você estiver trabalhando no Windows, poderá encontrar algumas APIs que usam a estrutura nativa CY ou CURRENCY. Por padrão, o tipo .NET decimal realizar marshal da estrutura nativa DECIMAL. No entanto, você pode usar um MarshalAsAttribute com o valor UnmanagedType.Currency para instruir o marshaller a converter um valor decimal em um valor CY nativo.
public struct Currency
{
[MarshalAs(UnmanagedType.Currency)]
public decimal dec;
}
struct Currency
{
CY dec;
};
Uniões
Uma união é um tipo de dados que pode conter diferentes tipos de dados em cima da mesma memória. É uma forma comum de dados na linguagem C. Uma união pode ser expressa em .NET usando LayoutKind.Explicit. É recomendável usar structs ao definir uma união em .NET. O uso de classes pode causar problemas de layout e produzir um comportamento imprevisível.
struct device1_config
{
void* a;
void* b;
void* c;
};
struct device2_config
{
int32_t a;
int32_t b;
};
struct config
{
int32_t type;
union
{
device1_config dev1;
device2_config dev2;
};
};
public unsafe struct Device1Config
{
void* a;
void* b;
void* c;
}
public struct Device2Config
{
int a;
int b;
}
public struct Config
{
public int Type;
public _Union Anonymous;
[StructLayout(LayoutKind.Explicit)]
public struct _Union
{
[FieldOffset(0)]
public Device1Config Dev1;
[FieldOffset(0)]
public Device2Config Dev2;
}
}
Marshal System.Object
No Windows, você pode realizar o marshaling de campos do tipo object para código nativo. Você pode converter estes campos para um de três tipos:
Por padrão, um campo do tipo object será convertido em um IUnknown* que encapsula o objeto.
public struct ObjectDefault
{
public object obj;
}
struct ObjectDefault
{
IUnknown* obj;
};
Se você quiser realizar marshal de um campo de objeto para um IDispatch*, adicione um MarshalAsAttribute com o valor UnmanagedType.IDispatch.
public struct ObjectDispatch
{
[MarshalAs(UnmanagedType.IDispatch)]
public object obj;
}
struct ObjectDispatch
{
IDispatch* obj;
};
Se você quiser realizar marshal dele como um VARIANT, adicione um MarshalAsAttribute com o valor UnmanagedType.Struct.
public struct ObjectVariant
{
[MarshalAs(UnmanagedType.Struct)]
public object obj;
}
struct ObjectVariant
{
VARIANT obj;
};
A tabela a seguir descreve como diferentes tipos de runtime do campo obj são mapeados para os vários tipos armazenados em um VARIANT:
| Tipo de .NET | Tipo VARIANT |
|---|---|
byte |
VT_UI1 |
sbyte |
VT_I1 |
short |
VT_I2 |
ushort |
VT_UI2 |
int |
VT_I4 |
uint |
VT_UI4 |
long |
VT_I8 |
ulong |
VT_UI8 |
float |
VT_R4 |
double |
VT_R8 |
char |
VT_UI2 |
string |
VT_BSTR |
System.Runtime.InteropServices.BStrWrapper |
VT_BSTR |
object |
VT_DISPATCH |
System.Runtime.InteropServices.UnknownWrapper |
VT_UNKNOWN |
System.Runtime.InteropServices.DispatchWrapper |
VT_DISPATCH |
System.Reflection.Missing |
VT_ERROR |
(object)null |
VT_EMPTY |
bool |
VT_BOOL |
System.DateTime |
VT_DATE |
decimal |
VT_DECIMAL |
System.Runtime.InteropServices.CurrencyWrapper |
VT_CURRENCY |
System.DBNull |
VT_NULL |