Personalizar o empacotamento da estrutura
Às vezes, as regras padrão de empacotamento para estruturas não são exatamente o que você precisa. Os tempos de execução do .NET fornecem alguns pontos de extensão para você personalizar o layout da sua estrutura e como os campos são organizados. A personalização do layout da estrutura é suportada para todos os cenários, mas a personalização do empacotamento de campo só é suportada para cenários em que o empacotamento de tempo de execução está habilitado. Se o empacotamento de tempo de execução estiver desativado, qualquer empacotamento de campo deverá ser feito manualmente.
Nota
Este artigo não aborda a personalização do marshalling para interoperabilidade gerada pelo código-fonte. Se você estiver usando a interoperabilidade gerada pelo código-fonte para P/Invokes ou COM, consulte Personalizando o empacotamento.
Personalizar layout de estrutura
O .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 orientações seguintes ajudá-lo-ão a evitar problemas comuns.
✔️ CONSIDERE usar LayoutKind.Sequential
sempre que possível.
✔️ USE apenas LayoutKind.Explicit
em marshalling quando sua estrutura nativa 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 organizar estruturas em plataformas que não sejam Windows se precisar direcionar tempos de execução antes do .NET Core 3.0. O tempo de execução do .NET Core antes da versão 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 que não sejam Windows. No entanto, o tempo de execução suporta a passagem de estruturas explícitas por referência em todas as plataformas.
Personalizando o empacotamento de campo booleano
O código nativo tem muitas representações booleanas diferentes. Somente no Windows, há três maneiras de representar valores booleanos. O tempo de execução não conhece a definição nativa de sua estrutura, então o melhor que ele pode fazer é adivinhar como organizar seus valores booleanos. O tempo de execução do .NET fornece uma maneira de indicar como organizar seu campo booleano. Os exemplos a seguir mostram como empacotar o .NET bool
para diferentes tipos booleanos nativos.
Valores booleanos padrão para empacotamento como um valor Win32 BOOL
nativo de 4 bytes, conforme mostrado no exemplo a seguir:
public struct WinBool
{
public bool b;
}
struct WinBool
{
public BOOL b;
};
Se quiser ser explícito, você 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;
};
Usando os UnmanagedType.U1
valores ou UnmanagedType.I1
abaixo, você pode dizer ao tempo de execução para organizar o b
campo como um tipo nativo bool
de 1 byte.
public struct CBool
{
[MarshalAs(UnmanagedType.U1)]
public bool b;
}
struct CBool
{
public bool b;
};
No Windows, você pode usar o UnmanagedType.VariantBool valor para informar ao tempo de execução para empacotar seu valor booleano para um valor de 2 bytes VARIANT_BOOL
:
public struct VariantBool
{
[MarshalAs(UnmanagedType.VariantBool)]
public bool b;
}
struct VariantBool
{
public VARIANT_BOOL b;
};
Nota
VARIANT_BOOL
é diferente da maioria dos tipos de bool em que VARIANT_TRUE = -1
e VARIANT_FALSE = 0
. Além disso, todos os valores que não são iguais são VARIANT_TRUE
considerados falsos.
Personalizando o empacotamento de campo de matriz
O .NET também inclui algumas maneiras de personalizar a organização de matrizes.
Por padrão, o .NET marshals arrays como um ponteiro para uma lista contígua dos elementos:
public struct DefaultArray
{
public int[] values;
}
struct DefaultArray
{
int32_t* values;
};
Se você estiver fazendo interface com APIs COM, talvez seja necessário organizar matrizes como SAFEARRAY*
objetos. Você pode usar o System.Runtime.InteropServices.MarshalAsAttribute e o UnmanagedType.SafeArray valor para informar o tempo de execução para organizar uma matriz como um 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
, então você pode usar os MarshalAsAttribute.SafeArraySubType campos e MarshalAsAttribute.SafeArrayUserDefinedSubType para personalizar o tipo de elemento exato do SAFEARRAY
.
Se você precisar organizar a matriz no local, você pode usar o UnmanagedType.ByValArray valor para dizer ao marshaller para organizar a matriz no local. Ao usar esse empacotamento, você também deve fornecer um valor para o MarshalAsAttribute.SizeConst campo para o número de elementos na matriz para que o tempo de execução possa alocar corretamente o espaço para a estrutura.
public struct InPlaceArray
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
public int[] values;
}
struct InPlaceArray
{
int values[4];
};
Nota
O .NET não suporta a organização de um campo de matriz de comprimento variável como um membro de matriz flexível C99.
Personalizando a organização de campos de cadeia de caracteres
O .NET também fornece uma ampla variedade de personalizações para organizar campos de cadeia de caracteres.
Por padrão, o .NET marshals 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, o padrão de codificação será 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 em sua definição struct, poderá usar os UnmanagedType.LPStr valores ou UnmanagedType.LPWStr em um 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 você quiser organizar suas cadeias de caracteres usando a codificação UTF-8, você pode usar o UnmanagedType.LPUTF8Str valor em seu MarshalAsAttribute.
public struct UTF8String
{
[MarshalAs(UnmanagedType.LPUTF8Str)]
public string str;
}
struct UTF8String
{
char* str;
};
Nota
O uso UnmanagedType.LPUTF8Str requer o .NET Framework 4.7 (ou versões posteriores) ou o .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 organizar uma cadeia de caracteres como um BSTR
arquivo . Usando o UnmanagedType.BStr valor, você pode organizar uma cadeia de caracteres como um BSTR
arquivo .
public struct BString
{
[MarshalAs(UnmanagedType.BStr)]
public string str;
}
struct BString
{
BSTR str;
};
Ao usar uma API baseada em WinRT, talvez seja necessário empacotar uma cadeia de caracteres como um HSTRING
arquivo . Usando o UnmanagedType.HString valor, você pode organizar uma cadeia de caracteres como um HSTRING
arquivo . HSTRING
o empacotamento só é suportado em tempos de execução com suporte WinRT integrado. O suporte ao WinRT foi removido no .NET 5, portanto HSTRING
, o empacotamento não é suportado no .NET 5 ou mais recente.
public struct HString
{
[MarshalAs(UnmanagedType.HString)]
public string str;
}
struct BString
{
HSTRING str;
};
Se sua API exigir que você passe a cadeia de caracteres in-loco na estrutura, você poderá usar o UnmanagedType.ByValTStr valor. Observe que a codificação de uma cadeia de caracteres marshalled by ByValTStr
é determinada a partir do CharSet
atributo. 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.
};
Personalizando a organização decimal de campos
Se você estiver trabalhando no Windows, poderá encontrar algumas APIs que usam o nativo CY
ou CURRENCY
a estrutura. Por padrão, o tipo .NET decimal
marshals para a estrutura nativa DECIMAL
. No entanto, você pode usar um MarshalAsAttribute com o UnmanagedType.Currency valor para instruir o marshaller a converter um decimal
valor em um valor nativo CY
.
public struct Currency
{
[MarshalAs(UnmanagedType.Currency)]
public decimal dec;
}
struct Currency
{
CY dec;
};
Sindicatos
Uma união é um tipo de dados 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
. É recomendável usar structs ao definir uma união no .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;
}
}
Marechal System.Object
No Windows, você pode empacotar object
campos digitados para código nativo. Você pode organizar esses campos em um de três tipos:
Por padrão, um object
campo -typed será empacotado para um IUnknown*
que encapsula o objeto.
public struct ObjectDefault
{
public object obj;
}
struct ObjectDefault
{
IUnknown* obj;
};
Se você quiser empacotar um campo de objeto para um IDispatch*
, adicione um MarshalAsAttribute com o UnmanagedType.IDispatch valor.
public struct ObjectDispatch
{
[MarshalAs(UnmanagedType.IDispatch)]
public object obj;
}
struct ObjectDispatch
{
IDispatch* obj;
};
Se você quiser marshal como um VARIANT
, adicione um MarshalAsAttribute com o UnmanagedType.Struct valor.
public struct ObjectVariant
{
[MarshalAs(UnmanagedType.Struct)]
public object obj;
}
struct ObjectVariant
{
VARIANT obj;
};
A tabela a seguir descreve como diferentes tipos de tempo de execução do obj
campo são mapeados para os vários tipos armazenados em um 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 |