Un gestore di marshalling costituisce un ponte tra le funzionalità della vecchia e della nuova interfaccia. Il marshalling personalizzato offre i seguenti vantaggi:
Consente di abilitare le applicazioni client progettate per interagire con un'interfaccia obsoleta al fine di funzionare anche con i server che implementano una nuova interfaccia.
Consente di abilitare le applicazioni client progettate per interagire con una nuova interfaccia al fine di funzionare con i server che implementano un'interfaccia obsoleta.
Se si dispone di un'interfaccia che introduce un comportamento diverso del gestore di marshalling o che è esposta a COM (Component Object Model) in modo diverso, è possibile progettare un gestore di marshalling personalizzato anziché utilizzare quello di interoperabilità. L'utilizzo di un gestore di marshalling personalizzato consente di ridurre al minimo le problematiche derivanti dalla diversità tra nuovi componenti .NET Framework e componenti COM esistenti.
Si immagini ad esempio di sviluppare un'interfaccia gestita denominata INew. Quando questa interfaccia viene esposta a COM tramite un oggetto COM Callable Wrapper (CCW) standard, dispone degli stessi metodi dell'interfaccia gestita e utilizza le regole di marshalling incorporate nel gestore di marshalling di interoperabilità. Si immagini ora che un'interfaccia COM nota denominata IOld fornisca già le stesse funzionalità dell'interfaccia INew. Progetta un gestore di marshalling personalizzato, è possibile fornire un'implementazione non gestita IOld che rimanda semplicemente le chiamate l'implementazione gestita dell'interfaccia INew. Pertanto, il gestore di marshalling personalizzato funge da ponte tra le interfacce gestita e non gestita.
Nota |
|---|
I gestori di marshalling personalizzati non vengono richiamati dal lato del codice gestito al lato non gestito in un'interfaccia di solo invio. |
Definizione del tipo di marshalling
Prima di compilare un gestore di marshalling personalizzato, è necessario stabilire su quali interfacce gestite e non gestite dovrà agire. Tali interfacce svolgono solitamente la stessa funzione, ma sono esposte in modo diverso, l'una a oggetti gestiti, l'altra a oggetti non gestiti.
Un compilatore gestito utilizza metadati per produrre un'interfaccia gestita. L'interfaccia risultante appare come qualsiasi altra interfaccia gestita. Nell'esempio seguente viene illustrata un'interfaccia tipica.
public interface INew
{
void NewMethod();
}
Il tipo non gestito viene definito con il linguaggio di definizione dell'interfaccia (IDL, Interface Definition Language) e compilato con il compilatore MIDL (Microsoft Interface Definition Language). Viene definita l'interfaccia all'interno di un'istruzione di libreria e le viene assegnato un ID di interfaccia con l'attributo UUID (Universal Unique Identifier), come indicato 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, nel file di output Old_i.c verrà definita una variabile const con l'IID (identificatore di interfaccia) come dimostra l'esempio seguente.
const IID IID_IOld = {0x9B2BAADD,0x0705,0x11D3,{0xA0,0xCD,0x00,0xC0,0x4F,0xA3,0x58,0x26}};
Anche il file Old.h viene prodotto da MIDL. Contiene una definizione C++ dell'interfaccia che può essere inclusa nel proprio codice sorgente C++.
Implementazione dell'interfaccia ICustomMarshaler
Il gestore di marshalling personalizzato deve implementare l'interfaccia ICustomMarshaler per fornire i wrapper appropriati al runtime.
Nel codice C# seguente viene visualizzata l'interfaccia di base che deve essere implementata da tutti i gestori di marshalling personalizzati.
public interface ICustomMarshaler
{
Object MarshalNativeToManaged( IntPtr pNativeData );
IntPtr MarshalManagedToNative( Object ManagedObj );
void CleanUpNativeData( IntPtr pNativeData );
void CleanUpManagedData( Object ManagedObj );
int GetNativeDataSize();
}
L'interfaccia ICustomMarshaler comprende metodi che forniscono il supporto di conversione, di pulizia e le informazioni sui dati di cui effettuare il 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 oggetto Runtime Callable Wrapper (RCW) personalizzato in grado di effettuare il marshalling dell'interfaccia non gestita passata come argomento. Il gestore di marshalling deve restituire un'istanza dell'oggetto RCW personalizzato per il tipo. |
Conversione (da codice gestito a codice nativo) | MarshalManagedToNative | Effettua il marshalling di un oggetto gestito in un puntatore ai dati nativi. Questo metodo restituisce un oggetto COM Callable Wrapper (CCW) personalizzato in grado di effettuare il marshalling dell'interfaccia gestita passata come argomento. Il gestore di marshalling deve restituire un'istanza dell'oggetto CCW personalizzato per il tipo. |
Pulizia (di codice nativo) | CleanUpNativeData | Consente al gestore di marshalling di eseguire la pulizia dei dati nativi (CCW) restituiti dal metodo MarshalManagedToNative. |
Pulizia (di codice gestito) | CleanUpManagedData | Consente al gestore di marshalling di eseguire la pulizia dei dati gestiti (RCW) restituiti dal metodo MarshalNativeToManaged. |
Informazioni (sul codice nativo) | GetNativeDataSize | Restituisce le dimensioni dei dati non gestiti di cui effettuare il marshalling. |
Conversion
ICustomMarshaler.MarshalNativeToManaged
Effettua il marshalling di un puntatore ai dati nativi in un oggetto gestito. Questo metodo restituisce un oggetto Runtime Callable Wrapper (RCW) personalizzato in grado di effettuare il marshalling dell'interfaccia non gestita passata come argomento. Il gestore di marshalling deve restituire un'istanza dell'oggetto RCW personalizzato per il tipo.
ICustomMarshaler.MarshalManagedToNative
Effettua il marshalling di un oggetto gestito in un puntatore ai dati nativi. Questo metodo restituisce un oggetto COM Callable Wrapper (CCW) personalizzato in grado di effettuare il marshalling dell'interfaccia gestita passata come argomento. Il gestore di marshalling deve restituire un'istanza dell'oggetto CCW personalizzato per il tipo.
Pulizia
Informazioni sulle dimensioni
Implementazione del metodo GetInstance
Oltre a implementare l'interfaccia ICustomMarshaler, i gestori di marshalling personalizzati devono implementare un metodo static denominato GetInstance che accetta un String come parametro e il cui tipo restituito è ICustomMarshaler. Questo metodo static viene chiamato dal livello di interoperabilità COM di Common Language Runtime per creare un'istanza del gestore di marshalling personalizzato. La stringa passata a GetInstance è un cookie che il metodo può utilizzare per personalizzare il gestore di marshalling personalizzato restituito.
static ICustomMarshaler *GetInstance(String *pstrCookie);
Applicazione di MarshalAsAttribute
Per utilizzare un gestore di marshalling personalizzato, applicare l'attributo MarshalAsAttribute al parametro o al campo di cui si desidera effettuare il marshalling.
È necessario inoltre passare il valore di enumerazione UnmanagedType.CustomMarshaler al costruttore MarshalAsAttribute. È necessario inoltre specificare il campo MarshalType con uno dei seguenti parametri denominati:
MarshalType
(obbligatorio): nome completo di assembly del gestore di marshalling personalizzato. Il nome deve includere lo spazio dei nomi e la classe del gestore di marshalling personalizzato. Se il gestore di marshalling personalizzato non è definito nell'assembly in cui viene utilizzato, è necessario specificare il nome dell'assembly in cui è definito.
MarshalCookie
(opzionale): un cookie passato al gestore di marshalling personalizzato. È possibile utilizzare il cookie per fornire ulteriori informazioni al gestore di marshalling personalizzato. Se, ad esempio, lo stesso gestore di marshalling viene utilizzato per fornire più wrapper, il cookie ne identificherà uno specifico. Il cookie viene passato al metodo GetInstance del gestore di marshalling.
L'attributo MarshalAsAttribute identifica il gestore di marshalling personalizzato in modo che quest'ultimo possa attivare il wrapper appropriato. Il servizio di interoperabilità di Common Language Runtime esamina quindi l'attributo e crea il gestore di marshalling personalizzato la prima volta in cui è necessario eseguire il marshalling dell'argomento (parametro o campo).
Il runtime chiama quindi i metodi MarshalNativeToManaged e MarshalManagedToNative nel gestore di marshalling personalizzato per attivare il wrapper corretto per la gestione della chiamata.
Utilizzo di un gestore di marshalling personalizzato
Quando il gestore di marshalling personalizzato è completo, può+ essere utilizzato come wrapper personalizzato per un determinato tipo. Nell'esempio seguente viene illustrata la definizione dell'interfaccia gestita IUserData:
interface IUserData
{
void DoSomeStuff(INew pINew);
}
Nell'esempio riportato di seguito, l'interfaccia IUserData utilizza i gestori di marshalling NewOldMarshaler personalizzati per abilitare applicazioni client non gestite al fine di passare da un'interfaccia IOld al metodo DoSomeStuff. La descrizione gestita del metodo DoSomeStuff accetta un'interfaccia INew, come illustrato nel precedente esempio, mentre la versione non gestita di DoSomeStuff accetta un puntatore a interfaccia IOld, 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 metodo DoSomeStuff indica che l'argomento utilizza un gestore di marshalling personalizzato, come illustrato nell'esempio riportato di seguito.
Imports System.Runtime.InteropServices
using System.Runtime.InteropServices;
using namespace System::Runtime::InteropServices;
Public Interface IUserData
Sub DoSomeStuff( _
<MarshalAs(UnmanagedType.CustomMarshaler, _
MarshalType := "MyCompany.NewOldMarshaler")> pINew As INew)
End Interface
interface IUserData
{
void DoSomeStuff(
[MarshalAs(UnmanagedType.CustomMarshaler,
MarshalType="MyCompany.NewOldMarshaler")]
INew pINew
);
}
public interface class IUserData
{
void DoSomeStuff(
[MarshalAs(UnmanagedType::CustomMarshaler,
MarshalType="MyCompany.NewOldMarshaler")]
INew^ pINew
);
};
Negli esempi precedenti, il primo parametro fornito all'attributo MarshalAsAttribute è corrisponde al valore di enumerazione UnmanagedType.CustomMarshalerUnmanagedType.CustomMarshaler.
Il secondo parametro è il campo MarshalType che fornisce il nome completo di assembly del gestore di marshalling personalizzato. Questo nome consiste dello spazio dei nomi e della classe del gestore di marshalling personalizzato (MarshalType="MyCompany.NewOldMarshaler").