Default Marshaling Behavior
Para ver el artículo en inglés, active la casilla Inglés. También puede ver el texto en inglés en una ventana emergente si pasa el puntero del mouse por el texto.
Traducción
Inglés

Default Marshaling Behavior

 

La serialización de interoperabilidad funciona con reglas que dictan cómo se comportan los datos asociados con parámetros de método cuando pasan entre memoria administrada y no administrada. Estas reglas integradas controlan las actividades de serialización como transformaciones de tipos de datos, si un destinatario puede cambiar los datos que recibe y devolver esos cambios al llamador, y en qué circunstancias el serializador proporciona optimizaciones de rendimiento.

En esta sección se identifican las características predeterminadas de comportamiento del servicio de serialización de interoperabilidad, y se muestra información detallada sobre la serialización de matrices, tipos booleanos, tipos de caracteres, delegados, clases, objetos, cadenas y estructuras.

System_CAPS_noteNota

No se admite la serialización de tipos genéricos. Para obtener más información, vea el artículo sobre cómo Interoperating Using Generic Types.

El serializador de interoperabiliad siempre intenta liberar memoria asignada por código no administrado. Este comportamiento cumple con las reglas de administración de memoria COM, pero difiere de las reglas que rigen C++ nativo.

Se puede producir confusión si se prevé un comportamiento de C++ nativo (sin liberación de memoria) al usar invocación de plataforma, que automáticamente libera memoria para los punteros. Por ejemplo, la llamada al siguiente método no administrado desde una DLL de C++ no libera automáticamente memoria.

BSTR MethodOne (BSTR b) {
     return b;
}

Sin embargo, si define el método como prototipo de invocación de una plataforma, reemplaza cada tipo BSTR por un tipo String, y llama a MethodOne, Common Language Runtime intenta liberar b dos veces. Puede cambiar el comportamiento del serialización mediante tipos IntPtr en lugar de tipos String.

El runtime usa siempre el método CoTaskMemFree para liberar memoria. Si la memoria con la que está trabajando no se asignó con el método CoTaskMemAlloc, debe usar un IntPtr y liberar la memoria manualmente mediante el método adecuado. De forma similar, puede evitar la liberación automática de la memoria en situaciones donde nunca se debería liberar memoria, como al usar la función GetCommandLine de Kernel32.dll, que devuelve un puntero a la memoria kernel. Para obtener más información sobre cómo liberar memoria manualmente, vea el ejemplo sobre búferes.

Las clases solo se pueden serializar con la interoperabilidad COM y siempre se serializan como interfaces. En algunos casos, la interfaz usada para serializar la clase se conoce como la interfaz de clase. Para obtener información sobre cómo reemplazar la interfaz de clase por una interfaz de su elección, vea el artículo de introducción a la interfaz de clase.

Cuando una clase administrada se pasa a COM, el serializador de interoperabilidad automáticamente encapsula la clase con un proxy COM y pasa la interfaz de clase generada por el proxy a la llamada de método COM. El proxy delega entonces todas las llamadas en la interfaz de clase al objeto administrado. El proxy también expone otras interfaces que no están implementadas explícitamente por la clase. El proxy implementa automáticamente interfaces como IUnknown e IDispatch en nombre de la clase.

Las coclases no suelen usarse como argumentos de método en COM. En lugar de la coclase, normalmente se pasa una interfaz predeterminada.

Cuando una interfaz se pasa a código administrado, el serializador de interoperabilidad es responsable de encapsular la interfaz en el contenedor adecuado y pasar este contenedor al método administrado. Determinar qué contenedor se usará puede resultar difícil. Cada instancia de un objeto COM tiene un solo contenedor, independientemente de cuántas interfaces implemente el objeto. Por ejemplo, un único objeto COM que implementa cinco interfaces distintas tiene un solo contenedor. El mismo contenedor expone las cinco interfaces. Si se crean dos instancias del objeto COM, también se crean dos instancias del contenedor.

Para que el contenedor mantenga el mismo tipo a lo largo del tiempo, el serializador de interoperabilidad debe identificar el contenedor correcto la primera vez que una interfaz expuesta por el objeto se pasa a través del serializador. El serializador identifica el objeto examinando una de las interfaces que implementa el objeto.

Por ejemplo, el serializador determina que el contenedor de clase se debe usar para encapsular la interfaz que se pasó a código administrado. Cuando la interfaz pasa primero por el serializador, este comprueba si la interfaz procede de un objeto conocido. Esta comprobación se produce en dos situaciones:

  • Se está implementando una interfaz mediante otro objeto administrado que se pasó a COM en otro lugar. El serializador puede identificar inmediatamente las interfaces expuestas por los objetos administrados y es capaz de hacer coincidir la interfaz con el objeto administrado que proporciona la implementación. El objeto administrado se pasa a continuación al método y no se necesita ningún contenedor.

  • Un objeto que ya se ha encapsulado se está implementando en la interfaz. Para determinar si este es el caso, el serializador consulta al objeto su interfaz IUnknown y compara la interfaz devuelta con las interfaces de otros objetos que ya están encapsulados. Si la interfaz es la misma que la de otro contenedor, los objetos tienen la misma identidad y el contenedor existente se pasa al método.

Si no es una interfaz de un objeto conocido, el serializador realiza lo siguiente:

  1. El serializador consulta al objeto la interfaz IProvideClassInfo2. Si se proporciona, el serializador usa el CLSID devuelto de IProvideClassInfo2.GetGUID para identificar la coclase que proporciona la interfaz. Con el CLSID, el serializador puede ubicar el contenedor en el Registro si previamente se ha registrado el ensamblado.

  2. El serializador consulta a la interfaz la interfaz IProvideClassInfo. Si se proporciona, el serializador usa el ITypeInfo devuelto desde Iprovideclassinfo2.GetClassinfo para determinar el CLSID de la clase que expone la interfaz. El serializador puede usar el CLSID para buscar los metadatos del contenedor.

  3. Si el serializador sigue sin poder identificar la clase, encapsula la interfaz en una clase de contenedor genérico denominado System.__ComObject.

Un delegado administrado se serializa como una interfaz COM o como un puntero de función según el mecanismo de llamada:

  • Para invocación de plataforma, se serializa un delegado como un puntero de función no administrada de forma predeterminada.

  • Para interoperabilidad COM, se serializa un delegado como una interfaz COM de tipo _Delegate de forma predeterminada. La interfaz _Delegate se define en la biblioteca de tipos Mscorlib.tlb y contiene el método Delegate.DynamicInvoke, lo que permite llamar al método al que hace referencia el delegado.

En la siguiente tabla se muestran las opciones de serialización para el tipo de datos de delegado administrado. El atributo MarshalAsAttribute proporciona varios valores de enumeración UnmanagedType para serializar los delegados.

Tipo de enumeración

Descripción de formato no administrado

UnmanagedType.FunctionPtr

Es un puntero de función no administrada.

UnmanagedType.Interface

Es una interfaz de tipo _Delegate, tal como se define en Mscorlib.tlb.

Considere el siguiente código de ejemplo en el que los métodos de DelegateTestInterface se exportan a una biblioteca de tipos COM. Observe que solo los delegados marcados con la palabra clave ref (o ByRef) se pasan como parámetros In/Out.

using System;
using System.Runtime.InteropServices;

public interface DelegateTest {
void m1(Delegate d);
void m2([MarshalAs(UnmanagedType.Interface)] Delegate d);   
void m3([MarshalAs(UnmanagedType.Interface)] ref Delegate d);  
void m4([MarshalAs(UnmanagedType.FunctionPtr)] Delegate d); 
void m5([MarshalAs(UnmanagedType.FunctionPtr)] ref Delegate d);   
}

importlib("mscorlib.tlb");
interface DelegateTest : IDispatch {
[id(…)] HRESULT m1([in] _Delegate* d);
[id(…)] HRESULT m2([in] _Delegate* d);
[id(…)] HRESULT m3([in, out] _Delegate** d);
[id()] HRESULT m4([in] int d);
[id()] HRESULT m5([in, out] int *d);
   };

Se puede desreferenciar un puntero de función, igual que con cualquier otro puntero de función no administrada.

System_CAPS_noteNota

Una referencia al puntero de función para un delegado administrado mantenido por código no administrado no impide que Common Language Runtime realice la recolección de elementos no utilizados en el objeto administrado.

Por ejemplo, el código siguiente es incorrecto porque la referencia al objeto cb, que se pasa al método SetChangeHandler, no mantiene a cb activo más allá de la vida del método Test. Una vez recopilados los elementos no utilizados del objeto cb, el puntero de función pasado a SetChangeHandler deja de ser válido.

public class ExternalAPI {
   [DllImport("External.dll")]
   public static extern void SetChangeHandler(
      [MarshalAs(UnmanagedType.FunctionPtr)]ChangeDelegate d);
}
public delegate bool ChangeDelegate([MarshalAs(UnmanagedType.LPWStr) string S);
public class CallBackClass {
   public bool OnChange(string S){ return true;}
}
internal class DelegateTest {
   public static void Test() {
      CallBackClass cb = new CallBackClass();
      // Caution: The following reference on the cb object does not keep the 
      // object from being garbage collected after the Main method 
      // executes.
      ExternalAPI.SetChangeHandler(new ChangeDelegate(cb.OnChange));   
   }
}

Para compensar la recolección inesperada de elementos no utilizados, el llamador debe asegurarse de que el objeto cb se mantiene activo mientras el puntero de función no administrada está en uso. Opcionalmente, puede hacer que el código no administrado le notifique al código administrado si el puntero de función ya no es necesario, como se muestra en el ejemplo siguiente.

internal class DelegateTest {
   CallBackClass cb;
   // Called before ever using the callback function.
   public static void SetChangeHandler() {
      cb = new CallBackClass();
      ExternalAPI.SetChangeHandler(new ChangeDelegate(cb.OnChange));
   }
   // Called after using the callback function for the last time.
   public static void RemoveChangeHandler() {
      // The cb object can be collected now. The unmanaged code is 
      // finished with the callback function.
      cb = null;
   }
}

La mayoría de los tipos de valor, como enteros y números de punto flotante, pueden transferirse en bloque de bits y no requieren serialización. Otros tipos no pueden transferirse en bloque de bits, tienen representaciones distintas en memoria administrada y no administrada y requieren serialización. Hay también otros tipos que requieren un formato explícito en el límite de interoperación.

En este tema se proporciona la siguiente información sobre tipos de valor con formato:

Además de describir tipos con formato, en este tema se identifican tipos de valor System que tienen un comportamiento de serialización poco habitual.

Un tipo con formato es un tipo complejo que contiene información que controla explícitamente la distribución de sus miembros en la memoria. La información de distribución de miembros se proporciona mediante el atributo StructLayoutAttribute. La distribución puede ser uno de los siguientes valores de enumeración LayoutKind:

  • LayoutKind.Automatic

    Indica que Common Language Runtime puede volver a ordenar los miembros del tipo para lograr una mayor eficacia. Sin embargo, cuando un tipo de valor se pasa a código no administrado, la distribución de los miembros es predecible. Si se intenta serializar automáticamente una estructura de este tipo, se produce una excepción.

  • LayoutKind.Sequential

    Indica que los miembros del tipo se distribuyen en la memoria no administrada en el mismo orden en que aparecen en la definición de tipo administrado.

  • LayoutKind.Explicit

    Indica que los miembros se distribuyen según el FieldOffsetAttribute proporcionado con cada campo.

En el ejemplo siguiente, los tipos Point y Rect proporcionan información de distribución de miembros mediante StructLayoutAttribute.

using System.Runtime.InteropServices;
[StructLayout(LayoutKind.Sequential)]
public struct Point {
   public int x;
   public int y;
}   

[StructLayout(LayoutKind.Explicit)]
public struct Rect {
   [FieldOffset(0)] public int left;
   [FieldOffset(4)] public int top;
   [FieldOffset(8)] public int right;
   [FieldOffset(12)] public int bottom;
}

Al serializar a código no administrado, estos tipos con formato se serializan como estructuras de estilo C. Esto proporciona una manera sencilla de llamar a una API no administrada que tiene argumentos de estructura. Por ejemplo, las estructuras POINT y RECT se pueden pasar a la función PtInRect de la API Win32 de Microsoft del modo siguiente:

BOOL PtInRect(const RECT *lprc, POINT pt);

Las estructuras se pueden pasar mediante la siguiente definición de invocación de plataforma:

class Win32API {
   [DllImport("User32.dll")]
   public static extern Bool PtInRect(ref Rect r, Point p);
}

El tipo de valor Rect se debe pasar por referencia porque la API no administrada espera que un puntero a un RECT se pase a la función. El tipo de valor Point se pasa por valor porque la API no administrada espera que POINT se pase en la pila. Esta diferencia sutil es muy importante. Las referencias se pasan a código no administrado como punteros. Los valores se pasan a código no administrado en la pila.

System_CAPS_noteNota

Cuando un tipo con formato se serializa como una estructura, solo son accesibles los campos dentro del tipo. Si el tipo tiene métodos, propiedades o eventos, son inaccesibles desde código no administrado.

Las clases también se pueden serializar a código no administrado como estructuras de estilo C, a condición de que tengan una distribución de miembros fija. La información de distribución de miembros para una clase también se proporciona con el atributo StructLayoutAttribute. La diferencia principal entre los tipos de valor con distribución fija y las clases con distribución fija es la forma en la que se serializan a código no administrado. Los tipos de valor se pasan por valor (en la pila) y, por consiguiente, el llamador no ve los cambios realizados por el destinatario en los miembros del tipo. Los tipos de referencia se pasan por referencia (se pasa una referencia al tipo en la pila); en consecuencia, el llamador ve todos los cambios realizados por el destinatario en miembros de un tipo que pueden transferirse en bloque de bits.

System_CAPS_noteNota

Si un tipo de referencia tiene miembros de tipos que no pueden transferirse en bloque de bits, se requiere una conversión doble: la primera vez cuando se pasa un argumento al lado no administrado y la segunda vez en la devolución de la llamada. Debido a esta sobrecarga adicional, los parámetros In/Out deben aplicarse explícitamente a un argumento si el llamador desea ver los cambios realizados por el destinatario.

En el ejemplo siguiente, la clase SystemTime tiene distribución de miembros secuencial y puede pasarse a la función GetSystemTime de la API Win32.

[StructLayout(LayoutKind.Sequential)]
   public class SystemTime {
   public ushort wYear; 
   public ushort wMonth;
   public ushort wDayOfWeek; 
   public ushort wDay; 
   public ushort wHour; 
   public ushort wMinute; 
   public ushort wSecond; 
   public ushort wMilliseconds; 
}

La función GetSystemTime se define como sigue:

void GetSystemTime(SYSTEMTIME* SystemTime);

La definición de invocación de plataforma equivalente para GetSystemTime es la siguiente:

class Win32API {
   [DllImport("Kernel32.dll", CharSet=CharSet.Auto)]
   public static extern void GetSystemTime(SystemTime st);
}

Tenga en cuenta que el argumento SystemTime no se tipifica como argumento de referencia porque SystemTime es una clase, no un tipo de valor. A diferencia de los tipos de valor, las clases siempre se pasan por referencia.

En el ejemplo de código siguiente se muestra otra clase Point que tiene un método denominado SetXY. Puesto que el tipo tiene una distribución secuencial, puede pasarse a código no administrado y serializarse como una estructura. Sin embargo, el miembro SetXY no es invocable desde código no administrado, aunque el objeto se pase por referencia.

[StructLayout(LayoutKind.Sequential)]
public class Point {
   int x, y;
   public void SetXY(int x, int y){ 
      this.x = x;
      this.y = y;
   }
}

Los tipos con formato también pueden pasarse a llamadas de métodos de interoperabilidad COM. De hecho, cuando se exportan a una biblioteca de tipos, los tipos de valor se convierten automáticamente en estructuras. Como se muestra en el ejemplo siguiente, el tipo de valor Point se convierte en una definición de tipo (typedef) con el nombre Point. Todas las referencias al tipo de valor Point en otro lugar de la biblioteca de tipos se reemplazan por el typedef Point.

Representación de biblioteca de tipos

typedef struct tagPoint {
   int x;
   int y;
} Point;
interface _Graphics {
   …
   HRESULT SetPoint ([in] Point p)
   HRESULT SetPointRef ([in,out] Point *p)
   HRESULT GetPoint ([out,retval] Point *p)
}

Las mismas reglas usadas para serializar valores y referencias para llamadas de invocación de plataforma se usan al serializar a través de interfaces COM. Por ejemplo, cuando una instancia del tipo de valor Point se pasa de .NET Framework a COM, Point se pasa por valor. Si el tipo de valor Point se pasa por referencia, un puntero a un Point se pasa en la pila. El serializador de interoperabilidad no admite niveles superiores de direccionamiento indirecto (Point **) en cualquier dirección.

System_CAPS_noteNota

Las estructuras que tienen el valor de enumeración LayoutKind establecido en Explicit no se pueden usar en la interoperabilidad COM porque la biblioteca de tipos exportada no puede expresar una distribución explícita.

El espacio de nombres System tiene varios tipos de valor que representan la forma de conversión boxing de los tipos primitivos en runtime. Por ejemplo, la estructura System.Int32 de tipo de valor representa la forma de conversión boxing de ELEMENT_TYPE_I4. En lugar de serializar estos tipos como estructuras, como otros tipos con formato, se serializan de la misma forma que los tipos primitivos a los que aplican conversión boxing. Por lo tanto, System.Int32 se serializa como ELEMENT_TYPE_I4 en lugar de como una estructura que contiene un único miembro de tipo long. La tabla siguiente contiene una lista de los tipos de valor en el espacio de nombres System que son representaciones de conversión boxing de tipos primitivos.

Tipo de valor System

Tipo de elemento

System.Boolean

ELEMENT_TYPE_BOOLEAN

System.SByte

ELEMENT_TYPE_I1

System.Byte

ELEMENT_TYPE_UI1

System.Char

ELEMENT_TYPE_CHAR

System.Int16

ELEMENT_TYPE_I2

System.UInt16

ELEMENT_TYPE_U2

System.Int32

ELEMENT_TYPE_I4

System.UInt32

ELEMENT_TYPE_U4

System.Int64

ELEMENT_TYPE_I8

System.UInt64

ELEMENT_TYPE_U8

System.Single

ELEMENT_TYPE_R4

System.Double

ELEMENT_TYPE_R8

System.String

ELEMENT_TYPE_STRING

System.IntPtr

ELEMENT_TYPE_I

System.UIntPtr

ELEMENT_TYPE_U

Hay algunos tipos de valores del espacio de nombres System que se tratan de forma diferente. Dado que el código no administrado ya tiene formatos establecidos para estos tipos, el serializador tiene reglas especiales para serializarlos. En la tabla siguiente se enumeran los tipos de valor especiales del espacio de nombres System, así como el tipo no administrado al que se serializan.

Tipo de valor System

Tipo IDL

System.DateTime

date

System.Decimal

DECIMAL

System.Guid

GUID

System.Drawing.Color

OLE_COLOR

El código siguiente muestra la definición de los tipos no administrados DATE, GUID, DECIMAL y OLE_COLOR en la biblioteca de tipos Stdole2.

typedef double DATE;
typedef DWORD OLE_COLOR;

typedef struct tagDEC {
    USHORT    wReserved;
    BYTE      scale;
    BYTE      sign;
    ULONG     Hi32;
    ULONGLONG Lo64;
} DECIMAL;

typedef struct tagGUID {
    DWORD Data1;
    WORD  Data2;
    WORD  Data3;
    BYTE  Data4[ 8 ];
} GUID;

El código siguiente muestra las definiciones correspondientes en la interfaz IValueTypes administrada.

public interface IValueTypes {
   void M1(System.DateTime d);
   void M2(System.Guid d);
   void M3(System.Decimal d);
   void M4(System.Drawing.Color d);
}

[…]
interface IValueTypes : IDispatch {
   HRESULT M1([in] DATE d);
   HRESULT M2([in] GUID d);
   HRESULT M3([in] DECIMAL d);
   HRESULT M4([in] OLE_COLOR d);
};
Mostrar:
© 2016 Microsoft