Если вы намерены предоставить типы в сборке 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 (импорт библиотек типов), необходимо выполнить следующие рекомендации.
Для интерфейсов COM необходимо применить GuidAttribute с идентификатором интерфейса.
Интерфейсам должен быть применён InterfaceTypeAttribute для указания базового типа этого интерфейса (IUnknown, IDispatch или IInspectable).
Параметр по умолчанию — иметь базовый тип IDispatch и добавить объявленные методы в ожидаемую таблицу виртуальных функций для интерфейса.
Только платформа .NET Framework поддерживает указание базового типа IInspectable.
Эти рекомендации предоставляют минимальные требования для распространенных сценариев. Существует множество дополнительных параметров настройки и описано в разделе "Применение атрибутов взаимодействия".
Определение интерфейсов COM в .NET
Когда код .NET пытается вызвать метод в COM-объекте через интерфейс с ComImportAttribute атрибутом, он должен создать виртуальную таблицу функций (также известную как vtable или vftable), чтобы сформировать определение интерфейса .NET для определения вызываемого машинного кода. Этот процесс является сложным. В следующих примерах показаны некоторые простые случаи.
Для этого интерфейса в следующей таблице описывается макет таблицы виртуальной функции:
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 */)]
interfaceIComInterface
{
voidMethod();
voidMethod2();
}
InterfaceTypeAttribute определяет базовый интерфейс. Он предоставляет несколько вариантов:
В таблице виртуальных функций сначала содержатся члены 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-интерфейсов, следующим образом:
Этот стиль объявления регулярно используется в качестве механизма для добавления методов в 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.
В .NET код C#, который выглядит как наследование интерфейса, фактически не является наследованием интерфейса. Рассмотрим следующий код:
C#
interfaceI
{
voidMethod1();
}
interfaceJ : I
{
voidMethod2();
}
Этот код не говорит: "J реализует I". На самом деле код говорит: "любой тип, реализующий J, также должен реализовывать I". Это различие приводит к базовому решению проектирования, которое делает наследование интерфейса на основе ComImportAttribute неэргономичным во взаимодействии. Интерфейсы всегда рассматриваются самостоятельно; базовый список интерфейсов не влияет на определение таблицы виртуальных функций для конкретного интерфейса .NET.
В результате естественный эквивалент предыдущего примера COM-интерфейса C++ приводит к другому макету таблицы виртуальных функций.
Так как эти таблицы виртуальных функций отличаются от примера C++, это приведет к серьезным проблемам во время выполнения. Правильное определение этих интерфейсов в .NET ComImportAttribute определяется следующим образом:
На уровне метаданных IComInterface2 не реализуется IComInterface, но только указывает, что реализующие IComInterface2 также должны реализовать IComInterface. Таким образом, каждый метод из базовых типов интерфейса должен быть переобъявлен.
Наследование интерфейса с GeneratedComInterfaceAttribute (.NET 8 и более поздние версии)
Генератор исходного кода COM, активируемый GeneratedComInterfaceAttribute, реализует наследование интерфейсов C# как наследование интерфейсов COM, поэтому таблицы виртуальных функций располагаются ожидаемым образом. Если вы используете предыдущий пример, правильное определение этих интерфейсов в .NET System.Runtime.InteropServices.Marshalling.GeneratedComInterfaceAttribute выглядит следующим образом:
Методы базовых интерфейсов не должны быть переобъявлены и не должны быть переобъявлены. В следующей таблице описаны полученные таблицы виртуальных функций:
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++, поэтому эти интерфейсы будут работать правильно.
Источник этого содержимого можно найти на GitHub, где также можно создавать и просматривать проблемы и запросы на вытягивание. Дополнительные сведения см. в нашем руководстве для участников.
Отзыв о
.NET
.NET
— это проект с открытым исходным кодом. Выберите ссылку, чтобы оставить отзыв:
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.
Продемонстрировать основы безопасности данных, управления жизненным циклом, информационной безопасности и соответствия требованиям для защиты развертывания Microsoft 365.