Condividi tramite


Comportamento di marshalling predefinito

Il marshalling di interoperabilità opera su regole che determinano il comportamento dei dati associati ai parametri del metodo durante il passaggio tra memoria gestita e non gestita. Queste regole integrate controllano attività di marshalling come ad esempio le trasformazioni del tipo di dati, determinano se un chiamato può modificare i dati passati e restituire tali modifiche al chiamante, e in quali circostanze il marshaller fornisce ottimizzazioni delle prestazioni.

Questa sezione identifica le caratteristiche comportamentali predefinite del servizio di marshalling di interoperabilità. Presenta informazioni dettagliate sul marshalling delle matrici, sui tipi booleani, sui tipi char, sui delegati, sulle classi, sugli oggetti, sulle stringhe e sulle strutture.

Annotazioni

Il marshalling di tipi generici non è supportato. Per altre informazioni, vedere Interoperabilità con tipi generici.

Gestione della memoria con il marshaller di interoperabilità

Il marshaller di interoperabilità tenta sempre di liberare memoria allocata dal codice non gestito. Questo comportamento è conforme alle regole di gestione della memoria COM, ma differisce dalle regole che regolano C++nativo.

La confusione può verificarsi se si prevede un comportamento C++ nativo (senza liberare memoria) quando si usa platform invoke, che libera automaticamente la memoria per i puntatori. Ad esempio, la chiamata al metodo non gestito seguente da una DLL C++ non libera automaticamente alcuna memoria.

Firma non gestita

BSTR MethodOne (BSTR b) {
     return b;
}

Tuttavia, se si definisce il metodo come prototipo di invocazione della piattaforma, sostituire ogni tipo BSTR con un tipo String e chiamare MethodOne, il Common Language Runtime tenta di liberare b due volte. È possibile modificare il comportamento di marshalling usando IntPtr tipi anziché String tipi.

Il runtime usa sempre il CoTaskMemFree metodo in Windows e free il metodo in altre piattaforme per liberare memoria. Se la memoria che stai utilizzando non è stata allocata con il CoTaskMemAlloc metodo su Windows o il malloc metodo su altre piattaforme, è necessario usare un IntPtr e liberare manualmente la memoria utilizzando il metodo appropriato. Analogamente, è possibile evitare la liberazione automatica della memoria in situazioni in cui la memoria non deve mai essere liberata, ad esempio quando si usa la GetCommandLine funzione da Kernel32.dll, che restituisce un puntatore alla memoria kernel. Per informazioni dettagliate sulla liberazione manuale della memoria, vedere il Buffers Sample.

Marshalling predefinito per le classi

Le classi possono essere gestite solo tramite l'interoperabilità COM e vengono sempre trattate come interfacce. In alcuni casi l'interfaccia usata per effettuare il marshalling della classe è nota come interfaccia di classe. Per informazioni sull'override dell'interfaccia della classe con un'interfaccia di propria scelta, vedere Introduzione all'interfaccia della classe.

Passaggio delle classi a COM

Quando una classe gestita viene passata a COM, il marshaller di interoperabilità esegue automaticamente il wrapping della classe con un proxy COM e passa l'interfaccia di classe prodotta dal proxy alla chiamata al metodo COM. Il proxy delega quindi tutte le chiamate sull'interfaccia della classe all'oggetto gestito. Il proxy espone anche altre interfacce non implementate in modo esplicito dalla classe . Il proxy implementa automaticamente interfacce come IUnknown e IDispatch per conto della classe .

Passaggio di classi al codice .NET

Le coclassi non vengono in genere usate come argomenti del metodo in COM. Al contrario, un'interfaccia predefinita viene in genere passata al posto della coclasse.

Quando un'interfaccia viene passata nel codice gestito, il marshall di interoperabilità è responsabile dell'involucro dell'interfaccia con il wrapper appropriato e del passaggio del wrapper al metodo gestito. Determinare il wrapper da usare può essere difficile. Ogni istanza di un oggetto COM ha un unico wrapper univoco, indipendentemente dal numero di interfacce implementate dall'oggetto. Ad esempio, un singolo oggetto COM che implementa cinque interfacce distinte ha un solo wrapper. Lo stesso wrapper espone tutte e cinque le interfacce. Se vengono create due istanze dell'oggetto COM, vengono create due istanze del wrapper.

Affinché il wrapper mantenga lo stesso tipo per tutta la sua vita, il marshaller di interop deve identificare il wrapper corretto la prima volta che un'interfaccia esposta da un oggetto viene fatta passare attraverso il marshaller. Il marshaller identifica l'oggetto esaminando una delle interfacce implementate dall'oggetto .

Ad esempio, il marshaller determina che il wrapper della classe deve essere utilizzato per racchiudere l'interfaccia passata nel codice gestito. Quando l'interfaccia viene passata per la prima volta tramite il marshaller, il marshaller verifica se l'interfaccia proviene da un oggetto noto. Questo controllo si verifica in due situazioni:

  • Un'interfaccia viene implementata da un altro oggetto gestito passato a COM altrove. Il marshaller è in grado di identificare facilmente le interfacce esposte da oggetti gestiti ed è in grado di associare l'interfaccia all'oggetto gestito che fornisce l'implementazione. L'oggetto gestito viene quindi passato al metodo e non è necessario alcun wrapper.

  • Un oggetto già sottoposto a wrapping implementa l'interfaccia . Per determinare se questo è il caso, il marshaller esegue una query sull'oggetto per la relativa IUnknown interfaccia e confronta l'interfaccia restituita con le interfacce di altri oggetti già sottoposti a wrapping. Se l'interfaccia è uguale a quella di un altro wrapper, gli oggetti hanno la stessa identità e il wrapper esistente viene passato al metodo .

Se un'interfaccia non proviene da un oggetto noto, il marshaller esegue le operazioni seguenti:

  1. Il marshaller interroga l'oggetto per l'interfaccia IProvideClassInfo2. Se specificato, il marshaller usa il CLSID restituito da IProvideClassInfo2.GetGUID per identificare la coclasse che fornisce l'interfaccia. Utilizzando il CLSID, il marshaller può individuare il wrapper nel Registro di sistema se l'assembly è stato registrato in precedenza.

  2. Il marshaller esegue una query sull'interfaccia IProvideClassInfo. Se specificato, il marshaller usa l'oggetto ITypeInfo restituito da IProvideClassInfo.GetClassinfo per determinare il CLSID della classe che espone l'interfaccia. Il marshaller può usare il CLSID per individuare i metadati del wrapper.

  3. Se il marshaller non riesce ancora a identificare la classe , esegue il wrapping dell'interfaccia con una classe wrapper generica denominata System.__ComObject.

Conversione predefinita per i delegati

Un delegato gestito viene convertito in un'interfaccia COM o in un puntatore a funzione, in base al meccanismo di chiamata.

  • Per l'invocazione della piattaforma, un delegato viene sottoposto a marshalling come puntatore di funzione non gestita di default.

  • Per l'interoperabilità COM, un delegato viene sottoposto a marshalling come interfaccia COM di tipo _Delegate per impostazione predefinita. L'interfaccia _Delegate è definita nella libreria dei tipi Mscorlib.tlb e contiene il Delegate.DynamicInvoke metodo , che consente di chiamare il metodo a cui fa riferimento il delegato.

La tabella seguente illustra le opzioni di marshalling per il tipo di dati delegato gestito. L'attributo MarshalAsAttribute fornisce diversi UnmanagedType valori di enumerazione per effettuare il marshalling dei delegati.

Tipo di enumerazione Descrizione del formato non gestito
UnmanagedType.FunctionPtr Puntatore a funzione non gestito.
UnmanagedType.Interface Interfaccia di tipo _Delegate, come definito in Mscorlib.tlb.

Si consideri il codice di esempio seguente in cui i metodi di DelegateTestInterface vengono esportati in una libreria dei tipi COM. Si noti che solo i delegati contrassegnati con la ref parola chiave (o ByRef) vengono passati come parametri In/Out.

using System;
using System.Runtime.InteropServices;

public interface DelegateTest {
void m1(Delegate d);
void m2([MarshalAs(UnmanagedType.Interface)] Delegate d);
void m3([MarshalAs(UnmanagedType.Interface)] ref Delegate d);
void m4([MarshalAs(UnmanagedType.FunctionPtr)] Delegate d);
void m5([MarshalAs(UnmanagedType.FunctionPtr)] ref Delegate d);
}

Rappresentazione della libreria di tipi

importlib("mscorlib.tlb");
interface DelegateTest : IDispatch {
[id(…)] HRESULT m1([in] _Delegate* d);
[id(…)] HRESULT m2([in] _Delegate* d);
[id(…)] HRESULT m3([in, out] _Delegate** d);
[id()] HRESULT m4([in] int d);
[id()] HRESULT m5([in, out] int *d);
   };

Un puntatore a funzione può essere dereferenziato, così come qualsiasi altro puntatore a funzione non gestito può essere dereferenziato.

In questo esempio, quando i due delegati vengono sottoposti a marshalling come UnmanagedType.FunctionPtr, il risultato è un int e un puntatore a un int. Poiché i tipi delegati vengono sottoposti a marshalling, int qui rappresenta un puntatore a un void (void*), ovvero l'indirizzo del delegato in memoria. In altre parole, questo risultato è specifico per i sistemi Windows a 32 bit, poiché int qui rappresenta le dimensioni del puntatore alla funzione.

Annotazioni

Un riferimento al puntatore di funzione per un delegato gestito trattenuto dal codice non gestito non impedisce al Common Language Runtime di eseguire la raccolta dei rifiuti sull'oggetto gestito.

Ad esempio, il codice seguente non è corretto perché il riferimento all'oggetto cb , passato al SetChangeHandler metodo , non mantiene cb attivo oltre la durata del Test metodo . Una volta che l'oggetto viene sottoposto a cb Garbage Collection, il puntatore alla funzione passato a SetChangeHandler non è più valido.

public class ExternalAPI {
   [DllImport("External.dll")]
   public static extern void SetChangeHandler(
      [MarshalAs(UnmanagedType.FunctionPtr)]ChangeDelegate d);
}
public delegate bool ChangeDelegate([MarshalAs(UnmanagedType.LPWStr) string S);
public class CallBackClass {
   public bool OnChange(string S){ return true;}
}
internal class DelegateTest {
   public static void Test() {
      CallBackClass cb = new CallBackClass();
      // Caution: The following reference on the cb object does not keep the
      // object from being garbage collected after the Main method
      // executes.
      ExternalAPI.SetChangeHandler(new ChangeDelegate(cb.OnChange));
   }
}

Per compensare una raccolta dei rifiuti inattesa, il chiamante deve garantire che l'oggetto cb rimanga attivo finché il puntatore di funzione non gestito è in uso. Facoltativamente, è possibile disporre del codice non gestito per notificare al codice gestito quando il puntatore alla funzione non è più necessario, come illustrato nell'esempio seguente.

internal class DelegateTest {
   CallBackClass cb;
   // Called before ever using the callback function.
   public static void SetChangeHandler() {
      cb = new CallBackClass();
      ExternalAPI.SetChangeHandler(new ChangeDelegate(cb.OnChange));
   }
   // Called after using the callback function for the last time.
   public static void RemoveChangeHandler() {
      // The cb object can be collected now. The unmanaged code is
      // finished with the callback function.
      cb = null;
   }
}

Marshalling predefinito per i tipi valore

La maggior parte dei tipi di valore, come numeri interi e numeri a virgola mobile, è blittable e non richiede il marshalling. Altri tipi non blittable hanno rappresentazioni diverse nella memoria gestita e non gestita e richiedono il processo di marshalling. Altri tipi richiedono comunque la formattazione esplicita attraverso il limite di interoperabilità.

In questa sezione vengono fornite informazioni sui tipi di valore formattati seguenti:

Oltre a descrivere i tipi formattati, questo argomento identifica tipi di valori di sistema con un comportamento di marshalling insolito.

Un tipo formattato è un tipo complesso che contiene informazioni che controllano in modo esplicito il layout dei relativi membri in memoria. Le informazioni sul layout del membro vengono fornite usando l'attributo StructLayoutAttribute . Il layout può essere uno dei valori di enumerazione seguenti LayoutKind :

  • LayoutKind.Auto

    Indica che "Common Language Runtime" è libero di riordinare i membri del tipo per migliorare l'efficienza. Tuttavia, quando un tipo di valore viene passato al codice non gestito, il layout dei membri è prevedibile. Un tentativo di effettuare il marshalling di tale struttura genera automaticamente un'eccezione.

  • LayoutKind.Sequenziale

    Indica che i membri del tipo devono essere disposti in memoria non gestita nello stesso ordine in cui vengono visualizzati nella definizione del tipo gestito.

  • LayoutKind.Explicit

    Indica che i membri sono disposti in base all'oggetto FieldOffsetAttribute fornito con ogni campo.

Tipi di valore usati in Platform Invoke

Nell'esempio seguente i Point tipi e Rect forniscono informazioni sul layout dei membri usando StructLayoutAttribute.

Imports System.Runtime.InteropServices
<StructLayout(LayoutKind.Sequential)> Public Structure Point
   Public x As Integer
   Public y As Integer
End Structure
<StructLayout(LayoutKind.Explicit)> Public Structure Rect
   <FieldOffset(0)> Public left As Integer
   <FieldOffset(4)> Public top As Integer
   <FieldOffset(8)> Public right As Integer
   <FieldOffset(12)> Public bottom As Integer
End Structure
using System.Runtime.InteropServices;
[StructLayout(LayoutKind.Sequential)]
public struct Point {
   public int x;
   public int y;
}

[StructLayout(LayoutKind.Explicit)]
public struct Rect {
   [FieldOffset(0)] public int left;
   [FieldOffset(4)] public int top;
   [FieldOffset(8)] public int right;
   [FieldOffset(12)] public int bottom;
}

Quando viene eseguito il marshalling su codice non gestito, questi tipi formattati vengono distribuiti come strutture in stile C. In questo modo è possibile chiamare facilmente un'API non gestita con argomenti di struttura. Ad esempio, POINT e RECT strutture possono essere passate alla funzione API PtInRect di Microsoft Windows come segue:

BOOL PtInRect(const RECT *lprc, POINT pt);

È possibile passare strutture utilizzando la seguente definizione di invocazione della piattaforma:

Friend Class NativeMethods
    Friend Declare Auto Function PtInRect Lib "User32.dll" (
        ByRef r As Rect, p As Point) As Boolean
End Class
internal static class NativeMethods
{
   [DllImport("User32.dll")]
   internal static extern bool PtInRect(ref Rect r, Point p);
}

Il tipo di valore Rect deve essere passato per riferimento perché l'API non gestita si aspetta che un puntatore a RECT venga passato alla funzione. Il Point tipo di valore viene passato per valore perché l'API non gestita prevede che POINT venga passato sullo stack. Questa sottile differenza è molto importante. I riferimenti vengono passati al codice non gestito come puntatori. I valori vengono passati al codice non gestito nello stack.

Annotazioni

Quando un tipo formattato è sottoposto a marshalling come struttura, sono accessibili solo i suoi campi. Se il tipo include metodi, proprietà o eventi, non sono accessibili dal codice non gestito.

È anche possibile eseguire il marshalling delle classi nel codice non gestito come strutture in stile C, purché abbiano una disposizione fissa dei membri. Le informazioni sul layout dei membri per una classe vengono fornite anche con l'attributo StructLayoutAttribute . La differenza principale tra i tipi valore con layout fisso e le classi con layout fisso è il modo in cui vengono distribuiti al codice non gestito. I tipi valore vengono passati per valore (nello stack) e di conseguenza tutte le modifiche apportate ai membri del tipo dal chiamato non vengono visualizzate dal chiamante. I tipi riferimento vengono passati per riferimento (viene passato un riferimento al tipo nello stack); di conseguenza, tutte le modifiche apportate ai membri di tipo blittable di un tipo dalla funzione chiamata vengono viste dal chiamante.

Annotazioni

Se un tipo di riferimento ha membri di tipi non blittable, la conversione è necessaria due volte: la prima volta quando un argomento viene passato al lato non gestito e la seconda volta quando viene restituito dalla chiamata. A causa di questo sovraccarico aggiunto, i parametri In/Out devono essere applicati in modo esplicito a un argomento se il chiamante desidera visualizzare le modifiche apportate dal chiamato.

Nell'esempio seguente la SystemTime classe ha un layout membro sequenziale e può essere passata alla funzione API GetSystemTime di Windows.

<StructLayout(LayoutKind.Sequential)> Public Class SystemTime
   Public wYear As System.UInt16
   Public wMonth As System.UInt16
   Public wDayOfWeek As System.UInt16
   Public wDay As System.UInt16
   Public wHour As System.UInt16
   Public wMinute As System.UInt16
   Public wSecond As System.UInt16
   Public wMilliseconds As System.UInt16
End Class
[StructLayout(LayoutKind.Sequential)]
   public class SystemTime {
   public ushort wYear;
   public ushort wMonth;
   public ushort wDayOfWeek;
   public ushort wDay;
   public ushort wHour;
   public ushort wMinute;
   public ushort wSecond;
   public ushort wMilliseconds;
}

La GetSystemTime funzione è definita come segue:

void GetSystemTime(SYSTEMTIME* SystemTime);

La definizione platform invoke equivalente per GetSystemTime è la seguente:

Friend Class NativeMethods
    Friend Declare Auto Sub GetSystemTime Lib "Kernel32.dll" (
        ByVal sysTime As SystemTime)
End Class
internal static class NativeMethods
{
   [DllImport("Kernel32.dll", CharSet = CharSet.Auto)]
   internal static extern void GetSystemTime(SystemTime st);
}

Si noti che l'argomento SystemTime non viene tipizzato come argomento di riferimento perché SystemTime è una classe, non un tipo di valore. A differenza dei tipi valore, le classi vengono sempre passate per riferimento.

Nell'esempio di codice seguente viene illustrata una classe diversa Point con un metodo denominato SetXY. Poiché il tipo ha un layout sequenziale, può essere passato al codice non gestito e sottoposto a marshalling come struttura. Tuttavia, il SetXY membro non è chiamabile dal codice non gestito, anche se l'oggetto viene passato per riferimento.

<StructLayout(LayoutKind.Sequential)> Public Class Point
   Private x, y As Integer
   Public Sub SetXY(x As Integer, y As Integer)
      Me.x = x
      Me.y = y
   End Sub
End Class
[StructLayout(LayoutKind.Sequential)]
public class Point {
   int x, y;
   public void SetXY(int x, int y){
      this.x = x;
      this.y = y;
   }
}

Tipi di valore utilizzati nell'interoperabilità COM

I tipi formattati possono anche essere passati alle chiamate del metodo di interoperabilità COM. Infatti, quando viene esportato in una libreria dei tipi, i tipi valore vengono convertiti automaticamente in strutture. Come illustrato nell'esempio seguente, il Point tipo valore diventa una definizione di tipo (typedef) con il nome Point. Tutti i riferimenti al Point tipo valore altrove nella libreria dei tipi vengono sostituiti con il Point typedef.

Rappresentazione della libreria dei tipi

typedef struct tagPoint {
   int x;
   int y;
} Point;
interface _Graphics {
   …
   HRESULT SetPoint ([in] Point p)
   HRESULT SetPointRef ([in,out] Point *p)
   HRESULT GetPoint ([out,retval] Point *p)
}

Le stesse regole usate per effettuare il marshalling di valori e riferimenti alle chiamate platform invoke vengono usate quando si fa il marshalling tramite interfacce COM. Ad esempio, quando un'istanza Point del tipo di valore viene passata da .NET Framework a COM, Point viene passato come valore. Se il tipo di valore Point viene passato per riferimento, un puntatore a un Point viene passato sullo stack. Il marshaller di interoperabilità non supporta livelli più elevati di indirizzamento indiretto (Point **) in nessuna delle due direzioni.

Annotazioni

Le strutture con il LayoutKind valore di enumerazione impostato su Explicit non possono essere utilizzate nell'interoperabilità COM perché la libreria dei tipi esportata non può esprimere un layout esplicito.

Tipi di valori di sistema

Lo System spazio dei nomi contiene diversi tipi di valore che rappresentano la forma incapsulata dei tipi primitivi di runtime. Ad esempio, la struttura del tipo valore System.Int32 rappresenta la forma boxed di ELEMENT_TYPE_I4. Invece di eseguire il marshalling di questi tipi come strutture, come avviene per altri tipi formattati, eseguirli nello stesso modo dei tipi primitivi con cui vengono inglobati. System.Int32 viene quindi eseguito il marshalling come ELEMENT_TYPE_I4 anziché come struttura contenente un singolo membro di tipo long. La tabella seguente contiene un elenco dei tipi valore nello System spazio dei nomi che sono rappresentazioni incapsulate di tipi primitivi.

Tipo di valore di sistema Tipo di elemento
System.Boolean ELEMENT_TYPE_BOOLEANO
System.SByte ELEMENT_TYPE_I1
System.Byte ELEMENT_TYPE_UI1
System.Char ELEMENT_TYPE_CHAR
System.Int16 ELEMENT_TYPE_I2
System.UInt16 ELEMENT_TYPE_U2
System.Int32 ELEMENT_TYPE_I4
System.UInt32 ELEMENT_TYPE_U4
System.Int64 ELEMENT_TYPE_I8
System.UInt64 ELEMENT_TYPE_U8
System.Single ELEMENT_TYPE_R4
System.Double ELEMENT_TYPE_R8
System.String ELEMENT_TYPE_STRING
System.IntPtr ELEMENT_TYPE_I
System.UIntPtr ELEMENT_TYPE_U

Alcuni altri tipi di valore nello spazio dei nomi System vengono gestiti in modo diverso. Poiché il codice non gestito ha già formati ben definiti per questi tipi, il marshaller dispone di regole speciali per il marshalling. Nella tabella seguente sono elencati i tipi di valore speciali nello namespace System, nonché il tipo non gestito a cui viene effettuata la conversione.

Tipo di valore di sistema Tipo IDL
System.DateTime DATA
System.Decimal DECIMALE
System.Guid GUID
System.Drawing.Color OLE_COLOR

Il codice seguente illustra la definizione dei tipi non gestiti DATE, GUID, DECIMAL e OLE_COLOR nella libreria dei tipi Stdole2.

Rappresentazione della libreria di tipi

typedef double DATE;
typedef DWORD OLE_COLOR;

typedef struct tagDEC {
    USHORT    wReserved;
    BYTE      scale;
    BYTE      sign;
    ULONG     Hi32;
    ULONGLONG Lo64;
} DECIMAL;

typedef struct tagGUID {
    DWORD Data1;
    WORD  Data2;
    WORD  Data3;
    BYTE  Data4[ 8 ];
} GUID;

Il codice seguente mostra le definizioni corrispondenti nell'interfaccia gestita IValueTypes .

Public Interface IValueTypes
   Sub M1(d As System.DateTime)
   Sub M2(d As System.Guid)
   Sub M3(d As System.Decimal)
   Sub M4(d As System.Drawing.Color)
End Interface
public interface IValueTypes {
   void M1(System.DateTime d);
   void M2(System.Guid d);
   void M3(System.Decimal d);
   void M4(System.Drawing.Color d);
}

Rappresentazione della libreria di tipi

[…]
interface IValueTypes : IDispatch {
   HRESULT M1([in] DATE d);
   HRESULT M2([in] GUID d);
   HRESULT M3([in] DECIMAL d);
   HRESULT M4([in] OLE_COLOR d);
};

Vedere anche