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.CustomMarshaler
de 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"
).