Konvertieren exportierter Typen

In diesem Abschnitt wird die Konvertierung der folgenden Typen durch den Exportvorgang beschrieben:

  • Klassen

  • Schnittstellen

  • Werttypen

  • Enumerationen

Im Allgemeinen behalten exportierte Typen den Namen, den sie in der Assembly auch hatten. Eine Ausnahme ist hierbei der dem verwalteten Namen zugeordnete Namespace. Zum Beispiel der Typ A.B.IList des folgenden Codebeispiels wird in der exportierten Typbibliothek in IList konvertiert. Ein COM-Client kann auf diesen Typ mit IList anstatt mit A.B.IList verweisen.

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

Dadurch können eventuell Konflikte zwischen Typennamen innerhalb einer Assembly auftreten, wenn mehrere Typen in unterschiedlichen Namespaces denselben Namen haben. Wenn der Exportvorgang einen Konflikt erkennt, wird der Namespace beibehalten, um Mehrdeutigkeiten bei der Namensgebung zu vermeiden. Im folgenden Codebeispiel werden zwei Namespaces mit demselben Typnamen dargestellt.

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

In der folgenden Darstellung der Typbibliothek wird die Auflösung der Typnamen gezeigt. Da Typbibliotheksnamen keine Punkte enthalten dürfen, werden beim Exportvorgang alle Punkte in den Namen durch Unterstriche ersetzt.

Typbibliothekdarstellung

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

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

Weiterhin generiert der Exportvorgang automatisch eine ProgId (Programm-ID) durch Kombination des Namespace und des Typnamens. Für die verwaltete LinkedList-Klasse im vorherigen Beispiel lautet die generierte ProgId A.B.LinkedList.

Die Kombination des Namespace und Typennamens kann in einer ungültigen ProgId resultieren. Eine ProgId ist auf 39 Zeichen begrenzt und kann außer Punkten keine anderen Satzzeichen enthalten. Um diese Einschränkungen zu vermeiden, können Sie das ProgIdAttribute anwenden, um eine ProgId im Quellcode zu spezifizieren, anstatt vom Exportvorgang einen Bezeichner generieren zu lassen.

Klassen

Der Exportvorgang konvertiert jede öffentliche Klasse (die das ComVisible (false)-Attribut nicht berücksichtigt) einer Assembly in eine Co-Klasse einer Typbibliothek. Eine exportierte Co-Klasse hat weder Methoden noch Eigenschaften. Sie behält jedoch den Namen der verwalteten Klasse bei und implementiert alle Schnittstellen, die explizit durch die verwaltete Klasse implementiert wurden.

Im folgenden Codebeispiel wird die Definition der IShape-Schnittstelle und der Circle-Klasse gezeigt, die IShape implementiert. Die Darstellung der konvertierten Typbibliothek folgt nach dem Codebeispiel.

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

Typbibliothekdarstellung

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

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

Jede Co-Klasse kann eine andere Schnittstelle implementieren. Diese wird Klassenschnittstelle genannt und kann durch den Exportvorgang automatisch generiert werden. Die Klassenschnittstelle macht alle Methoden und Eigenschaften verfügbar, die in der verwalteten Originalklasse verfügbar sind. Dies ermöglicht COM-Clients darauf zuzugreifen, indem sie einen Aufruf durch die Klassenschnittstelle durchführen.

Sie können der Klasse eine spezifische UUID (Universal Unique Identifier) zuordnen, indem Sie GuidAttribute sofort oberhalb der Definition der verwalteten Klasse anwenden. Während des Konvertierungsvorgangs überträgt der Exportvorgang den Wert, der GuidAttribute bereitgestellt wurde, zur UUID der Typbibliothek. Andernfalls erhält der Exportvorgang UUIDs von einem Hash, das den vollständigen Klassennamen mit dem Namespace enthält. Die Verwendung des vollständigen Namens gewährleistet, dass eine Klasse mit einem bestimmten Namen in einem bestimmten Namespace immer dieselbe UUID generiert. Weiterhin wird gewährleistet, dass zwei Klassen mit verschiedenen Namen niemals dieselbe UUID generieren.

Abstrakte Klassen, nicht öffentliche Klassen und Standardkonstruktoren werden mit dem noncreatable-Typbibliotheksattribut gekennzeichnet. Andere Typbibliotheksattribute wie z. B. licensed, hidden, restricted und control, die bei Co-Klassen angewendet werden, sind nicht festgelegt.

Schnittstellen

Der Exportvorgang konvertiert verwaltete Schnittstellen in COM-Schnittstellen. Diese verfügen über dieselben Methoden und Eigenschaften wie verwaltete Schnittstellen; die Methodensignatur unterscheidet sich jedoch erheblich.

Schnittstellenbezeichner

COM-Schnittstellen enthalten einen Schnittstellenbezeichner (IID, Interface Identifier), um die einzelnen Schnittstellen unterscheiden zu können. Durch Anwendung des GuidAttribute-Attributs können Sie einer beliebigen verwalteten Schnittstelle eine feste IID zuordnen. Wenn Sie dieses Attribut weglassen und keine feste IID zuordnen, wird sie automatisch durch den Exportvorgang während der Konvertierung zugeordnet. Eine durch die Laufzeit zugeordnete IID enthält den Schnittstellennamen (einschließlich Namespace) und die vollständige Signatur von allen Methoden, die in der Schnittstelle definiert sind. Wenn Sie die Methoden für die verwaltete Schnittstelle neu ordnen oder Methodenargument und Rückgabetyp ändern, wird die IID geändert, die dieser Schnittstelle zugeordnet ist. Änderungen des Methodennamens haben keine Auswirkung auf die IID.

Durch Verwendung der durch die Laufzeit implementierten QueryInterface-Methode können COM-Clients sowohl Schnittstellen mit festen IIDs als auch mit IIDs, die durch die Laufzeit zugeordnet wurden, abrufen. IIDs, die durch die Laufzeit generiert wurden, bleiben innerhalb der Metadaten des Typs nicht erhalten.

Schnittstellentypen

Wenn nicht anders angegeben, konvertiert der Exportvorgang alle verwalteten Schnittstellen in duale Schnittstellen einer Typbibliothek. Mithilfe dualer Schnittstellen können COM-Clients zwischen frühem Binden und spätem Binden wählen.

Durch Anwendung des InterfaceTypeAttribute-Attributs bei einer Schnittstelle können Sie wahlweise angeben, ob die Schnittstelle als duale Schnittstelle, als von IUnknown abgeleitete Schnittstelle oder als Dispinterface (eine auf Dispatch beschränkte Schnittstelle) exportiert werden soll. Alle exportierten Schnittstellen werden entweder von IUnknown oder von IDispatch erweitert, unabhängig von der Vererbungshierarchie im verwalteten Code.

Im folgenden Codebeispiel werden die optionalen Werte dargestellt, um den Schnittstellentyp zu steuern. Nach dem Exportieren in eine Typbibliothek erzeugen diese Optionen die in der Darstellung der Typbibliothek gezeigten Werte. Die Darstellung der Typbibliothek folgt nach dem Codebeispiel.

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

Typbibliothekdarstellung

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

Die meisten Schnittstellen sind während des Exportvorgangs mit dem odl-Typbibliotheksattribut und dem oleautomation-Typbibliotheksattribut gekennzeichnet. (Dispinterfaces bilden eine Ausnahme). Duale Schnittstellen sind mit dem dual-Typbibliotheksattribut gekennzeichnet. Eine duale Schnittstelle wird von der IDispatch-Schnittstelle abgeleitet, macht aber für ihre Methoden auch vtable-Slots verfügbar.

Klassenschnittstellen

Eine vollständige Beschreibung der Klassenschnittstelle sowie Empfehlungen zur Verwendung finden Sie unter Einführung in die Klassenschnittstelle. Der Exportvorgang kann diese Schnittstelle automatisch für eine verwaltete Klasse generieren, die nicht über eine im verwalteten Code explizit definierte Schnittstelle verfügt. COM-Clients können nicht direkt auf Klassenmethoden zugreifen.

Im folgenden Beispielcode wird eine Basisklasse und eine abgeleitete Klasse dargestellt. Keine der beiden Klassen implementiert eine explizite Schnittstelle. Der Exportvorgang stellt eine Klassenschnittstelle für beide verwaltete Klassen bereit.

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

Typbibliothekdarstellung

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

Die exportierten Klassen haben folgende Eigenschaften:

  • Jede Klassenschnittstelle behält den Namen der verwalteten Klasse, jedoch mit einem vorangestellten Unterstrich. Wenn ein Konflikt zwischen einem Schnittstellennamen und einem vorher definierten Schnittstellennamen auftritt, wird an diesen Namen ein Unterstrich und eine inkrementelle Nummer angehängt. Der nächste verfügbare Name für _ClassWithClassInterface beispielsweise ist _ClassWithClassInterface_2.

  • Durch den Exportvorgang werden jeweils neue Schnittstellenbezeichner (IID) generiert. Sie können die IID der Klassenschnittstelle nicht explizit festlegen.

  • Standardmäßig werden beide Klassenschnittstellen von den IDispatch-Schnittstellen abgeleitet.

  • Die Schnittstellen verfügen über die Attribute ODL, dual, hidden, nonextensible und oleautomation.

  • Beide Schnittstellen verfügen über alle öffentlichen Member ihrer Basisklasse (System.Object).

  • Sie enthalten weder die privaten Member noch die internen Member der Klasse.

  • Jedem Member wird automatisch eine eindeutige DispId zugeordnet. Die DispIds können durch die Anwendung von DispIdAttribute auf den Klassenmember explizit festgelegt werden.

  • Methodensignaturen werden umgewandelt, um HRESULTs zurückzugeben, und verfügen über [out, retval]-Parameter.

  • Die Eigenschaften und Felder werden umgewandelt in [propget], [propput] und [propputref].

Standardschnittstelle

COM ist so etwas wie eine Standardschnittstelle. Member der Standardschnittstelle werden von spät gebundenen Sprachen wie Visual Basic als Klassenmember behandelt. .NET Framework erfordert keine Standardschnittstelle, da die Klassen selbst über Member verfügen können. Beim Verfügbarmachen von Klassen für COM ist deren Verwendung einfacher, wenn sie über eine Standardschnittstelle verfügen.

Wird eine verwaltete Klasse als Co-Klasse in eine Typbibliothek exportiert, wird normalerweise eine Schnittstelle als Standardschnittstelle für diese Klasse identifiziert. Wenn keine Schnittstelle als Standardschnittstelle in der Typbibliothek identifiziert wird, betrachten die meisten COM-Anwendungen die erste implementierte Schnittstelle als Standardschnittstelle dieser Co-Klasse.

Wie im folgenden Beispielcode gezeigt wird, konvertiert der Exportvorgang eine verwaltete Klasse, die über keine Klassenschnittstelle verfügt, und markiert die erste implementierte Schnittstelle als Standardschnittstelle in der exportierten Typbibliothek. Die Typbibliotheksdarstellung der konvertierten Klasse folgt nach dem Codebeispiel.

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

Typbibliothekdarstellung

coclass ClassWithNoClassInterface {
    [default] IExplicit; 
    IAnother;
}

Beim Exportvorgang wird die Klassenschnittstelle immer als Standardschnittstelle für diese Klasse markiert, unabhängig davon, ob eine andere Schnittstelle explizit durch die Klasse implementiert ist. Das folgende Beispiel zeigt zwei Klassen.

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

Typbibliothekdarstellung

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

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

Werttypen

Werttypen (Typen, die System.Value erweitern) werden in Typbibliotheken als C-Strukturen mit der Typdefinition exportiert. Das Layout der Strukturmember wird durch das StructLayoutAttribute-Attribut gesteuert, das dem Typ zugewiesen ist. Nur die Felder des Werttyps werden exportiert. Wenn ein Werttyp über Methoden verfügt, kann von COM aus nicht auf diese zugegriffen werden.

Beispiel:

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

Im folgenden Beispiel wird der Point-Werttyp als point typedef in COM exportiert:

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

Beachten Sie, dass beim Konvertierungsvorgang die SetXY-Methode von typedef entfernt wird.

Enumerationen

Der Exportvorgang fügt den Typbibliotheken eine verwaltete Enumeration hinzu, bei der die Membernamen verändert wurden, um eine eindeutige Memberbenennung zu gewährleisten. Während des Exportvorgangs wird durch Tlbexp.exe dem Enumerationsnamen jedes Members ein Unterstrich vorangestellt. Dadurch wird die Eindeutigkeit jedes einzelnen Membernamens gewährleistet. In der folgenden einfachen Enumeration beispielsweise wird ein Set von Darstellungen der Typbibliothek erzeugt.

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

Typbibliothekdarstellung

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

Die Laufzeit ordnet die Member von verwalteten Enumerationen den entsprechenden Enumerationen zu. Alle Verweise auf Sunday in der DaysOfWeek-Enumeration aus dem vorhergehenden Beispiel müssen z. B. mit DaysOfWeek gekennzeichnet werden. Sie können nicht auf Sunday an Stelle von DaysOfWeek.Sunday verweisen. COM-Enumerationen erfordern eine eindeutige Memberbenennung.

Siehe auch

Konzepte

Konvertierung von exportierten Assemblys

Konvertieren exportierter Module

Konvertieren exportierter Member

Konvertieren exportierter Parameter

Weitere Ressourcen

Zusammenfassung: Konvertieren einer Assembly in eine Typbibliothek