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.
Por vezes, as regras predefinidas de marshalling para estruturas podem não ser exatamente as de que necessita. Os runtimes .NET fornecem alguns pontos de extensão para personalizar o layout da sua estrutura e como os campos são organizados. A personalização da disposição da estrutura é suportada para todos os cenários, mas a personalização do empacotamento de campos só é suportada nos cenários em que o empacotamento em tempo de execução está ativado. Se o marshalling em tempo de execução estiver desativado, o marshalling de qualquer campo deve ser feito manualmente.
Note
Este artigo não aborda a personalização do marshalling para interoperabilidade gerada por código-fonte. Se estiveres a usar interop gerada pelo código-fonte para P/Invokes ou COM, consulta personalizar o empacotamento.
Personalizar a disposição da estrutura
.NET fornece o System.Runtime.InteropServices.StructLayoutAttribute atributo e a System.Runtime.InteropServices.LayoutKind enumeração para permitir que personalize a forma como os campos são colocados na memória. As orientações seguintes vão ajudá-lo a evitar problemas comuns.
✔️ CONSIDERE usar LayoutKind.Sequential sempre que possível.
✔️ Utilize apenas LayoutKind.Explicit no marshaling quando a sua estrutura nativa também tiver um layout explícito, por exemplo, uma união.
❌ EVITE usar classes para representar tipos complexos nativos através de herança.
❌EVITE usar LayoutKind.Explicit ao organizar estruturas em plataformas que não sejam Windows se precisar de direcionar runtimes anteriores ao .NET Core 3.0. O tempo de execução do .NET Core antes da 3.0 não suporta a passagem de estruturas explícitas por valor para funções nativas em sistemas Intel ou AMD de 64 bits não Windows. No entanto, o runtime suporta a passagem de estruturas explícitas por referência em todas as plataformas.
Buffers de tamanho fixo e matrizes em linha
Se precisar de definir um buffer de tamanho fixo numa estrutura, não use a StructLayoutAttribute.Size propriedade para alocar espaço extra. A Size propriedade controla o layout não gerido do tipo (o layout usado ao chamar Marshal.StructureToPtr) e só afeta o layout gerido para tipos blittable. Para tipos não blitáveis, o runtime só aloca espaço com base nos campos reais, o que pode levar à corrupção da memória se tentares usar o espaço extra.
❌ EVITE usar StructLayoutAttribute.Size para criar buffers de tamanho fixo em tipos geridos.
✔️ USE System.Runtime.CompilerServices.InlineArrayAttribute para definir buffers de tamanho fixo no .NET moderno (C# 12 e versões posteriores).
O exemplo seguinte mostra a abordagem incorreta que pode levar à corrupção da 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 mais informações sobre matrizes inline, consulte a referência da linguagem C#.
Personalizar a conversão de campos booleanos
O código nativo tem muitas representações booleanas diferentes. Só no Windows, existem três formas de representar valores booleanos. O runtime não conhece a definição nativa da tua estrutura, por isso o melhor que pode fazer é tentar adivinhar como organizar os teus valores booleanos. O tempo de execução .NET fornece uma forma de indicar como organizar o seu campo Booleano. Os exemplos seguintes mostram como efetuar o marshaling de .NET bool para diferentes tipos Boolean nativos.
Os valores Boolean são, por predefinição, convertidos por marshaling para um valor nativo Win32 BOOL de 4 bytes, conforme ilustrado no exemplo seguinte:
public struct WinBool
{
public bool b;
}
struct WinBool
{
public BOOL b;
};
Se quiser ser explícito, pode 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;
};
Utilizando os valores UnmanagedType.U1 ou UnmanagedType.I1 abaixo, pode indicar ao tempo de execução para empacotar o campo b como um tipo nativo bool de 1 byte.
public struct CBool
{
[MarshalAs(UnmanagedType.U1)]
public bool b;
}
struct CBool
{
public bool b;
};
No Windows, pode utilizar o valor UnmanagedType.VariantBool para indicar ao ambiente de execução que converta o valor booleano num 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 do array
.NET inclui também algumas formas de personalizar o marshalling de matrizes.
Por defeito, o .NET organiza os arrays como um apontador para uma lista contígua dos elementos:
public struct DefaultArray
{
public int[] values;
}
struct DefaultArray
{
int32_t* values;
};
Se estiveres a trabalhar com APIs COM, podes ter de empacotar matrizes como objetos SAFEARRAY*. Pode utilizar o valor System.Runtime.InteropServices.MarshalAsAttribute e UnmanagedType.SafeArray para indicar ao ambiente de execução que organize um array como um SAFEARRAY*:
public struct SafeArrayExample
{
[MarshalAs(UnmanagedType.SafeArray)]
public int[] values;
}
struct SafeArrayExample
{
SAFEARRAY* values;
};
Se precisares de personalizar que tipo de elemento está no SAFEARRAY, então podes usar os MarshalAsAttribute.SafeArraySubType campos e MarshalAsAttribute.SafeArrayUserDefinedSubType para personalizar o tipo exato de elemento do SAFEARRAY.
Se precisares de empacotar o array no próprio local, podes usar o valor UnmanagedType.ByValArray para dizer ao empacotador para empacotar o array no próprio local. Ao usar este marshalling, também tem de fornecer um valor ao campo MarshalAsAttribute.SizeConst para indicar o número de elementos na matriz, 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
.NET não suporta o empacotamento de um campo de comprimento variável como um membro de array flexível do C99.
Personalizar o empacotamento de campos de cadeias de caracteres
O .NET também oferece uma grande variedade de personalizações para o marshalling de campos de strings.
Por predefinição, o .NET converte uma cadeia num apontador para uma cadeia terminada em nulo. A codificação depende do valor do StructLayoutAttribute.CharSet campo no System.Runtime.InteropServices.StructLayoutAttribute. Se não for especificado nenhum atributo, a codificação é, por predefiniçã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 precisares de usar codificações diferentes para campos distintos ou preferires ser mais explícito na definição da tua struct, podes usar os UnmanagedType.LPStr valores ou UnmanagedType.LPWStr num System.Runtime.InteropServices.MarshalAsAttribute atributo.
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 quiseres organizar as tuas cadeias de caracteres com a codificação UTF-8, podes usar o valor UnmanagedType.LPUTF8Str no teu ficheiro MarshalAsAttribute.
public struct UTF8String
{
[MarshalAs(UnmanagedType.LPUTF8Str)]
public string str;
}
struct UTF8String
{
char* str;
};
Note
A utilização UnmanagedType.LPUTF8Str requer o .NET Framework 4.7 (ou versões posteriores) ou o .NET Core 1.1 (ou versões posteriores). Não está disponível no .NET Standard 2.0.
Se estiveres a trabalhar com APIs COM, poderás ter de empacotar uma cadeia de caracteres como um BSTR. Utilizando o valor UnmanagedType.BStr, pode efetuar o marshaling de uma cadeia de caracteres como um BSTR.
public struct BString
{
[MarshalAs(UnmanagedType.BStr)]
public string str;
}
struct BString
{
BSTR str;
};
Ao utilizar uma API baseada em WinRT, poderá ser necessário efetuar o marshaling de uma cadeia de caracteres como HSTRING. Utilizando o valor UnmanagedType.HString, pode empacotar uma cadeia de caracteres como um HSTRING.
HSTRING O marshalling só é suportado em ambientes de execução com suporte incorporado para WinRT. O suporte ao WinRT foi removido no .NET 5, pelo que HSTRING o marshalling não é suportado no .NET 5 ou versões posteriores.
public struct HString
{
[MarshalAs(UnmanagedType.HString)]
public string str;
}
struct BString
{
HSTRING str;
};
Se a tua API exigir que passes a cadeia de caracteres diretamente na estrutura, podes usar o valor UnmanagedType.ByValTStr. Tenha em atenção que a codificação de uma cadeia de caracteres convertida por ByValTStr é determinada a partir do atributo CharSet. Além disso, requer que o comprimento da cadeia seja fornecido pelo campo MarshalAsAttribute.SizeConst.
[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.
};
Personalizar a serialização de um campo decimal
Se estiveres a trabalhar no Windows, poderás encontrar algumas APIs que usam a estrutura nativa CY ou CURRENCY. Por predefinição, o tipo .NET decimal é convertido em marshaling para a estrutura nativa DECIMAL. No entanto, pode usar um MarshalAsAttribute com o valor UnmanagedType.Currency para instruir o marshaller a converter um valor decimal num valor CY nativo.
public struct Currency
{
[MarshalAs(UnmanagedType.Currency)]
public decimal dec;
}
struct Currency
{
CY dec;
};
Sindicatos
Uma união é um tipo de dado que pode conter diferentes tipos de dados sobre a mesma memória. É uma forma comum de dados na linguagem C. Uma união pode ser expressa em .NET usando LayoutKind.Explicit. Recomenda-se usar structs ao definir uma união em .NET. Usar classes pode causar problemas de layout e produzir comportamentos imprevisíveis.
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;
}
}
Marechal System.Object
No Windows, pode efetuar marshaling de campos do tipo object em código nativo. Pode agrupar estes campos num dos três tipos:
Por defeito, um campo do tipo object será convertido num IUnknown* que envolve o objeto.
public struct ObjectDefault
{
public object obj;
}
struct ObjectDefault
{
IUnknown* obj;
};
Se quiseres serializar um campo de objeto para um IDispatch*, adiciona um(a) MarshalAsAttribute com o valor UnmanagedType.IDispatch.
public struct ObjectDispatch
{
[MarshalAs(UnmanagedType.IDispatch)]
public object obj;
}
struct ObjectDispatch
{
IDispatch* obj;
};
Se quiseres serializá-lo como um VARIANT, adiciona um MarshalAsAttribute com o valor UnmanagedType.Struct.
public struct ObjectVariant
{
[MarshalAs(UnmanagedType.Struct)]
public object obj;
}
struct ObjectVariant
{
VARIANT obj;
};
A tabela seguinte descreve como diferentes tipos de tempo de execução do obj campo correspondem aos vários tipos armazenados num VARIANT:
| Tipo .NET | Tipo de variante |
|---|---|
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 |