Определение типов .NET для взаимодействия COM

Предоставление доступа к типам .NET для COM

Если вы планируете предоставлять типы в сборке COM-приложениям, во время разработки необходимо учитывать требования COM-взаимодействия. Управляемые типы (класс, интерфейс, структура и перечисление) легко интегрируются с COM-типами, если следовать приведенным ниже рекомендациям:

  • Классы должны явным образом реализовывать интерфейсы.

    Несмотря на то, что COM-взаимодействие предоставляет механизм для автоматического создания интерфейса, содержащего все члены класса и члены его базового класса, гораздо эффективнее предоставлять явные интерфейсы. Автоматически создаваемый интерфейс называется интерфейсом класса. См. рекомендации в разделе Introducing the class interface (Введение в интерфейс класса).

    В Visual Basic, C# и C++ можно внедрять определения интерфейса в код вместо использования языка IDL или его эквивалента. Дополнительные сведения о синтаксисе см. в документации по соответствующему языку.

  • Управляемые типы должны быть открытыми.

    Регистрация и экспорт в библиотеку типов поддерживаются только для открытых типов. В результате видимыми для модели COM являются только открытые типы.

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

  • Методы, свойства, поля и события должны быть открытыми.

    Члены открытых типов, которые должны быть видимыми для модели COM, также должны быть открытыми. Видимость сборки, открытого типа или открытых членов открытого типа можно ограничить с помощью атрибута ComVisibleAttribute. По умолчанию все открытые типы и члены являются видимыми.

  • Для активации из модели COM типы должны иметь открытый конструктор без параметров.

    Таким образом, видимыми для модели COM являются только управляемые открытые типы. При этом без открытого конструктора без параметров (конструктор без аргументов) COM-клиенты не смогут создавать этот тип. COM-клиенты могут использовать такой тип, если он активирован другими способами.

  • Типы не могут быть абстрактными.

    Ни COM-клиенты, ни клиенты .NET не могут создавать абстрактные типы.

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

Использование типов COM из .NET

Если вы планируете использовать com-типы из .NET и не хотите использовать такие средства, как Tlbimp.exe (импорт библиотек типов), необходимо выполнить следующие рекомендации.

  • Интерфейсы должны применяться ComImportAttribute .
  • Интерфейсы должны применяться GuidAttribute с идентификатором интерфейса для COM-интерфейса.
  • Интерфейсы должны применяться InterfaceTypeAttribute для указания базового типа интерфейса этого интерфейса (IUnknownилиIDispatchIInspectable).
    • Параметр по умолчанию — иметь базовый тип IDispatch и добавить объявленные методы в ожидаемую таблицу виртуальных функций для интерфейса.
    • Только платформа .NET Framework поддерживает указание базового типаIInspectable.

Эти рекомендации предоставляют минимальные требования для распространенных сценариев. Существует множество дополнительных параметров настройки и описано в разделе "Применение атрибутов взаимодействия".

Определение интерфейсов COM в .NET

Когда код .NET пытается вызвать метод в COM-объекте через интерфейс с ComImportAttribute атрибутом, он должен создать виртуальную таблицу функций (также известную как vtable или vftable), чтобы сформировать определение интерфейса .NET для определения вызываемого машинного кода. Этот процесс является сложным. В следующих примерах показаны некоторые простые случаи.

Рассмотрим интерфейс COM с несколькими методами:

struct IComInterface : public IUnknown
{
    STDMETHOD(Method)() = 0;
    STDMETHOD(Method2)() = 0;
};

Для этого интерфейса в следующей таблице описывается макет таблицы виртуальной функции:

IComInterface Слот таблицы виртуальных функций Имя метода
0 IUnknown::QueryInterface
1 IUnknown::AddRef
2 IUnknown::Release
3 IComInterface::Method
4 IComInterface::Method2

Каждый метод добавляется в таблицу виртуальных функций в том порядке, в который он был объявлен. Определенный порядок определяется компилятором C++, но для простых случаев без перегрузки порядок объявления определяет порядок в таблице.

Объявите интерфейс .NET, соответствующий этому интерфейсу, следующим образом:

[ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid(/* The IID for IComInterface */)]
interface IComInterface
{
    void Method();
    void Method2();
}

Указывает InterfaceTypeAttribute базовый интерфейс. Он предоставляет несколько вариантов:

ComInterfaceType Значение Базовый тип интерфейса Поведение членов в интерфейсе атрибутов
InterfaceIsIUnknown IUnknown В таблице виртуальных функций сначала есть члены IUnknown, а затем члены этого интерфейса в порядке объявления.
InterfaceIsIDispatch IDispatch Элементы не добавляются в таблицу виртуальных функций. Они доступны только через IDispatch.
InterfaceIsDual IDispatch В таблице виртуальных функций сначала есть члены IDispatch, а затем члены этого интерфейса в порядке объявления.
InterfaceIsIInspectable IInspectable В таблице виртуальных функций сначала есть члены IInspectable, а затем члены этого интерфейса в порядке объявления. Поддерживается только в платформа .NET Framework.

Наследование интерфейса COM и .NET

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

Генератор источника COM, использующий System.Runtime.InteropServices.Marshalling.GeneratedComInterfaceAttribute атрибут, взаимодействует с наследованием интерфейса, поэтому он ведет себя больше, как ожидалось.

Наследование интерфейса COM в C++

В C++разработчики могут объявлять COM-интерфейсы, производные от других COM-интерфейсов, следующим образом:

struct IComInterface : public IUnknown
{
    STDMETHOD(Method)() = 0;
    STDMETHOD(Method2)() = 0;
};

struct IComInterface2 : public IComInterface
{
    STDMETHOD(Method3)() = 0;
};

Этот стиль объявления регулярно используется в качестве механизма для добавления методов в COM-объекты без изменения существующих интерфейсов, что будет критическим изменением. Этот механизм наследования приводит к следующим макетам таблиц виртуальных функций:

IComInterface Слот таблицы виртуальных функций Имя метода
0 IUnknown::QueryInterface
1 IUnknown::AddRef
2 IUnknown::Release
3 IComInterface::Method
4 IComInterface::Method2
IComInterface2 Слот таблицы виртуальных функций Имя метода
0 IUnknown::QueryInterface
1 IUnknown::AddRef
2 IUnknown::Release
3 IComInterface::Method
4 IComInterface::Method2
5 IComInterface2::Method3

В результате можно легко вызвать метод, определенный IComInterface из объекта IComInterface2*. В частности, вызов метода в базовом интерфейсе не требует вызова для QueryInterface получения указателя на базовый интерфейс. Кроме того, C++ позволяет неявное преобразование из IComInterface2*IComInterface*, которое хорошо определено и позволяет избежать повторного QueryInterface вызова. В результате в C или C++вам никогда не придется вызывать QueryInterface базовый тип, если вы не хотите, что позволит повысить производительность.

Примечание.

Интерфейсы WinRT не следуют этой модели наследования. Они определены для выполнения той же модели, что [ComImport]и модель взаимодействия COM на основе .NET.

Наследование интерфейса с ComImportAttribute

В .NET код C#, который выглядит как наследование интерфейса, фактически не является наследованием интерфейса. Рассмотрим следующий код:

interface I
{
    void Method1();
}
interface J : I
{
    void Method2();
}

Этот код не говорит: "J реализует I". На самом деле код говорит: "любой тип, реализующий J также должен реализовывать I". Это различие приводит к базовому решению проектирования, которое делает наследование интерфейса в ComImportAttributeнеергономическом взаимодействия. Интерфейсы всегда считаются собственными; Базовый список интерфейсов интерфейса не влияет на вычисления, чтобы определить таблицу виртуальных функций для заданного интерфейса .NET.

В результате естественный эквивалент предыдущего примера COM-интерфейса C++ приводит к другому макету таблицы виртуальных функций.

Код C#:

[ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface IComInterface
{
    void Method();
    void Method2();
}

[ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface IComInterface2 : IComInterface
{
    void Method3();
}

Макеты таблиц виртуальных функций:

IComInterface Слот таблицы виртуальных функций Имя метода
0 IUnknown::QueryInterface
1 IUnknown::AddRef
2 IUnknown::Release
3 IComInterface::Method
4 IComInterface::Method2
IComInterface2 Слот таблицы виртуальных функций Имя метода
0 IUnknown::QueryInterface
1 IUnknown::AddRef
2 IUnknown::Release
3 IComInterface2::Method3

Так как эти таблицы виртуальных функций отличаются от примера C++, это приведет к серьезным проблемам во время выполнения. Правильное определение этих интерфейсов в .NET ComImportAttribute следующим образом:

[ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface IComInterface
{
    void Method();
    void Method2();
}

[ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface IComInterface2 : IComInterface
{
    new void Method();
    new void Method2();
    void Method3();
}

На уровне метаданных не реализуетсяIComInterface, но только указывает, IComInterface2 что реализующие IComInterface2 также должны реализовыватьсяIComInterface. Таким образом, каждый метод из базовых типов интерфейса должен быть переобъявлен.

Наследование интерфейса с GeneratedComInterfaceAttribute (.NET 8 и более поздних версий)

Генератор источника COM, инициируемый GeneratedComInterfaceAttribute реализацией наследования интерфейса C# в качестве наследования COM-интерфейса, поэтому таблицы виртуальных функций выкладываются должным образом. Если вы используете предыдущий пример, правильное определение этих интерфейсов в .NET System.Runtime.InteropServices.Marshalling.GeneratedComInterfaceAttribute выглядит следующим образом:

[GeneratedComInterface]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface IComInterface
{
    void Method();
    void Method2();
}

[GeneratedComInterface]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface IComInterface2 : IComInterface
{
    void Method3();
}

Методы базовых интерфейсов не должны быть переобъявлены и не должны быть переобъявлены. В следующей таблице описаны полученные таблицы виртуальных функций:

IComInterface Слот таблицы виртуальных функций Имя метода
0 IUnknown::QueryInterface
1 IUnknown::AddRef
2 IUnknown::Release
3 IComInterface::Method
4 IComInterface::Method2
IComInterface2 Слот таблицы виртуальных функций Имя метода
0 IUnknown::QueryInterface
1 IUnknown::AddRef
2 IUnknown::Release
3 IComInterface::Method
4 IComInterface::Method2
5 IComInterface2::Method3

Как видно, эти таблицы соответствуют примеру C++, поэтому эти интерфейсы будут работать правильно.

См. также