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


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

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

Встроенная среда выполнения .NET (не созданная из источника), система взаимодействия COM только для Windows создает заглушку IL — поток инструкций IL, которые JIT-ed — во время выполнения, чтобы упростить переход с управляемого кода на 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.StringMarshallingCustomType Свойства GeneratedComInterfaceAttribute.StringMarshalling применяются ко всем параметрам и типам возвращаемых типов 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. Интерфейсы с InterfaceTypeAttribute значением, отличным InterfaceIsIUnknown от того, которые не поддерживаются в исходном коде COM. Любые интерфейсы без InterfaceTypeAttribute предполагается наследовать.IUnknown Это отличается от встроенного COM, где используется InterfaceIsDualзначение по умолчанию.

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

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

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

  • [In] и [Out] атрибуты разрешены только в массивах. [In, Out] Если [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 реализации.

См. также