Interfaccia System.Runtime.InteropServices.ICustomMarshaler

Questo articolo fornisce osservazioni supplementari alla documentazione di riferimento per questa API.

L'interfaccia ICustomMarshaler fornisce wrapper personalizzati per la gestione delle chiamate ai metodi.

Un marshaller fornisce un ponte tra le funzionalità delle interfacce precedenti e nuove. Il marshalling personalizzato offre i vantaggi seguenti:

  • Consente alle applicazioni client progettate per funzionare con un'interfaccia precedente per lavorare anche con i server che implementano una nuova interfaccia.
  • Consente alle applicazioni client compilate di usare una nuova interfaccia per lavorare con i server che implementano un'interfaccia precedente.

Se si dispone di un'interfaccia che introduce un comportamento di marshalling diverso o esposto a Component Object Model (COM) in modo diverso, è possibile progettare un marshaller personalizzato anziché usare il marshaller di interoperabilità. Usando un marshaller personalizzato, è possibile ridurre al minimo la distinzione tra i nuovi componenti di .NET Framework e i componenti COM esistenti.

Si supponga, ad esempio, di sviluppare un'interfaccia gestita denominata INew. Quando questa interfaccia viene esposta a COM tramite un wrapper com chiamabile standard (CCW), ha gli stessi metodi dell'interfaccia gestita e usa le regole di marshalling incorporate nel marshaller di interoperabilità. Si supponga ora che un'interfaccia COM nota denominata IOld fornisca già la stessa funzionalità dell'interfaccia INew . Progettando un marshaller personalizzato, è possibile fornire un'implementazione non gestita di IOld che delega semplicemente le chiamate all'implementazione gestita dell'interfaccia INew . Di conseguenza, il marshaller personalizzato funge da ponte tra le interfacce gestite e non gestite.

Nota

I marshaller personalizzati non vengono richiamati quando si chiama dal codice gestito al codice non gestito in un'interfaccia di sola distribuzione.

Definire il tipo di marshalling

Prima di poter creare un marshaller personalizzato, è necessario definire le interfacce gestite e non gestite che verranno sottoposto a marshalling. Queste interfacce eseguono in genere la stessa funzione, ma vengono esposte in modo diverso agli oggetti gestiti e non gestiti.

Un compilatore gestito produce un'interfaccia gestita dai metadati e l'interfaccia risultante è simile a qualsiasi altra interfaccia gestita. Nell'esempio seguente viene illustrata un'interfaccia tipica.

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

Definire il tipo non gestito in Interface Definition Language (IDL) e compilarlo con il compilatore MIDL (Microsoft Interface Definition Language). Definire l'interfaccia all'interno di un'istruzione di libreria e assegnarla un ID interfaccia con l'attributo UUID (Universal Unique Identifier), come illustrato nell'esempio seguente.

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

Il compilatore MIDL produce diversi file di output. Se l'interfaccia è definita in Old.idl, il file di output Old_i.c definisce una const variabile con l'identificatore di interfaccia (IID) dell'interfaccia, come illustrato nell'esempio seguente.

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

Il file Old.h viene prodotto anche da MIDL. Contiene una definizione C++ dell'interfaccia che può essere inclusa nel codice sorgente C++.

Implementare l'interfaccia ICustomMarshaler

Il marshaller personalizzato deve implementare l'interfaccia ICustomMarshaler per fornire i wrapper appropriati al runtime.

Il codice C# seguente visualizza l'interfaccia di base che deve essere implementata da tutti i marshaller personalizzati.

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

L'interfaccia ICustomMarshaler include metodi che forniscono supporto per la conversione, supporto per la pulizia e informazioni sui dati da sottoporre a marshalling.

Tipo di operazione Metodo ICustomMarshaler Descrizione
Conversione (da codice nativo a codice gestito) MarshalNativeToManaged Effettua il marshalling di un puntatore ai dati nativi in un oggetto gestito. Questo metodo restituisce un wrapper chiamabile di runtime personalizzato (RCW) in grado di effettuare il marshalling dell'interfaccia non gestita passata come argomento. Il marshaller deve restituire un'istanza dell'RCW personalizzato per tale tipo.
Conversione (da codice gestito a nativo) MarshalManagedToNative Effettua il marshalling di un oggetto gestito in un puntatore ai dati nativi. Questo metodo restituisce un wrapper com chiamabile personalizzato (CCW) in grado di effettuare il marshalling dell'interfaccia gestita passata come argomento. Il marshaller deve restituire un'istanza del CCW personalizzato per tale tipo.
Pulizia (di codice nativo) CleanUpNativeData Consente al marshaller di pulire i dati nativi (CCW) restituiti dal MarshalManagedToNative metodo .
Pulizia (di codice gestito) CleanUpManagedData Consente al marshaller di pulire i dati gestiti (RCW) restituiti dal MarshalNativeToManaged metodo .
Informazioni (sul codice nativo) GetNativeDataSize Restituisce le dimensioni dei dati non gestiti da sottoporre a marshalling.

Conversione

ICustomMarshaler.MarshalNativeToManaged

Effettua il marshalling di un puntatore ai dati nativi in un oggetto gestito. Questo metodo restituisce un wrapper chiamabile di runtime personalizzato (RCW) in grado di effettuare il marshalling dell'interfaccia non gestita passata come argomento. Il marshaller deve restituire un'istanza dell'RCW personalizzato per tale tipo.

ICustomMarshaler.MarshalManagedToNative

Effettua il marshalling di un oggetto gestito in un puntatore ai dati nativi. Questo metodo restituisce un wrapper com chiamabile personalizzato (CCW) in grado di effettuare il marshalling dell'interfaccia gestita passata come argomento. Il marshaller deve restituire un'istanza del CCW personalizzato per tale tipo.

Pulizia

ICustomMarshaler.CleanUpNativeData

Consente al marshaller di pulire i dati nativi (CCW) restituiti dal MarshalManagedToNative metodo .

ICustomMarshaler.CleanUpManagedData

Consente al marshaller di pulire i dati gestiti (RCW) restituiti dal MarshalNativeToManaged metodo .

Informazioni sulle dimensioni

ICustomMarshaler.GetNativeDataSize

Restituisce le dimensioni dei dati non gestiti da sottoporre a marshalling.

Nota

Se un marshaller personalizzato chiama i metodi che impostano l'ultimo errore P/Invoke durante il marshalling da nativo a gestito o durante la pulizia, il valore restituito da Marshal.GetLastWin32Error() e Marshal.GetLastPInvokeError() rappresenterà la chiamata nelle chiamate di marshalling o pulizia. Ciò può causare errori mancanti quando si usano marshaller personalizzati con P/Invoke con DllImportAttribute.SetLastError impostato su true. Per mantenere l'ultimo errore P/Invoke, usare i Marshal.GetLastPInvokeError() metodi e Marshal.SetLastPInvokeError(Int32) nell'implementazione ICustomMarshaler .

Implementare il metodo GetInstance

Oltre a implementare l'interfaccia ICustomMarshaler , i marshaller personalizzati devono implementare un static metodo denominato GetInstance che accetta come String parametro e ha un tipo restituito di ICustomMarshaler. Questo static metodo viene chiamato dal livello di interoperabilità COM di Common Language Runtime per creare un'istanza del marshaller personalizzato. La stringa passata a GetInstance è un cookie che il metodo può usare per personalizzare il marshaller personalizzato restituito. L'esempio seguente mostra un'implementazione minima, ma 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();
}

Applicare MarshalAsAttribute

Per usare un marshaller personalizzato, è necessario applicare l'attributo al parametro o al campo di cui viene eseguito il MarshalAsAttribute marshalling.

È inoltre necessario passare il valore di UnmanagedType.CustomMarshaler enumerazione al MarshalAsAttribute costruttore. Inoltre, è necessario specificare il MarshalType campo con uno dei parametri denominati seguenti:

  • MarshalType (obbligatorio): nome completo dell'assembly del marshaller personalizzato. Il nome deve includere lo spazio dei nomi e la classe del marshaller personalizzato. Se il marshaller personalizzato non è definito nell'assembly in cui viene usato, è necessario specificare il nome dell'assembly in cui è definito.

    Nota

    È possibile usare il MarshalTypeRef campo anziché il MarshalType campo . MarshalTypeRef accetta un tipo più semplice da specificare.

  • MarshalCookie (facoltativo): cookie passato al marshaller personalizzato. È possibile usare il cookie per fornire informazioni aggiuntive al marshaller. Ad esempio, se lo stesso marshaller viene usato per fornire un certo numero di wrapper, il cookie identifica un wrapper specifico. Il cookie viene passato al GetInstance metodo del marshaller.

L'attributo MarshalAsAttribute identifica il marshaller personalizzato in modo che possa attivare il wrapper appropriato. Il servizio di interoperabilità di Common Language Runtime esamina quindi l'attributo e crea il marshaller personalizzato la prima volta che l'argomento (parametro o campo) deve essere sottoposto a marshalling.

Il runtime chiama quindi i MarshalNativeToManaged metodi e MarshalManagedToNative sul marshaller personalizzato per attivare il wrapper corretto per gestire la chiamata.

Usare un marshaller personalizzato

Al termine del marshaller personalizzato, è possibile usarlo come wrapper personalizzato per un determinato tipo. L'esempio seguente illustra la definizione dell'interfaccia IUserData gestita:

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

Nell'esempio seguente l'interfaccia IUserData usa il NewOldMarshaler marshaller personalizzato per consentire alle applicazioni client non gestite di passare un'interfaccia IOld al DoSomeStuff metodo . La descrizione gestita del DoSomeStuff metodo accetta un'interfaccia INew , come illustrato nell'esempio precedente, mentre la versione non gestita di DoSomeStuff accetta un IOld puntatore all'interfaccia, come illustrato nell'esempio seguente.

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

La libreria dei tipi generata esportando la definizione gestita di IUserData restituisce la definizione non gestita illustrata in questo esempio anziché la definizione standard. L'attributo MarshalAsAttribute applicato all'argomento INew nella definizione gestita del DoSomeStuff metodo indica che l'argomento usa un marshaller personalizzato, come illustrato nell'esempio seguente.

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

Negli esempi precedenti il primo parametro fornito all'attributo MarshalAsAttribute è il UnmanagedType.CustomMarshaler valore UnmanagedType.CustomMarshalerdi enumerazione .

Il secondo parametro è il MarshalType campo , che fornisce il nome completo dell'assembly del marshaller personalizzato. Questo nome è costituito dallo spazio dei nomi e dalla classe del marshaller personalizzato (MarshalType="MyCompany.NewOldMarshaler").