Преобразование экспортированного типа

В данном разделе описывается преобразование при экспорте следующих типов:

  • Классы

  • интерфейсов,

  • типы значений;

  • Перечисления

Обычно экспортированные типы сохраняют имя, которое они имели в сборке, но без обозначения пространства имен, связанного с управляемым именем. Например, в следующем примере тип A.B.IList преобразуется в тип IList в экспортированной библиотеке типов. COM-клиент может ссылаться на тип как на IList вместо A.B.IList.

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

При таком подходе не исключены конфликты имен типов в сборке, поскольку имена типов, находящиеся в разных пространствах имен, могут быть одинаковыми. Если подобный конфликт обнаруживается при экспорте, пространство имен сохраняется, чтобы исключить путаницу в именовании. В следующем примере кода показаны два пространства имен с одинаковым именем типа.

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

В следующем представлении библиотеки типов демонстрируется преобразование каждого имени типа. Кроме того, поскольку использование точек в именах библиотек типов не допускается, процедура экспорта заменяет каждую точку подчеркиванием.

Представление библиотеки типов

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

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

Процедура экспорта также автоматически создает программный идентификатор (ProgId), объединяя пространство имен и имя типа. Например, идентификатором ProgId, который создан для управляемого класса LinkedList и показан в предыдущем примере, является A.B.LinkedList.

Объединение пространства имен и имени типа может создать неправильный идентификатор ProgId. Размер идентификатора ProgId ограничен 39 знаками, идентификатор не может содержать каких-либо знаков препинания, кроме точек. Чтобы избежать этих ограничений, разработчик может задать идентификатор ProgId в своем коде, применяя атрибут ProgIdAttribute, вместо того, чтобы позволить генерировать этот идентификатор процедуре экспорта.

Классы

Процедура экспорта преобразует каждый открытый класс (для которого отсутствует атрибут ComVisible (false)) в сборке в компонентный класс в библиотеке типов. В экспортированном компонентном классе отсутствуют методы и свойства, но он сохраняет имя управляемого класса и реализует все интерфейсы, явным образом реализованные в управляемом классе.

В следующем примере кода демонстрируется определение интерфейса IShape и класса Circle, реализующего интерфейс IShape. Пример кода сопровождается представлением преобразованной библиотеки типов.

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

Представление библиотеки типов

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

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

Каждый компонентный класс может реализовать один дополнительный интерфейс, называемый интерфейсом класса, который может быть автоматически создан процедурой экспорта. Интерфейс класса предоставляет доступ ко всем методам и свойствам, доступным в исходном управляемом классе, благодаря чему COM-клиенты могут обращаться к этим методам и свойствам через интерфейс класса.

Можно присвоить классу универсальный уникальный идентификатор (UUID), применяя атрибут GuidAttribute непосредственно над определением управляемого класса. Во время преобразования процедура экспорта переносит значение атрибута GuidAttribute в идентификатор UUID в библиотеке типов. В противном случае процедура экспорта получает идентификаторы UUID из хэша, содержащего полное имя класса, включая пространство имен. Использование полного имени гарантирует, что класс с заданным именем в заданном пространстве имен всегда создает один и тот е идентификатор UUID, в то время как два класса с разными именами никогда не создадут одинаковый идентификатор UUID.

Абстрактные классы и классы без открытых, заданных по умолчанию конструкторов помечаются атрибутом noncreatable библиотеки типов. Другие атрибуты библиотеки типов, применяемые к компонентным классам, такие как licensed, hidden, restricted и control, не задаются.

Интерфейсы

Процедура экспорта преобразует управляемые интерфейсы в COM-интерфейсы с теми же методами и свойствами, что и у управляемого интерфейса, однако сигнатуры методов существенно различаются.

Удостоверения интерфейсов

COM-интерфейсы содержат идентификатор интерфейса (IID), позволяющий отличить один интерфейс от другого. Любому управляемому интерфейсу можно присвоить фиксированный идентификатор IID, применив атрибут GuidAttribute. Если пропустить этот атрибут, не присвоив фиксированный идентификатор IID, процедура экспорта автоматически выполнит присвоение во время преобразования. Идентификатор IID, присвоенный во время выполнения, состоит из имени интерфейса (включая пространство имен) и полной сигнатуры всех методов, определенных в этом интерфейсе. Реорганизация методов управляемого интерфейса или изменение аргументов и возвращаемых типов метода приводит к изменению идентификатора IID, присвоенного данному интерфейсу. Изменение имени метода не влияет на IID.

С помощью метода QueryInterface, реализованного средой времени выполнения, COM-клиенты могут получить интерфейс с фиксированным или присвоенным во время выполнения идентификатором IID. Идентификаторы IID, созданные средой времени выполнения, не сохраняются в метаданных для данного типа.

Типы интерфейсов

Если не задано иное, процедура экспорта преобразует все управляемые интерфейсы в сдвоенные интерфейсы в библиотеке типов. Сдвоенные интерфейсы позволяют COM-клиентам использовать раннее или позднее связывание на выбор.

Можно применить к интерфейсу атрибут InterfaceTypeAttribute, чтобы по своему выбору указать, как следует экспортировать этот интерфейс: как сдвоенный интерфейс, производный интерфейс от IUnknown или диспетчерский интерфейс. Все экспортированные интерфейсы являются непосредственным расширением или IUnknown, или IDispatch, независимо от иерархии наследования в управляемом коде.

В следующем примере кода показываются необязательные значения для управления типом интерфейса. После экспорта в библиотеку типов эти параметры обеспечивают результаты, показанные в представлении библиотеки типов, приведенной после примера кода.

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

Представление библиотеки типов

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

Большинство интерфейсов во время экспорта помечается атрибутами odl и oleautomation библиотеки типов. (Исключение составляют диспетчерские интерфейсы.) Сдвоенные интерфейсы помечаются атрибутом dual библиотеки типов. Сдвоенный интерфейс является производным от интерфейса IDispatch, но также предоставляет ячейки vtable для своих методов.

Интерфейсы классов

Полное описание интерфейса класса и рекомендации по его использованию см. в разделе Введение в интерфейс классов. Этот интерфейс может быть создан автоматически от имени управляемого класса процедурой экспорта без явного определения в управляемом коде. COM-клиенты не могут получать доступ к методам класса напрямую.

В следующем примере кода показаны базовый и производный классы. Ни один из классов не реализует интерфейс явным образом. Процедура экспорта создает интерфейс класса для обоих управляемых классов.

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

Представление библиотеки типов

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

Интерфейсы экспортированных классов обладают следующими характеристиками.

  • Каждый интерфейс класса сохраняет имя управляемого класса, но с префиксом в виде знака подчеркивания. Если имя интерфейса конфликтует с ранее определенным именем интерфейса, к новому имени добавляется знак подчеркивания и порядковый номер. Например, следующим доступным именем для интерфейса _ClassWithClassInterface является _ClassWithClassInterface_2.

  • Процедура экспорта всегда создает новые идентификаторы интерфейса (IID). Идентификатор IID интерфейса класса не может быть задан явным образом.

  • По умолчанию оба интерфейса класса являются производными от интерфейсов IDispatch.

  • Для этих интерфейсов установлены атрибуты ODL, dual, hidden, nonextensible и oleautomation.

  • Оба интерфейса содержат все открытые члены соответствующего базового класса (System.Object).

  • Они не содержат закрытых или внутренних членов класса.

  • Каждому члену автоматически присваивается уникальный идентификатор DispId. Идентификаторы DispIds можно установить явно, применяя атрибут DispIdAttribute к члену класса.

  • Сигнатуры метода преобразуются для возвращения значений HRESULT и имеют параметры [out, retval].

  • Свойства и поля преобразуются в [propget], [propput] и [propputref].

Интерфейс по умолчанию

В COM используется понятие интерфейса по умолчанию. Члены интерфейса по умолчанию воспринимаются языками с поздним связыванием (например Visual Basic) как члены класса. В среде .NET Framework отсутствует необходимость использовать интерфейс по умолчанию, поскольку члены могут быть у самих классов. Тем не менее, классы, предоставляемые для COM, гораздо проще использовать, если в них предусмотрен интерфейс по умолчанию.

Когда управляемый класс экспортируется в библиотеку типов как компонентный класс, один из интерфейсов обычно определяется как интерфейс по умолчанию для класса. Если в библиотеке типов ни один интерфейс не определен как интерфейс по умолчанию, большинство COM-приложений воспринимает первый реализованный интерфейс как интерфейс по умолчанию для компонентного класса.

Как показано в следующих примерах кода, процедура экспорта преобразует управляемый класс, у которого отсутствует интерфейс класса, и помечает первый реализованный интерфейс как интерфейс по умолчанию в экспортированной библиотеке типов. Пример кода сопровождается представлением библиотеки типов преобразованного класса.

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

Представление библиотеки типов

coclass ClassWithNoClassInterface {
    [default] IExplicit; 
    IAnother;
}

Процедура экспорта всегда помечает интерфейс класса как интерфейс по умолчанию для класса, независимо от наличия других интерфейсов, явно реализуемых классом. В следующем примере показаны два класса.

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

Представление библиотеки типов

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

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

Типы значений

Типы значений (типы, расширяющие System.Value) экспортируются в библиотеки типов как структуры в стиле языка C с определением типа. Компоновка членов структуры управляется атрибутом StructLayoutAttribute, применяемым к типу. Экспортируются только поля типа значения. Если у типа значения есть методы, они недоступны из COM.

Пример.

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

Тип значения Point экспортируется в COM как определение типа point, как показано в следующем примере:

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

Обратите внимание на то, что процедура преобразования удаляет метод SetXY из определения типа.

Перечисления

Процедура экспорта добавляет управляемое перечисление в библиотеки типов, изменяя имена членов для обеспечения их уникальности. Чтобы гарантировать уникальность каждого имени члена, программа Tlbexp.exe во время экспорта добавляет для каждого члена префикс в виде имени перечисления и знака подчеркивания. Например, следующее простое перечисление преобразуется в набор представлений библиотеки типов.

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

Представление библиотеки типов

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

Среда времени выполнения ограничивает область действия членов управляемого перечисления перечислением, к которому они относятся. Например, все ссылки на Sunday в перечислении DaysOfWeek, показанном в предыдущем примере, должны содержать DaysOfWeek. Нельзя ссылаться на Sunday вместо DaysOfWeek.Sunday. Уникальное именование членов является обязательным для COM-перечислений.

См. также

Основные понятия

Преобразование экспортированной сборки

Преобразование экспортированного модуля

Преобразование экспортированных членов

Преобразование экспортированного параметра

Другие ресурсы

Резюме преобразования сборки в библиотеку типов