Conversione dei tipi esportati

In questo argomento viene descritto in che modo il processo di esportazione converte i seguenti tipi:

  • Classi

  • Interfacce

  • Tipi di valore

  • Enumerazioni

In generale, i tipi esportati conservano il nome che avevano nell'assembly, fatta eccezione per lo spazio dei nomi associato al nome gestito. Il tipo A.B.IList che appare nell'esempio di codice riportato di seguito viene ad esempio convertito in IList nella libreria dei tipi esportata. Un client COM farà riferimento al tipo IList anziché a A.B.IList.

Namespace A
    Namespace B
        Interface IList
               …       
        End Interface
    End Namespace
End Namespace
namespace A {
    namespace B {
        interface IList {
               …
        }
    }
}

Ne consegue che esisterebbe la possibilità che si verifichi un conflitto, in quanto i tipi contenuti in un assembly, se risiedono in spazi dei nomi diversi, possono avere lo stesso nome. Quando il processo di esportazione rileva un conflitto, conserva lo spazio dei nomi per risolvere le ambiguità di denominazione. Nell'esempio di codice che segue vengono illustrati due spazi dei nomi contenenti tipi che hanno lo stesso nome.

Namespace A
    Namespace B
        Public Class LinkedList
            Implements IList
        End Class
      
        Public Interface IList
        End Interface 
    End Namespace
End Namespace

Namespace C
    Public Interface IList
    End Interface
End Namespace
namespace A {
    namespace B {
        public class LinkedList : IList {…}
        public interface IList {…}
    }
}
   namespace C {
       public interface IList {…}
}

Nella rappresentazione di libreria dei tipi riportata di seguito viene mostrata la risoluzione del nome di ciascun tipo. Inoltre, siccome i nomi utilizzati nelle librerie dei tipi non possono contenere punti, il processo di esportazione sostituisce ciascun punto con un carattere di sottolineatura.

Rappresentazione di libreria dei tipi

library Widgets 
{
    […]
    coclass LinkedList 
    {
        interface A_B_IList
    };

    […]
    interface A_B_IList {…};
    […]
    interface C_IList {…};
};

Il processo di esportazione genera automaticamente anche un identificatore a livello di codice (ProgId, Programmatic Identifier), combinando spazio dei nomi e nome del tipo. Ad esempio, il ProgId generato per la classe gestita LinkedList che appariva nel precedente esempio è A.B.LinkedList.

La combinazione di spazio dei nomi e nome di tipo può produrre un ProgId non valido. Un ProgId non può eccedere i 39 caratteri e non può contenere segni di punteggiatura diversi dal punto. Per ovviare a queste limitazioni, è possibile specificare un ProgId nel codice sorgente utilizzando ProgIdAttribute, anziché consentire al processo di esportazione di generarne uno automaticamente.

Classi

Il processo di esportazione converte ogni classe pubblica priva dell'attributo ComVisible (false) contenuta in un assembly in una coclasse nella libreria dei tipi. Una coclasse esportata non ha né metodi né proprietà. Essa conserva tuttavia il nome della classe gestita e implementa tutte le interfacce da questa esplicitamente implementate.

Nell'esempio di codice che segue viene mostrata la definizione dell'interfaccia IShape e della classe Circle, che implementa IShape. La rappresentazione della libreria dei tipi convertita è riportata dopo l'esempio di codice.

Public Interface IShape
    Sub Draw()
    Sub Move(x As Integer, y As Integer)
End Interface

Class Circle
    Implements IShape
    Sub Draw Implements IShape.Draw
    …    
    Sub Move(x As Integer, y As Integer) Implements IShape.Move
    …
    Sub Enlarge(x As Integer)
    …
End Class
public interface IShape {
    void Draw();
    void Move(int x, int y);
}
class Circle : IShape  {
    void Draw();
    void Move(int x, int y);
    void Enlarge(int x);
}

Rappresentazione di libreria dei tipi

[ uuid(…), dual, odl, oleautomation ]
interface IShape : IDispatch {
    HRESULT Draw();
    HRESULT Move(int x, int y);
}

[ uuid(…) ]
coclass Circle {
    interface IShape;
}

Ogni coclasse può implementare un'altra interfaccia, denominata interfaccia della classe, che il processo di esportazione può generare automaticamente. L'interfaccia della classe espone tutti i metodi e le proprietà disponibili nella classe gestita originale e permette quindi ai client COM di accedervi tramite chiamate rivolte all'interfaccia della classe.

È possibile assegnare alla classe un identificatore univoco universale (UUID, Universal Unique Identifier) applicando GuidAttribute subito sopra la definizione della classe gestita. Durante la conversione, il processo di esportazione trasferisce il valore fornito all'attributo GuidAttribute all'UUID nella libreria dei tipi. In alternativa, il processo di esportazione può ottenere gli UUID da un hash che include il nome completo della classe, compreso lo spazio dei nomi. L'utilizzo del nome completo assicura che una classe che ha un dato nome e risiede in un determinato spazio dei nomi produce sempre lo stesso UUID e che due classi con nomi diversi non generano mai lo stesso UUID.

Le classi astratte e quelle prive di un costruttore pubblico sono contrassegnate con l'attributo della libreria dei tipi noncreatable. Altri attributi della libreria dei tipi che si applicano alle coclassi, quali licensed, hidden, restricted e control, non vengono impostati.

Interfacce

Il processo di esportazione converte le interfacce gestite in interfacce COM conservando gli stessi metodi e le stesse proprietà, ma modificando in modo considerevole la firma dei metodi.

Identità delle interfacce

Le interfacce COM includono un identificatore di interfaccia (IID, Interface Identifier) che consente di distinguere un'interfaccia dall'altra. È possibile assegnare un IID fisso ad ogni interfaccia gestita utilizzando l'attributo GuidAttribute. Se si omette questo attributo e non si assegna un IID fisso, il processo di esportazione ne creerà automaticamente uno durante la conversione. Un IID assegnato dal runtime è composto dal nome dell'interfaccia (comprensivo dello spazio dei nomi) e dalla firma completa di tutti i metodi definiti all'interno dell'interfaccia. Se si riordinano i metodi dell'interfaccia gestita o si modificano gli argomenti o i tipi restituiti dei metodi, si modificherà l'IID assegnato a quell'interfaccia. La modifica del nome di un metodo non influisce sull'IID.

Il metodo QueryInterface implementato dal runtime consente ai client COM di ottenere un'interfaccia con un IID fisso o assegnato dal runtime. Gli IID generati dal runtime non vengono copiati nei metadati del tipo.

Tipi di interfacce

Se non diversamente specificato, il processo di esportazione converte tutte le interfacce gestite in interfacce duali nella libreria dei tipi. Le interfacce duali consentono ai client COM di scegliere tra associazione anticipata e tardiva.

Per indicare se un'interfaccia deve essere esportata come duale, derivata da IUnknown o solo dispatch, è possibile applicarvi l'attributo InterfaceTypeAttribute. Tutte le interfacce esportate sono estese direttamente da IUnknown o IDispatch, indipendentemente dalla gerarchia di ereditarietà nel codice gestito.

Nell'esempio di codice che segue vengono mostrati i valori facoltativi per il controllo del tipo di interfaccia. Con l'esportazione, queste opzioni producono i risultati illustrati nella rappresentazione di libreria dei tipi riportata dopo l'esempio di codice.

' Creates a Dual interface by default.
Public Interface InterfaceWithNoInterfaceType
    Sub test()
End Interface

' Creates a Dual interface explicitly.
<InterfaceType(ComInterfaceType.InterfaceIsDual)> _
Public Interface InterfaceWithInterfaceIsDual
    Sub test()
End Interface

' Creates an IUnknown interface (not dispatch).
<InterfaceType(ComInterfaceType.InterfaceIsIUnknown)> _
Public Interface InterfaceWithInterfaceIsIUnknown
    Sub test()
End Interface

' Creates a Dispatch-only interface (dispinterface).
<InterfaceType(ComInterfaceType.InterfaceIsIDispatch)> _
Public Interface InterfaceWithInterfaceIsIDispatch
    Sub test()
End Interface
// Creates a Dual interface by default.
public interface InterfaceWithNoInterfaceType {
    void test();
}
// Creates a Dual interface explicitly.
[InterfaceType(ComInterfaceType.InterfaceIsDual)]
public interface InterfaceWithInterfaceIsDual {
    void test();
}
// Creates an IUnknown interface (not dispatch).
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface InterfaceWithInterfaceIsIUnknown {
    void test();
}
// Creates a Dispatch-only interface(dispinterface).
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface InterfaceWithInterfaceIsIDispatch {
    void test();
}

Rappresentazione di libreria dei tipi

[ odl, uuid(…), dual, oleautomation ]
interface InterfaceWithNoInterfaceType : IDispatch {
    HRESULT test();
};
[ odl, uuid(…), dual, oleautomation ]
interface InterfaceWithInterfaceIsDual : IDispatch {
     HRESULT test();
};
[ odl, uuid(…), oleautomation ]
interface InterfaceWithInterfaceIsIUnknown : IUnknown {
     HRESULT test();
};
[ uuid(…) ]
dispinterface InterfaceWithInterfaceIsIDispatch {
     properties:
     methods:
         void test();
};

La maggior parte delle interfacce sono contrassegnate dagli attributi della libreria dei tipi odl e oleautomation durante il processo di esportazione. Fanno eccezione le interfacce solo dispatch. Le interfacce duali sono contrassegnate dall'attributo della libreria dei tipi dual. Le interfacce duali derivano dall'interfaccia IDispatch, ma espongono anche slot vtable per i propri metodi.

Interfacce della classe

Per una descrizione completa dell'interfaccia della classe e per avvertenze sull'utilizzo, vedere Introduzione all'interfaccia della classe. Il processo di esportazione può generare questa interfaccia automaticamente per le classi gestite che non dispongono di un'interfaccia esplicitamente definita nel codice gestito. I client COM non possono accedere ai metodi di una classe direttamente.

Nell'esempio di codice che segue vengono mostrate una classe base e una classe derivata. Nessuna delle due implementa un'interfaccia esplicita. Il processo di esportazione fornisce un'interfaccia della classe per entrambe le classi gestite.

Public Class BaseClassWithClassInterface
    Private Shared StaticPrivateField As Integer
    Private PrivateFld As Integer
   
    Private Property PrivateProp() As Integer
        Get
            Return 0
        End Get
        Set
        End Set
    End Property
   
    Private Sub PrivateMeth()
        Return
    End Sub 
    Friend Shared StaticInternalField As Integer
    Friend InternalFld As Integer
   
    Friend Property InternalProp() As Integer
        Get
            Return 0
        End Get
        Set
        End Set
    End Property
   
    Friend Sub InternalMeth()
        Return
    End Sub 
    Public Shared StaticPublicField As Integer
    Public PublicFld As Integer
   
    Public Property PublicProp() As Integer
        Get
            Return 0
        End Get
        Set
        End Set
    End Property
   
    Public Sub PublicMeth()
        Return
    End Sub
End Class
 
Public Class DerivedClassWithClassInterface
    Inherits BaseClassWithClassInterface
   
    Public Sub Test()
        Return
    End Sub
End Class
public class BaseClassWithClassInterface {
    private  static int  StaticPrivateField;
    private  int  PrivateFld;
    private  int  PrivateProp{get{return 0;} set{;}}
    private  void PrivateMeth() {return;}

    internal static int  StaticInternalField;
    internal int  InternalFld;
    internal int  InternalProp{get{return 0;} set{;}}
    internal void InternalMeth() {return;}

    public   static int  StaticPublicField;
    public   int  PublicFld;
    public   int  PublicProp{get{return 0;} set{;}}
    public   void PublicMeth() {return;}
}

public class DerivedClassWithClassInterface : BaseClassWithClassInterface {
    public void Test() {return;}
}

Rappresentazione di libreria dei tipi

[odl,uuid(…), hidden, dual, nonextensible, oleautomation]
interface _BaseClassWithClassInterface : IDispatch {
     [id(00000000),propget]   HRESULT ToString([out, retval] BSTR* p);
     [id(0x60020001)]         HRESULT Equals([in] VARIANT obj, 
                                [out, retval] VARIANT_BOOL* p);
     [id(0x60020002)]         HRESULT GetHashCode([out,retval] long* p);
     [id(0x60020003)]         HRESULT GetType([out, retval] _Type** p);
     [id(0x60020004),propget] HRESULT PublicProp([out,retval] long* p);
     [id(0x60020004),propput] HRESULT PublicProp([in] long p);
     [id(0x60020006)]         HRESULT PublicMeth();
     [id(0x60020007),propget] HRESULT PublicFld([out, retval]long* p);
     [id(0x60020007),propput] HRESULT PublicFld([in] long p);
};
[odl,uuid(…), hidden, dual, nonextensible, oleautomation]
interface _DerivedClassWithClassInterface : IDispatch {
     [id(00000000),propget]   HRESULT ToString([out, retval] BSTR* p);
     [id(0x60020001)]         HRESULT Equals([in] VARIANT obj, 
                                [out, retval] VARIANT_BOOL* p);
     [id(0x60020002)]         HRESULT GetHashCode([out,retval] long* p);
     [id(0x60020003)]         HRESULT GetType([out, retval] _Type** p);
     [id(0x60020004),propget] HRESULT PublicProp([out,retval] long* p);
     [id(0x60020004),propput] HRESULT PublicProp([in] long p);
     [id(0x60020006)]         HRESULT PublicMeth();
     [id(0x60020007),propget] HRESULT PublicFld([out, retval]long* p);
     [id(0x60020007),propput] HRESULT PublicFld([in] long p);
     [id(0x60020008)]         HRESULT Test();
}

Le interfacce delle classi esportate hanno le seguenti caratteristiche:

  • Ciascuna interfaccia della classe conserva il nome della classe gestita, prefissato da un carattere di sottolineatura. Quando il nome di un'interfaccia è in conflitto con un nome di interfaccia già definito, al nuovo nome viene accodato un carattere di sottolineatura seguito da un numero incrementale. Il successivo nome disponibile per _ClassWithClassInterface è ad esempio _ClassWithClassInterface_2.

  • Il processo di esportazione genera sempre identificatori di interfaccia (IID, Interface Identifier) nuovi. Non è possibile impostare esplicitamente l'IID dell'interfaccia della classe.

  • In base all'impostazione predefinita, entrambe le interfacce della classe derivano da interfacce IDispatch.

  • Le interfacce presentano gli attributi ODL, dual, hidden, nonextensible e oleautomation.

  • Entrambe le interfacce hanno tutti i membri pubblici della propria classe base (System.Object).

  • Non contengono i membri privati o interni della classe.

  • A ciascun membro è stato automaticamente assegnato un DispId univoco. È possibile impostare i DispId esplicitamente applicando DispIdAttribute al membro della classe.

  • Le firme dei metodi sono state trasformate in modo da restituire HRESULT e accettare parametri [out, retval].

  • Le proprietà e i campi sono stati trasformati in [propget], [propput] e [propputref].

Interfaccia predefinita

COM fa affidamento sulla disponibilità di un'interfaccia predefinita. I membri dell'interfaccia predefinita sono trattati come membri della classe da linguaggi ad associazione tardiva come Visual Basic. In .NET Framework non occorre predisporre un'interfaccia predefinita, perché le classi stesse possono avere membri. Le classi esposte a COM potranno essere tuttavia utilizzate molto più facilmente in presenza di un'interfaccia predefinita.

Quando una classe gestita viene esportata in una libreria dei tipi come coclasse, una delle interfacce viene solitamente designata interfaccia predefinita della classe. Se nessuna interfaccia viene designata come predefinita, la maggior parte delle applicazioni COM assumerà che l'interfaccia predefinita della coclasse è la prima interfaccia implementata.

Il processo di esportazione illustrato nell'esempio di codice che segue converte una classe gestita priva di interfaccia della classe e contrassegna la prima interfaccia implementata come predefinita nella libreria dei tipi esportata. La rappresentazione della classe convertita è riportata dopo l'esempio di codice.

<ClassInterface(ClassInterfaceType.None)> _
Public Class ClassWithNoClassInterface
    Implements IExplicit
    Implements IAnother
    Sub M()
    …
End Class
[ClassInterface(ClassInterfaceType.None)]
public class ClassWithNoClassInterface : IExplicit, IAnother {
    void M();   
}

Rappresentazione di libreria dei tipi

coclass ClassWithNoClassInterface {
    [default] IExplicit; 
    IAnother;
}

Nel processo di esportazione, l'interfaccia della classe viene sempre contrassegnata come interfaccia predefinita per la classe stessa, indipendentemente da ogni altra interfaccia implementata in modo esplicito dalla classe. Nell'esempio che segue vengono illustrate due classi.

<ClassInterface(ClassInterfaceType.AutoDispatch)> _
Public Class ClassWithAutoDispatch
    Implements IAnother
    Sub M()
    …
End Class 
 
<ClassInterface(ClassInterfaceType.AutoDual)> _
Public Class ClassWithAutoDual
    Implements IAnother
    Sub M()
    …
End Class
[ClassInterface(ClassInterfaceType.AutoDispatch)]
public class ClassWithAutoDispatch : IExplicit, IAnother {
    void M();   
}

[ClassInterface(ClassInterfaceType.AutoDual)]
public class ClassWithAutoDual : IExplicit, IAnother {
    void M();   
}

Rappresentazione di libreria dei tipi

// ClassWithAutoDispatch: IDispatch
coclass ClassWithAutoDispatch {
    [default] _ClassWithAutoDispatch;
    interface _Object;
    IExplicit;
    IAnother;
}
interface _ClassWithAutoDual {…}

coclass ClassWithAutoDual {
    [default] _ClassWithAutoDual; 
    IExplicit;
    IAnother;
}

Tipi valore

I tipi valore (tipi che estendono System.Value) sono esportati nelle librerie dei tipi come strutture di tipo C con definizione del tipo. Il layout dei membri della struttura è controllato con l'attributo StructLayoutAttribute, che viene applicato al tipo. Vengono esportati solo i campi del tipo valore. Se un tipo valore ha metodi, questi non saranno accessibili da COM.

Di seguito è riportato un esempio:

[StructLayout(LayoutKind.Sequential)]
public struct Point {
    int x;
    int y;
    public void SetXY(int x, int y){ 
        this.x = x;
        this.y = y;
    }
};

Il valore Point viene esportato in COM come typedef, come illustrato nel seguente esempio:

typedef 
[uuid(…)]
struct tagPoint {
    short x;
    short y;
} Point;

Si noti che il processo di conversione rimuove il metodo SetXY dal typedef.

Enumerazioni

I nomi dei membri delle enumerazioni gestite aggiunte alle librerie dei tipi durante il processo di esportazione vengono modificati in modo da garantirne l'univocità. Per assicurare che il nome di ciascun membro sia univoco, durante l'esportazione con Tlbexp.exe vi viene anteposto il nome dell'enumerazione seguito da un carattere di sottolineatura. La semplice enumerazione riportata di seguito produce ad esempio un set di rappresentazioni di librerie dei tipi.

Enum DaysOfWeek {
    Sunday = 0;
    Monday;
    Tuesday;
    …
};

Rappresentazione di libreria dei tipi

enum DaysOfWeek {
    DaysOfWeek_Sunday = 0;
    DaysOfWeek_Monday;
    DaysOfWeek_Tuesday;
    …
};

Nel runtime, l'ambito dei membri delle enumerazioni gestite viene circoscritto all'enumerazione a cui questi appartengono. Tutti i riferimenti al membro Sunday dell'enumerazione DaysOfWeek sopra illustrata devono essere ad esempio qualificati con l'enumerazione DaysOfWeek. Non è possibile fare riferimento a Sunday anziché a DaysOfWeek.Sunday. La denominazione univoca dei membri è un requisito delle enumerazioni COM.

Vedere anche

Concetti

Conversione degli assembly esportati
Conversione dei moduli esportati
Conversione dei membri esportati
Conversione dei parametri esportati

Altre risorse

Riepilogo della conversione da assembly a libreria dei tipi