Compartir a través de


Serializar tipos

El marshaling es el proceso de transformación de tipos cuando necesitan cruzar entre código administrado y nativo.

La marshalización es necesaria porque los tipos del código administrado y no administrado son diferentes. En código administrado, por ejemplo, tiene una string, mientras que las cadenas no administradas pueden utilizar codificación de .NET string (UTF-16), codificación de página de códigos ANSI, UTF-8, terminadas en null, ASCII, etc. De forma predeterminada, el subsistema P/Invoke intenta actuar correctamente según el 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.LPUTF8Str)] string parameter);

// or

[LibraryImport("somenativelibrary.dll", StringMarshalling = StringMarshalling.Utf8)]
static extern int MethodB(string parameter);

Si aplica el 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, vea marshalling en tiempo de ejecución desactivado.

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.

La primera tabla describe las asignaciones de los tipos para los que el ordenamiento es el mismo tanto para P/Invoke como para el ordenamiento de campos.

Importante

Al llamar a una función de C que usa long, use CLong o CULong (.NET 6+) en lugar de C# long. Para obtener más información y soluciones alternativas para versiones anteriores de .NET, consulte Consideraciones sobre el tipo de datos multiplataforma.

Nota:

El tipo de wchar_t es UTF-16 (2 bytes) en Windows pero está definido por el compilador en otras plataformas, normalmente UTF-32 (4 bytes) en Linux y macOS. Debido a esto, es difícil de usar como ABI multiplataforma única. Al diseñar una API nativa multiplataforma, prefiera con un contrato de codificación claramente definido (por ejemplo, UTF-8) en lugar de .

Nota:

Las cadenas nativas usan la codificación que define la biblioteca o la plataforma. Cuando se llama a una función de C que toma , coincide con la codificación esperada eligiendo la opción de serialización de cadenas correcta, como para UTF-8, para UTF-16 o para otras codificaciones.

Palabra clave de C# Tipo de .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 o 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 o 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 .NET (por ejemplo, void*) void*
Tipo derivado de void*
Tipo derivado de void*
bool System.Boolean Tipo Win32
decimal System.Decimal Estructura de COM
delegado de .NET Puntero de función nativa
System.DateTime Tipo Win32
System.Guid Tipo Win32

Algunas categorías de serialización tienen distintos valores predeterminados si está serializando un parámetro o una estructura.

Tipo de .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 atributo
Una clase con , o 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 marshalling predeterminadas exclusivas para Windows. En plataformas que no son de Windows, no se pueden serializar estos tipos.

Tipo de .NET Tipo nativo (parámetro) Tipo nativo (campo)
System.Object VARIANT IUnknown*
System.Array Interfaz COM No permitido sin un atributo
System.ArgIterator va_list No permitida
System.Collections.IEnumerator IEnumVARIANT* No permitida
System.Collections.IEnumerable IDispatch* No permitida
System.DateTimeOffset que representa el número de tics desde la medianoche del 1 de enero de 1601 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 de .NET Tipo nativo (solo parámetro)
System.Text.StringBuilder Ya sea o dependiendo de 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

Cuando se llama a métodos en objetos COM en .NET, el entorno de ejecución de .NET cambia las reglas de procesamiento de datos predeterminadas para que coincidan con la semántica COM común. En la tabla siguiente se enumeran las reglas que los entornos de ejecución de .NET usan en escenarios COM.

Tipo de .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[] de

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 atributo resulta útil.

using System;
using System.Runtime.InteropServices;

Win32Interop.GetSystemTime(out Win32Interop.SystemTime systemTime);

Console.WriteLine(systemTime.Year);

internal static partial class Win32Interop
{
    [LibraryImport("kernel32.dll")]
    internal static partial void GetSystemTime(out SystemTime systemTime);

    [StructLayout(LayoutKind.Sequential)]
    internal ref 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;
    }
}

El código anterior muestra un ejemplo sencillo de llamar a una función. La parte interesante está en la línea 13. 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, *LPSYSTEMTIME;

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.