Interfaz System.Runtime.InteropServices.ICustomMarshaler

En este artículo se proporcionan comentarios adicionales a la documentación de referencia de esta API.

La ICustomMarshaler interfaz proporciona contenedores personalizados para controlar las llamadas a métodos.

Un serializador proporciona un puente entre la funcionalidad de las interfaces antiguas y nuevas. La serialización personalizada proporciona las siguientes ventajas:

  • Permite que las aplicaciones cliente diseñadas para trabajar con una interfaz antigua también funcionen con servidores que implementan una nueva interfaz.
  • Permite que las aplicaciones cliente creadas funcionen con una nueva interfaz para trabajar con servidores que implementan una interfaz antigua.

Si tiene una interfaz que presenta un comportamiento de serialización diferente o que se expone al modelo de objetos componentes (COM) de otra manera, puede diseñar un serializador personalizado en lugar de usar el serializador de interoperabilidad. Mediante el uso de un serializador personalizado, puede minimizar la distinción entre los nuevos componentes de .NET Framework y los componentes COM existentes.

Por ejemplo, supongamos que está desarrollando una interfaz administrada denominada INew. Cuando esta interfaz se expone a COM a través de un contenedor com estándar al que se puede llamar (CCW), tiene los mismos métodos que la interfaz administrada y usa las reglas de serialización integradas en el serializador de interoperabilidad. Ahora supongamos que una interfaz COM conocida denominada IOld ya proporciona la misma funcionalidad que la INew interfaz. Al diseñar un serializador personalizado, puede proporcionar una implementación no administrada de IOld que simplemente delegue las llamadas a la implementación administrada de la INew interfaz. Por lo tanto, el serializador personalizado actúa como un puente entre las interfaces administradas y no administradas.

Nota:

Los serializadores personalizados no se invocan al llamar desde código administrado a código no administrado en una interfaz de solo envío.

Definición del tipo de serialización

Para poder crear un serializador personalizado, debe definir las interfaces administradas y no administradas que se serializarán. Estas interfaces suelen realizar la misma función, pero se exponen de forma diferente a objetos administrados y no administrados.

Un compilador administrado genera una interfaz administrada a partir de metadatos y la interfaz resultante es similar a cualquier otra interfaz administrada. En el ejemplo siguiente se muestra una interfaz típica.

public interface INew
{
    void NewMethod();
}
Public Interface INew
    Sub NewMethod()
End Interface

El tipo no administrado se define en Interface Definition Language (IDL) y se compila con el compilador Microsoft Interface Definition Language (MIDL). Defina la interfaz dentro de una instrucción de biblioteca y asígnele un identificador de interfaz con el atributo de identificador único universal (UUID), como se muestra en el ejemplo siguiente.

 [uuid(9B2BAADA-0705-11D3-A0CD-00C04FA35826)]
library OldLib {
     [uuid(9B2BAADD-0705-11D3-A0CD-00C04FA35826)]
     interface IOld : IUnknown
         HRESULT OldMethod();
}

El compilador MIDL genera varios archivos de salida. Si la interfaz se define en Old.idl, el archivo de salida Old_i.c define una const variable con el identificador de interfaz (IID) de la interfaz, como se muestra en el ejemplo siguiente.

const IID IID_IOld = {0x9B2BAADD,0x0705,0x11D3,{0xA0,0xCD,0x00,0xC0,0x4F,0xA3,0x58,0x26}};

El archivo Old.h también lo genera MIDL. Contiene una definición de C++ de la interfaz que se puede incluir en el código fuente de C++.

Implementación de la interfaz ICustomMarshaler

El serializador personalizado debe implementar la ICustomMarshaler interfaz para proporcionar los contenedores adecuados al entorno de ejecución.

El siguiente código de C# muestra la interfaz base que deben implementar todos los serializadores personalizados.

public interface ICustomMarshaler
{
    Object MarshalNativeToManaged(IntPtr pNativeData);
    IntPtr MarshalManagedToNative(Object ManagedObj);
    void CleanUpNativeData(IntPtr pNativeData);
    void CleanUpManagedData(Object ManagedObj);
    int GetNativeDataSize();
}
Public Interface ICustomMarshaler
     Function MarshalNativeToManaged( pNativeData As IntPtr ) As Object
     Function MarshalManagedToNative( ManagedObj As Object ) As IntPtr
     Sub CleanUpNativeData( pNativeData As IntPtr )
     Sub CleanUpManagedData( ManagedObj As Object )
     Function GetNativeDataSize() As Integer
End Interface

La ICustomMarshaler interfaz incluye métodos que proporcionan compatibilidad con la conversión, compatibilidad de limpieza e información sobre los datos que se van a serializar.

Tipo de operación Método ICustomMarshaler Descripción
Conversión (de código nativo a administrado) MarshalNativeToManaged Serializa un puntero a los datos nativos en un objeto administrado. Este método devuelve un contenedor al que se puede llamar en tiempo de ejecución personalizado (RCW) que puede serializar la interfaz no administrada que se pasa como argumento. El serializador debe devolver una instancia del RCW personalizado para ese tipo.
Conversión (de administrado a código nativo) MarshalManagedToNative Serializa un objeto administrado en un puntero a datos nativos. Este método devuelve un contenedor com personalizado al que se puede llamar (CCW) que puede serializar la interfaz administrada que se pasa como argumento. El serializador debe devolver una instancia del CCW personalizado para ese tipo.
Limpieza (de código nativo) CleanUpNativeData Permite al serializador limpiar los datos nativos (ccW) devueltos por el MarshalManagedToNative método .
Limpieza (de código administrado) CleanUpManagedData Permite al serializador limpiar los datos administrados (RCW) devueltos por el MarshalNativeToManaged método .
Información (acerca del código nativo) GetNativeDataSize Devuelve el tamaño de los datos no administrados que se van a serializar.

Conversión

ICustomMarshaler.MarshalNativeToManaged

Serializa un puntero a los datos nativos en un objeto administrado. Este método devuelve un contenedor al que se puede llamar en tiempo de ejecución personalizado (RCW) que puede serializar la interfaz no administrada que se pasa como argumento. El serializador debe devolver una instancia del RCW personalizado para ese tipo.

ICustomMarshaler.MarshalManagedToNative

Serializa un objeto administrado en un puntero a datos nativos. Este método devuelve un contenedor com personalizado al que se puede llamar (CCW) que puede serializar la interfaz administrada que se pasa como argumento. El serializador debe devolver una instancia del CCW personalizado para ese tipo.

Limpieza

ICustomMarshaler.CleanUpNativeData

Permite al serializador limpiar los datos nativos (ccW) devueltos por el MarshalManagedToNative método .

ICustomMarshaler.CleanUpManagedData

Permite al serializador limpiar los datos administrados (RCW) devueltos por el MarshalNativeToManaged método .

Información de tamaño

ICustomMarshaler.GetNativeDataSize

Devuelve el tamaño de los datos no administrados que se van a serializar.

Nota:

Si un serializador personalizado llama a cualquier método que establezca el último error P/Invoke al serializar de nativo a administrado o al limpiar, el valor devuelto por Marshal.GetLastWin32Error() y Marshal.GetLastPInvokeError() representará la llamada en las llamadas de serialización o limpieza. Esto puede hacer que se pierdan errores al usar serializadores personalizados con P/Invokes con establecido en DllImportAttribute.SetLastErrortrue. Para conservar el último error de P/Invoke, use los Marshal.GetLastPInvokeError() métodos y Marshal.SetLastPInvokeError(Int32) en la ICustomMarshaler implementación.

Implementación del método GetInstance

Además de implementar la ICustomMarshaler interfaz, los serializadores personalizados deben implementar un static método denominado GetInstance que acepta como String parámetro y tiene un tipo de valor devuelto de ICustomMarshaler. La capa de interoperabilidad COM de Common Language Runtime llama a este static método para crear instancias de una instancia del serializador personalizado. La cadena que se pasa a GetInstance es una cookie que el método puede usar para personalizar el serializador personalizado devuelto. En el ejemplo siguiente se muestra una implementación mínima, pero completa ICustomMarshaler .

public class NewOldMarshaler : ICustomMarshaler
{
    public static ICustomMarshaler GetInstance(string pstrCookie)
        => new NewOldMarshaler();

    public Object MarshalNativeToManaged(IntPtr pNativeData) => throw new NotImplementedException();
    public IntPtr MarshalManagedToNative(Object ManagedObj) => throw new NotImplementedException();
    public void CleanUpNativeData(IntPtr pNativeData) => throw new NotImplementedException();
    public void CleanUpManagedData(Object ManagedObj) => throw new NotImplementedException();
    public int GetNativeDataSize() => throw new NotImplementedException();
}

Aplicar MarshalAsAttribute

Para usar un serializador personalizado, debe aplicar el MarshalAsAttribute atributo al parámetro o campo que se serializa.

También debe pasar el UnmanagedType.CustomMarshaler valor de enumeración al MarshalAsAttribute constructor. Además, debe especificar el MarshalType campo con uno de los parámetros con nombre siguientes:

  • MarshalType (obligatorio): nombre completo del ensamblado del serializador personalizado. El nombre debe incluir el espacio de nombres y la clase del serializador personalizado. Si el serializador personalizado no está definido en el ensamblado en el que se usa, debe especificar el nombre del ensamblado en el que se define.

    Nota:

    Puede usar el MarshalTypeRef campo en lugar del MarshalType campo . MarshalTypeRef toma un tipo que es más fácil de especificar.

  • MarshalCookie (opcional): cookie que se pasa al serializador personalizado. Puede usar la cookie para proporcionar información adicional al serializador. Por ejemplo, si se usa el mismo serializador para proporcionar una serie de contenedores, la cookie identifica un contenedor específico. La cookie se pasa al GetInstance método del serializador.

El MarshalAsAttribute atributo identifica el serializador personalizado para que pueda activar el contenedor adecuado. A continuación, el servicio de interoperabilidad de Common Language Runtime examina el atributo y crea el serializador personalizado la primera vez que se debe serializar el argumento (parámetro o campo).

A continuación, el tiempo de ejecución llama a los MarshalNativeToManaged métodos y MarshalManagedToNative en el serializador personalizado para activar el contenedor correcto para controlar la llamada.

Uso de un serializador personalizado

Una vez completado el serializador personalizado, puede usarlo como contenedor personalizado para un tipo determinado. En el ejemplo siguiente se muestra la definición de la IUserData interfaz administrada:

interface IUserData
{
    void DoSomeStuff(INew pINew);
}
Public Interface IUserData
    Sub DoSomeStuff(pINew As INew)
End Interface

En el ejemplo siguiente, la IUserData interfaz usa el NewOldMarshaler serializador personalizado para permitir que las aplicaciones cliente no administradas pasen una IOld interfaz al DoSomeStuff método . La descripción administrada del DoSomeStuff método toma una INew interfaz, como se muestra en el ejemplo anterior, mientras que la versión no administrada de toma un IOld puntero de DoSomeStuff interfaz, como se muestra en el ejemplo siguiente.

[uuid(9B2BAADA-0705-11D3-A0CD-00C04FA35826)]
library UserLib {
     [uuid(9B2BABCD-0705-11D3-A0CD-00C04FA35826)]
     interface IUserData : IUnknown
         HRESULT DoSomeStuff(IUnknown* pIOld);
}

La biblioteca de tipos que se genera exportando la definición administrada de IUserData produce la definición no administrada que se muestra en este ejemplo en lugar de la definición estándar. El MarshalAsAttribute atributo aplicado al INew argumento de la definición administrada del DoSomeStuff método indica que el argumento usa un serializador personalizado, como se muestra en el ejemplo siguiente.

using System.Runtime.InteropServices;
Imports System.Runtime.InteropServices
interface IUserData
{
    void DoSomeStuff(
        [MarshalAs(UnmanagedType.CustomMarshaler,
         MarshalType="NewOldMarshaler")]
    INew pINew
    );
}
Public Interface IUserData
    Sub DoSomeStuff( _
        <MarshalAs(UnmanagedType.CustomMarshaler, _
        MarshalType := "MyCompany.NewOldMarshaler")> pINew As INew)
End Interface

En los ejemplos anteriores, el primer parámetro proporcionado al MarshalAsAttribute atributo es el UnmanagedType.CustomMarshaler valor UnmanagedType.CustomMarshalerde enumeración .

El segundo parámetro es el MarshalType campo , que proporciona el nombre completo del ensamblado del serializador personalizado. Este nombre consta del espacio de nombres y la clase del serializador personalizado (MarshalType="MyCompany.NewOldMarshaler").