Определение типов .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
илиIDispatch
IInspectable
).- Параметр по умолчанию — иметь базовый тип
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++, поэтому эти интерфейсы будут работать правильно.