Бөлісу құралы:


Создание источника для ComWrappers

.NET 8 представляет генератор источников, который создает реализацию API ComWrappers для вас. Генератор распознает GeneratedComInterfaceAttribute.

Встроенная в среду выполнения .NET (а не создание из исходного кода) система взаимодействия COM, доступная только для Windows, генерирует IL-заглушку — поток инструкций IL, которые компилируются во время выполнения с использованием технологии JIT — чтобы облегчить переход от управляемого кода к COM и наоборот. Так как заглушка IL создается во время выполнения, она несовместима с NativeAOT и IL обрезкой. Генерация заглушек во время выполнения также может затруднить диагностику проблем маршаллинга.

Встроенное взаимодействие использует такие атрибуты, как ComImport или DllImport, которые зависят от создания кода во время выполнения. В приведенном ниже коде показан соответствующий пример:

[ComImport]
interface IFoo
{
    void Method(int i);
}

[DllImport("MyComObjectProvider")]
static nint GetPointerToComInterface(); // C definition - IUnknown* GetPointerToComInterface();

[DllImport("MyComObjectProvider")]
static void GivePointerToComInterface(nint comObject); // C definition - void GivePointerToComInterface(IUnknown* pUnk);

// Use the system to create a Runtime Callable Wrapper to use in managed code
nint ptr = GetPointerToComInterface();
IFoo foo = (IFoo)Marshal.GetObjectForIUnknown(ptr);
foo.Method(0);
...
// Use the system to create a COM Callable Wrapper to pass to unmanaged code
IFoo foo = GetManagedIFoo();
nint ptr = Marshal.GetIUnknownForObject(foo);
GivePointerToComInterface(ptr);

ComWrappers API позволяет взаимодействовать с COM в C# без использования встроенной COM-системы, но требует значительного объема шаблонного и небезопасного рукописного кода. Генератор интерфейсов COM автоматизирует этот процесс и делает ComWrappers таким же простым, как встроенный COM, но предоставляет его в удобном для обрезки и совместимом с AOT виде.

Базовое использование

Чтобы использовать генератор COM-интерфейса, добавьте атрибуты GeneratedComInterfaceAttribute и GuidAttribute в определение интерфейса, который вы хотите импортировать из или предоставить для COM. Тип должен быть помечен partial и иметь видимость internal или public, чтобы сгенерированный код мог получить к нему доступ.

[GeneratedComInterface]
[Guid("3faca0d2-e7f1-4e9c-82a6-404fd6e0aab8")]
internal partial interface IFoo
{
    void Method(int i);
}

Затем, чтобы предоставить класс, реализующий интерфейс для COM, добавьте GeneratedComClassAttribute к классу реализации. Этот класс также должен быть partial и либо internal либо public.

[GeneratedComClass]
internal partial class Foo : IFoo
{
    public void Method(int i)
    {
        // Do things
    }
}

Во время компиляции генератор создает реализацию API ComWrappers, и вы можете использовать тип StrategyBasedComWrappers или пользовательский производный тип для работы с интерфейсом COM.

[LibraryImport("MyComObjectProvider")]
private static partial nint GetPointerToComInterface(); // C definition - IUnknown* GetPointerToComInterface();

[LibraryImport("MyComObjectProvider")]
private static partial void GivePointerToComInterface(nint comObject); // C definition - void GivePointerToComInterface(IUnknown* pUnk);

// Use the ComWrappers API to create a Runtime Callable Wrapper to use in managed code
ComWrappers cw = new StrategyBasedComWrappers();
nint ptr = GetPointerToComInterface();
IFoo foo = (IFoo)cw.GetOrCreateObjectForComInstance(ptr, CreateObjectFlags.None);
foo.Method(0);
...
// Use the system to create a COM Callable Wrapper to pass to unmanaged code
ComWrappers cw = new StrategyBasedComWrappers();
Foo foo = new();
nint ptr = cw.GetOrCreateComInterfaceForObject(foo, CreateComInterfaceFlags.None);
GivePointerToComInterface(ptr);

Настройка управления маршаллингом

Генератор com-интерфейса учитывает MarshalUsingAttribute атрибут и некоторые виды использования атрибута MarshalAsAttribute для настройки маршаллинга параметров. Дополнительные сведения см. в статье о настройке генерируемого источником маршалинга с использованием атрибута MarshalUsing и настройке маршалинга параметров с использованием атрибута MarshalAs. Свойства GeneratedComInterfaceAttribute.StringMarshalling и GeneratedComInterfaceAttribute.StringMarshallingCustomType применяются ко всем параметрам и возвращаемым типам типа string в интерфейсе, если они не имеют других атрибутов маршаллинга.

Неявные HRESULTs и PreserveSig

Методы COM в C# имеют другую сигнатуру, отличную от собственных методов. Стандартный COM имеет тип возвращаемого значения HRESULT, 4 байтового целочисленного типа, представляющего состояния ошибок и успешности. Это HRESULT возвращаемое значение по умолчанию скрыто в сигнатуре C# и преобразуется в исключение при возврате значения ошибки. Последний out-параметр собственной сигнатуры COM может быть по желанию преобразован в возвращаемое значение в сигнатуре C#.

Например, в следующих фрагментах показаны сигнатуры методов C# и соответствующая нативная сигнатура, которую определяет генератор.

void Method1(int i);

int Method2(float i);
HRESULT Method1(int i);

HRESULT Method2(float i, _Out_ int* returnValue);

Если вы хотите обработать HRESULT самостоятельно, вы можете использовать PreserveSigAttribute на методе, чтобы указать генератору не выполнять это преобразование. В следующих фрагментах кода показано, какую исходную сигнатуру ожидает генератор при применении [PreserveSig]. Методы COM должны возвращать HRESULT, поэтому возвращаемое значение любого метода PreserveSig должно быть int.

[PreserveSig]
int Method1(int i, out int j);

[PreserveSig]
int Method2(float i);
HRESULT Method1(int i, int* j);

HRESULT Method2(float i);

Дополнительные сведения см. в разделе Неявные переводы подписей методов в взаимодействии .NET.

Несовместимость и различия со встроенным COM-интерфейсом

Только IUnknown

Единственной поддерживаемой базой интерфейса является IUnknown. Интерфейсы со значением, отличным от InterfaceIsIUnknown, не поддерживаются в сгенерированном COM-коде. Любые интерфейсы без InterfaceTypeAttribute предполагается наследовать от IUnknown. Это отличается от встроенного COM, где используется InterfaceIsDualзначение по умолчанию.

Маршаллирование по умолчанию и поддержка

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

  • Во встроенной системе COM все типы имеют неявный атрибут [In], за исключением массивов управляемых элементов, которые имеют неявный атрибут [In, Out]. В сгенерированном из исходного кода COM, все типы, включая массивы элементарных типов данных, имеют [In] семантику.

  • [In] и [Out] атрибуты разрешены только в массивах. Если требуется поведение [Out] или [In, Out] для других типов, используйте модификаторы параметров in и out.

Производные интерфейсы

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

[ComImport]
[Guid("3faca0d2-e7f1-4e9c-82a6-404fd6e0aab8")]
interface IBase
{
    void Method1(int i);
    void Method2(float i);
}

[ComImport]
[Guid("3faca0d2-e7f1-4e9c-82a6-404fd6e0aab8")]
interface IDerived : IBase
{
    new void Method1(int i);
    new void Method2(float f);
    void Method3(long l);
    void Method4(double d);
}

Генератор интерфейсов COM не ожидает тени базовых методов. Чтобы создать метод, наследуемый от другого, просто укажите базовый интерфейс как базовый интерфейс C# и добавьте методы производного интерфейса. Дополнительные сведения см. в документации по проектированию.

[GeneratedComInterface]
[Guid("3faca0d2-e7f1-4e9c-82a6-404fd6e0aab8")]
interface IBase
{
    void Method1(int i);
    void Method2(float i);
}

[GeneratedComInterface]
[Guid("3faca0d2-e7f1-4e9c-82a6-404fd6e0aab8")]
interface IDerived : IBase
{
    void Method3(long l);
    void Method4(double d);
}

Обратите внимание, что интерфейс с GeneratedComInterface атрибутом может наследоваться только от одного базового интерфейса, имеющего GeneratedComInterface атрибут.

Производные интерфейсы через границы сборки

В .NET 8 не поддерживается определение интерфейса с GeneratedComInterfaceAttribute атрибутом, производным от GeneratedComInterfaceинтерфейса-атрибута, определенного в другой сборке.

В .NET 9 и более поздних версиях этот сценарий поддерживается со следующими ограничениями:

  • Базовый тип интерфейса должен быть скомпилирован для той же целевой платформы, что и производный тип.
  • Базовый тип интерфейса не должен скрывать членов своего базового интерфейса, если таковой имеется.

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

Примечание.

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

Маршал API

Некоторые API в Marshal несовместимы с COM, генерируемыми на основе кода. Замените эти методы на соответствующие методы в реализации ComWrappers.

См. также