Conversion d'un type exporté

Cette rubrique décrit comment le processus d'exportation convertit les types suivants :

  • Classes

  • Interfaces

  • Types valeur

  • Énumérations

En général, les types exportés conservent le nom qu'ils avaient dans un assembly, à l'exclusion de l'espace de noms associé au nom managé. Par exemple, le type A.B.IList dans l'exemple de code suivant est converti en IList dans la bibliothèque de types exportée. Un client COM peut se référer au type en tant que IList au lieu de A.B.IList.

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

Compte tenu de cette approche, les noms de types dans un assembly peuvent le cas échéant entrer en collision les uns avec les autres, car des types situés dans des espaces de noms différents peuvent porter le même nom. Lorsque le processus d'exportation détecte une collision, il conserve l'espace de noms afin de lever les ambiguïtés entourant l'attribution de noms. L'exemple de code suivant montre deux espaces de noms avec le même nom de type.

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 {…}
}

La représentation de la bibliothèque de types suivante montre la résolution de chaque nom de type. De même, dans la mesure où les signes « . » (points) ne sont pas valables dans les noms de bibliothèques de types, le processus d'exportation remplace chaque point par un trait de soulignement.

Représentation de la bibliothèque de types

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

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

Le processus d'exportation génère aussi automatiquement un identificateur programmatique (ProgId) en combinant espace de noms et nom de type. Par exemple, le ProgId généré pour la classe LinkedList managée indiquée dans les exemples précédents est A.B.LinkedList.

La combinaison espace de noms et nom de type peut donner lieu à un ProgId non valide. Un ProgId est limité à 39 caractères et ne peut contenir aucun signe de ponctuation autre que le point. Pour éviter ces limitations, vous pouvez spécifier un ProgId dans votre code source en appliquant ProgIdAttribute, au lieu de laisser le processus d'exportation générer un identificateur à votre place.

Classes

Le processus d'exportation convertit chaque classe publique (qui omet l'attribut ComVisible (false)) dans un assembly en une coclasse dans une bibliothèque de types. Une coclasse exportée ne possède ni méthode ni propriété ; cependant, elle conserve le nom de la classe managée et implémente toutes les interfaces explicitement implémentées par la classe managée.

L'exemple de code suivant montre la définition de l'interface IShape et de la classe Circle, qui implémente IShape. La représentation de la bibliothèque de types convertie suit l'exemple de code.

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);
}

Représentation de la bibliothèque de types

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

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

Chaque coclasse peut implémenter une autre interface, appelée interface de classe, que le processus d'exportation peut générer automatiquement. L'interface de classe expose toutes les méthodes et propriétés disponibles dans la classe managée d'origine, permettant ainsi aux clients COM d'y accéder en appelant l'interface de classe.

Vous pouvez assigner un identificateur unique universel (UUID, Universal Unique Identifier) spécifique à la classe en appliquant l'attribut GuidAttribute immédiatement au-dessus de la définition de la classe managée. Pendant le processus de conversion, le processus d'exportation transfère la valeur fournie à l'attribut GuidAttribute à l'UUID dans la bibliothèque de types. Sinon, le processus d'exportation obtient les UUID à partir d'un hachage qui comprend le nom complet de la classe, y compris l'espace de noms. L'utilisation du nom complet garantit qu'une classe avec un nom particulier dans un espace de noms spécifique génère toujours le même UUID, et que deux classes portant des noms différents ne génèrent jamais le même UUID.

Les classes abstraites et les classes sans constructeur public par défaut sont marquées avec l'attribut de bibliothèque de types noncreatable. D'autres attributs de bibliothèque de types qui s'appliquent aux coclasses, tels que licensed, hidden, restricted et control, ne sont pas définis.

Interfaces

Le processus d'exportation convertit les interfaces managées en interfaces COM avec les mêmes méthodes et propriétés que l'interface managée, mais avec des signatures de méthodes qui diffèrent considérablement.

Identités des interfaces

Les interfaces COM comprennent un identificateur d'interface (IID) pour distinguer les interfaces les unes des autres. Vous pouvez assigner un IID fixe à n'importe quelle interface managée en appliquant l'attribut GuidAttribute. Si vous omettez cet attribut et n'assignez pas un IID fixe, le processus d'exportation en affecte automatiquement un pendant la conversion. Un IID assigné par le runtime comprend le nom de l'interface (y compris l'espace de noms) et la signature complète de toutes les méthodes définies dans l'interface. En réorganisant les méthodes sur l'interface managée ou en modifiant l'argument et les types de retour de la méthode, vous modifiez l'IID assigné à cette interface. Le changement d'un nom de méthode n'affecte pas l'IID.

En utilisant la méthode QueryInterface implémentée par le runtime, les clients COM peuvent obtenir une interface ayant soit un IID fixe soit un IID assigné par le runtime. Les IID générés par le runtime ne sont pas rendus persistants dans les métadonnées du type.

Types interface

Sauf indication contraire, le processus d'exportation convertit toutes les interfaces managées en interfaces doubles dans une bibliothèque de types. Les interfaces doubles permettent aux clients COM de choisir entre une liaison anticipée et une liaison tardive.

Vous pouvez appliquer l'attribut InterfaceTypeAttribute à une interface pour indiquer sélectivement que l'interface doit être exportée en tant qu'interface double, interface dérivée de l'interface IUnknown ou interface de dispatch uniquement (dispinterface). Toutes les interfaces exportées s'étendent directement à partir de IUnknown ou de IDispatch, indépendamment de leur hiérarchie d'héritage dans le code managé.

L'exemple de code suivant montre les valeurs facultatives pour la détermination du type interface. Une fois exportées en une bibliothèque de types, ces options produisent les résultats indiqués dans la représentation de la bibliothèque de types qui suit le code.

' 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();
}

Représentation de la bibliothèque de types

[ 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 plupart des interfaces sont marquées avec les attributs de bibliothèque de types odl et oleautomation pendant le processus d'exportation. (Les dispinterfaces constituent l'exception). Les interfaces doubles sont marquées avec l'attribut de bibliothèque de types dual. Une interface double dérive de l'interface IDispatch, mais elle expose également des emplacements vtable pour ses méthodes.

Interfaces de classe

Pour une description complète de l'interface de classe et pour les recommandations d'usage, consultez Présentation de l'interface de classe. Le processus d'exportation peut générer cette interface automatiquement pour le compte d'une classe managée sans interface définie explicitement dans le code managé. Les clients COM ne peuvent pas accéder directement aux méthodes de la classe.

L'exemple de code suivant montre une classe de base et une classe dérivée. Aucune de ces classes n'implémente une interface explicite. Le processus d'exportation fournit une interface de classe pour les deux classes managées.

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;}
}

Représentation de la bibliothèque de types

[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();
}

Les interfaces de classe exportées présentent les caractéristiques suivantes :

  • Chaque interface de classe conserve le nom de la classe managée, mais elle est précédée d'un trait de soulignement. Lorsqu'un nom d'interface entre en conflit avec un nom d'interface défini précédemment, le nouveau nom reçoit un trait de soulignement suivi d'un numéro incrémentiel à la fin. Par exemple, le prochain nom disponible pour _ClassWithClassInterface est _ClassWithClassInterface_2.

  • Le processus d'exportation génère toujours de nouveaux identificateurs d'interface (IID). Vous ne pouvez pas définir explicitement l'IID de l'interface de classe.

  • Par défaut, les deux interfaces de classe dérivent des interfaces IDispatch.

  • Les interfaces possèdent comme attributs ODL, dual, hidden, nonextensible et oleautomation.

  • Les deux interfaces possèdent tous les membres publics de leur classe de base (System.Object).

  • Elles ne contiennent pas les membres privés et internes de la classe.

  • Chaque membre reçoit automatiquement un DispId unique. Les DispId peuvent être définis explicitement en appliquant DispIdAttribute au membre de la classe.

  • Les signatures de méthode sont transformées pour retourner des HRESULT et ont des paramètres [out, retval].

  • Les propriétés et champs sont transformés en [propget], [propput] et [propputref].

Interface par défaut

COM possède la notion d'une interface par défaut. Les membres de l'interface par défaut sont considérés comme les membres de la classe par des langages à liaison tardive tels que Visual Basic. Dans le .NET Framework, une interface par défaut n'est pas nécessaire, car les classes elles-mêmes peuvent avoir des membres. Cependant, lorsqu'elles explosent des classes à COM, les classes sont plus faciles à utiliser si elles disposent d'une interface par défaut.

Lorsqu'une classe managée est exportée vers une bibliothèque de types en tant que coclasse, une interface est généralement identifiée en tant qu'interface par défaut pour la classe. Si aucune interface n'est identifiée comme étant la valeur par défaut dans la bibliothèque de types, la plupart des applications COM supposent que la première interface implémentée est l'interface par défaut de cette coclasse.

Comme le montre l'exemple de code suivant, le processus d'exportation convertit une classe managée dépourvue d'interface de classe et marque la première interface implémentée en tant qu'interface par défaut dans la bibliothèque de types exportée. La représentation de la bibliothèque de types de la classe convertie suit l'exemple de code suivant.

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

Représentation de la bibliothèque de types

coclass ClassWithNoClassInterface {
    [default] IExplicit; 
    IAnother;
}

Le processus d'exportation marque toujours l'interface de classes comme l'interface par défaut de la classe, quelles que soient les autres interfaces que la classe implémente de manière explicite. L'exemple suivant illustre deux classes.

<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();   
}

Représentation de la bibliothèque de types

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

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

Types valeur

Les types valeur (les types qui étendent System.Value) sont exportés vers les bibliothèques de types en tant que structures de style C avec la définition de type. La disposition des membres de la structure est contrôlée à l'aide de StructLayoutAttribute, qui est appliqué au type. Seuls les champs du type valeur sont exportés. Si un type valeur possède des méthodes, celles-ci sont inaccessibles à partir de COM.

Par exemple :

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

Le type valeur Point est exporté vers COM en tant que typedef point, comme le montre l'exemple suivant :

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

Notez que le processus de conversion supprime la méthode SetXY dans typedef.

Énumérations

Le processus d'exportation ajoute une énumération managée aux bibliothèques de types en tant qu'énumération avec les noms des membres modifiés afin de garantir une attribution de nom univoque aux membres. Pour s'assurer que chaque nom de membre est unique, Tlbexp.exe fait précéder d'un trait de soulignement le nom de l'énumération de chaque membre pendant le processus d'exportation. Par exemple, l'énumération simple suivante produit un ensemble de représentations de la bibliothèque de types.

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

Représentation de la bibliothèque de types

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

Le runtime limite la portée des membres d'énumérations managées à celle de l'énumération à laquelle ils appartiennent. Par exemple, toutes les références à Sunday dans l'énumération DaysOfWeek, utilisée dans l'exemple précédent, doivent être qualifiées avec l'énumération DaysOfWeek. Vous ne pouvez pas référencer Sunday à la place de DaysOfWeek.Sunday. L'attribution de noms uniques aux membres est une exigence des énumérations COM.

Voir aussi

Concepts

Conversion d'un assembly exporté

Conversion d'un module exporté

Conversion d'un membre exporté

Conversion d'un paramètre exporté

Autres ressources

Résumé de la conversion d'un assembly en bibliothèque de types