Nota
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
La serialización es el proceso de transformación de tipos cuando tienen que cruzar el límite entre administrado y nativo.
La marshalización es necesaria porque los tipos del código administrado y no administrado son diferentes. En el código administrado, por ejemplo, tiene un string
, mientras que las cadenas no administradas pueden ser codificación de .NET string
(UTF-16), codificación de página de códigos ANSI, UTF-8, terminada en null, ASCII, etc. De forma predeterminada, el subsistema P/Invoke intenta hacer lo correcto en función del comportamiento predeterminado, descrito en este artículo. Sin embargo, para aquellas situaciones en las que necesita un control adicional, puede emplear el atributo MarshalAs para especificar cuál es el tipo esperado en el lado no administrado. Por ejemplo, si desea que la cadena se envíe como una cadena UTF-8 terminada en NULL, puede hacerlo de la siguiente manera:
[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);
Si aplica el System.Runtime.CompilerServices.DisableRuntimeMarshallingAttribute
atributo al ensamblado, no se aplican las reglas de la sección siguiente. Para obtener información sobre cómo se exponen los valores de .NET al código nativo cuando se aplica este atributo, consulte serialización en tiempo de ejecución deshabilitada.
Reglas predeterminadas para serializar tipos comunes
Por lo general, el entorno de ejecución intenta hacer "lo correcto" al serializar para requerir la menor cantidad de trabajo. En las tablas siguientes se describe cómo se procesa por defecto cada tipo cuando se usa en un parámetro o campo. Los tipos de caracteres y enteros de ancho fijo de C99/C++11 se usan para asegurarse de que la tabla siguiente es correcta para todas las plataformas. Puede usar cualquier tipo nativo que tenga los mismos requisitos de alineación y tamaño que estos tipos.
Esta primera tabla describe las asignaciones para distintos tipos para los que la serialización es el misma que en la serialización de campos y de P/Invoke.
Palabra clave de 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 |
Ya sea char o char16_t en función de la codificación de P/Invoke o de la estructura. Consulte la documentación del conjunto de caracteres. |
System.Char |
Ya sea char* o char16_t* en función de la codificación de P/Invoke o de la estructura. Consulte la documentación del conjunto de caracteres. |
|
nint |
System.IntPtr |
intptr_t |
nuint |
System.UIntPtr |
uintptr_t |
Tipos de puntero de .NET (por ejemplo, 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 |
Estructura DECIMAL de COM |
Delegado de .NET | Puntero de función nativa | |
System.DateTime |
Tipo Win32 DATE |
|
System.Guid |
Tipo Win32 GUID |
Algunas categorías de serialización tienen distintos valores predeterminados si está serializando un parámetro o una estructura.
Tipo .NET | Tipo nativo (parámetro) | Tipo nativo (campo) |
---|---|---|
Matriz de .NET | Un puntero al inicio de una matriz de representaciones nativas de los elementos de matriz. | No permitido sin un [MarshalAs] atributo |
Una clase con LayoutKind , Sequential o Explicit |
Un puntero a la representación nativa de la clase | Representación nativa de la clase |
En la tabla siguiente se incluyen las reglas de serialización predeterminadas que son solo de Windows. En las plataformas que no sean Windows, no puede serializar estos tipos.
Tipo .NET | Tipo nativo (parámetro) | Tipo nativo (campo) |
---|---|---|
System.Object |
VARIANT |
IUnknown* |
System.Array |
Interfaz COM | No permitido sin un [MarshalAs] atributo |
System.ArgIterator |
va_list |
No permitida |
System.Collections.IEnumerator |
IEnumVARIANT* |
No permitida |
System.Collections.IEnumerable |
IDispatch* |
No permitida |
System.DateTimeOffset |
int64_t que representa el número de tics desde la medianoche del 1 de enero de 1601 |
int64_t que representa el número de tics desde la medianoche del 1 de enero de 1601 |
Algunos tipos solo se pueden organizar como parámetros y no como campos. Estos tipos se enumeran en la tabla siguiente:
Tipo .NET | Tipo nativo (solo parámetro) |
---|---|
System.Text.StringBuilder |
Ya sea char* o char16_t* dependiendo de CharSet de P/Invoke. Consulte la documentación del conjunto de caracteres. |
System.ArgIterator |
va_list (solo en Windows x86/x64/arm64) |
System.Runtime.InteropServices.ArrayWithOffset |
void* |
System.Runtime.InteropServices.HandleRef |
void* |
Si estos valores predeterminados no hacen exactamente lo que desea, puede personalizar cómo se gestionan los parámetros. El artículo de serialización de parámetros le guía a través de cómo personalizar cómo se serializan los distintos tipos de parámetros.
Serialización predeterminada en escenarios COM
Al llamar a métodos en objetos COM en .NET, el tiempo de ejecución de .NET modifica las reglas de marshaling predeterminadas para que se ajusten a la semántica COM común. En la tabla siguiente se enumeran las reglas que usan los entornos de ejecución de .NET en escenarios COM:
Tipo .NET | Tipo nativo (llamadas al método COM) |
---|---|
System.Boolean |
VARIANT_BOOL |
StringBuilder |
LPWSTR |
System.String |
BSTR |
Tipos delegados |
_Delegate* en .NET Framework. No permitido en .NET Core y .NET 5+. |
System.Drawing.Color |
OLECOLOR |
Matriz de .NET | SAFEARRAY |
System.String[] |
SAFEARRAY de BSTR |
Serializar clases y estructuras
Otro aspecto de la serialización de tipos es cómo pasar una estructura a un método no administrado. Por ejemplo, algunos de los métodos no administrados requieren una estructura como parámetro. En estos casos, debe crear una estructura correspondiente o una clase en parte administrada del mundo para usarla como parámetro. Pero no basta con definir la clase. También es necesario indicarle al serializador cómo asignar campos de la clase a la estructura no administrada. Aquí el StructLayout
atributo resulta ú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);
}
El código anterior muestra un ejemplo sencillo de llamar a una GetSystemTime()
función. La parte interesante está en la línea 4. El atributo especifica que los campos de la clase se deben asignar secuencialmente a la estructura del otro lado (no administrado). Esto significa que la nomenclatura de los campos no es importante, solo su orden es importante, ya que debe corresponder a la estructura no administrada, que se muestra en el ejemplo siguiente:
typedef struct _SYSTEMTIME {
WORD wYear;
WORD wMonth;
WORD wDayOfWeek;
WORD wDay;
WORD wHour;
WORD wMinute;
WORD wSecond;
WORD wMilliseconds;
} SYSTEMTIME, *PSYSTEMTIME;
A veces, la serialización predeterminada para la estructura no hace lo que necesita. El artículo Personalización de la serialización de estructuras enseña a personalizar el modo de serializar la estructura.