Compartir a través de


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 envolturas personalizadas para manejar 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 wrapper de llamada COM estándar (CCW), tiene los mismos métodos que la interfaz administrada y utiliza las reglas de serialización integradas en el marshaller 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 gestionadas y no gestionadas.

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.

Define el 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

Su serializador personalizado debe implementar la interfaz ICustomMarshaler para proporcionar los contenedores adecuados al tiempo 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, soporte 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 Gestiona un puntero a datos nativos y lo convierte en un objeto administrado. Este método devuelve un wrapper de llamada en tiempo de ejecución (RCW) personalizado que puede serializar la interfaz no administrada que se pasa como argumento. El clasificador debe devolver una instancia del RCW personalizado de dicho tipo.
Conversión (de administrado a código nativo) MarshalManagedToNative Gestiona un puntero a datos nativos y lo convierte en un objeto administrado. 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 clasificador debe devolver una instancia del CCW personalizado de dicho tipo.
Limpieza (de código nativo) CleanUpNativeData Permite al serializador limpiar los datos nativos (CCW) que son devueltos por el método MarshalManagedToNative.
Limpieza (de código administrado) CleanUpManagedData Permite al serializador limpiar los datos gestionados (RCW) que son devueltos por el método MarshalNativeToManaged.
Información (acerca del código nativo) GetNativeDataSize Devuelve el tamaño de los datos no administrados de los que se van a calcular las referencias.

Conversión

ICustomMarshaler.MarshalNativeToManaged

Gestiona un puntero a datos nativos y lo convierte en un objeto administrado. Este método devuelve un wrapper de llamada en tiempo de ejecución (RCW) personalizado que puede serializar la interfaz no administrada que se pasa como argumento. El clasificador debe devolver una instancia del RCW personalizado de dicho tipo.

ICustomMarshaler.MarshalManagedToNative

Gestiona un puntero a datos nativos y lo convierte en un objeto administrado. 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 clasificador debe devolver una instancia del CCW personalizado de dicho tipo.

Limpieza

ICustomMarshaler.CleanUpNativeData

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

ICustomMarshaler.CleanUpManagedData

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

Información de tamaño

ICustomMarshaler.GetNativeDataSize

Devuelve el tamaño de los datos no administrados de los que se van a calcular las referencias.

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 pasen por alto errores al usar ensambladores personalizados con P/Invokes con DllImportAttribute.SetLastError configurado en true. Para conservar el último error de P/Invoke, use los métodos Marshal.GetLastPInvokeError() y Marshal.SetLastPInvokeError(Int32) en la implementación ICustomMarshaler.

Implementación del método GetInstance

Además de implementar la interfaz ICustomMarshaler, los serializadores personalizados deben implementar un método static llamado GetInstance que acepta un String como parámetro y tiene un tipo de valor devuelto de ICustomMarshaler. La capa de interoperabilidad COM de Common Language Runtime (CLR) llama a este método static para instanciar un 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 el atributo MarshalAsAttribute

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

También debe pasar el valor de enumeración UnmanagedType.CustomMarshaler al constructor MarshalAsAttribute. 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 utiliza, debe especificar el nombre del ensamblado en el que está definido.

    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): Una 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 atributo MarshalAsAttribute identifica al serializador personalizado para que pueda activar el contenedor apropiado. 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 métodos MarshalNativeToManaged y MarshalManagedToNative en el serializador personalizado para activar el contenedor correcto y encargarse de la llamada.

Usa un serializador personalizado

Una vez completado el serializador personalizado, puede utilizarlo 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 serializador NewOldMarshaler personalizado para que las aplicaciones cliente no administradas puedan pasar una IOld interfaz al DoSomeStuff método. La descripción administrada del método DoSomeStuff toma una interfazINew, como se muestra en el ejemplo anterior, mientras que la versión no administrada de DoSomeStuff toma un puntero de interfaz IOld, 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 atributo MarshalAsAttribute aplicado al argumento INew de la definición administrada del método DoSomeStuff 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 atributo MarshalAsAttribute es el valor de enumeración UnmanagedType.CustomMarshalerUnmanagedType.CustomMarshaler.

El segundo parámetro es el campo MarshalType, 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").