Прочитать на английском

Поделиться через


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

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

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

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

    Хотя COM-взаимодействие предоставляет механизм автоматического создания интерфейса, содержащего все члены класса и членов его базового класса, гораздо лучше предоставить явные интерфейсы. Автоматически созданный интерфейс называется интерфейсом класса. Рекомендации см. в разделе "Общие сведения об интерфейсе класса".

    Вы можете использовать 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.
  • Для интерфейсов COM необходимо применить GuidAttribute с идентификатором интерфейса.
  • Интерфейсам должен быть применён InterfaceTypeAttribute для указания базового типа этого интерфейса (IUnknown, IDispatch или IInspectable).
    • Параметр по умолчанию — иметь базовый тип IDispatch и добавить объявленные методы в ожидаемую таблицу виртуальных функций для интерфейса.
    • Только платформа .NET Framework поддерживает указание базового типа IInspectable.

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

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

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

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

C++
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, соответствующий этому интерфейсу, следующим образом:

C#
[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-интерфейсов, следующим образом:

C++
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 не следуют этой модели наследования. Они спроектированы так, чтобы следовать той же модели взаимодействия, что и модель COM, основанная на .NET.

Наследование интерфейса с помощью ComImportAttribute

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

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

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

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

Код 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 определяется следующим образом:

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

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

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

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

C#
[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++, поэтому эти интерфейсы будут работать правильно.

См. также


Дополнительные ресурсы

Обучение

Модуль

Design Resilient Code with Interfaces - Training

In this module, you explore advanced concepts of interfaces in C#. You learn how to implement explicit interface members, combine multiple interfaces, and reduce code dependencies using interfaces.

Сертификация

Сертифицированный корпорацией Майкрософт: помощник администратора по защите информации и соответствию требованиям - Certifications

Продемонстрировать основы безопасности данных, управления жизненным циклом, информационной безопасности и соответствия требованиям для защиты развертывания Microsoft 365.