다음을 통해 공유


ComWrappers의 원본 생성

.NET 8에는 ComWrappers API의 구현을 만드는 원본 생성기가 도입되었습니다. 생성기는 GeneratedComInterfaceAttribute를 인식합니다.

.NET 런타임의 기본 제공(소스 생성이 아닌) Windows 전용 COM interop 시스템은 런타임에 JIT-ed인 IL 명령 스트림인 IL 스텁을 생성하여 관리 코드에서 COM으로의 전환을 용이하게 하고 그 반대의 경우도 마찬가지입니다. 이 IL 스텁은 런타임에 생성되므로 NativeAOTIL 트리밍과 호환되지 않습니다. 런타임에 스텁을 생성하면 마샬링 문제를 진단하기가 어려울 수 있습니다.

기본 제공 interop은 런타임에 코드 생성에 의존하는 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 인터페이스 생성기를 사용하려면 COM에서 가져오거나 COM에 노출하려는 인터페이스 정의에 GeneratedComInterfaceAttribute 특성 및 GuidAttribute 특성을 추가합니다. 형식은 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
    }
}

컴파일 시간에 생성기는 ComWrappers API의 구현을 만들고 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.StringMarshallingGeneratedComInterfaceAttribute.StringMarshallingCustomType 속성은 다른 마샬링 속성이 없는 경우 인터페이스의 모든 매개 변수 및 반환 유형 string에 적용됩니다.

암시적 HRESULT 및 PreserveSig

C#의 COM 메서드에는 네이티브 메서드와 다른 서명이 있습니다. 표준 COM에는 오류 및 성공 상태를 나타내는 4바이트 정수 형식의 HRESULT 반환 형식이 있습니다. 이 HRESULT 반환 값은 기본적으로 C# 서명에서 숨겨지고 오류 값이 반환되면 예외로 변환됩니다. 네이티브 COM 서명의 마지막 "out" 매개 변수는 필요에 따라 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 interop의 암시적 메서드 서명 변환을 참조하세요.

기본 제공 COM의 비호환성 및 차이점

IUnknown에만 해당

지원되는 유일한 인터페이스 기반은 IUnknown입니다. InterfaceIsIUnknown 이외의 값이 있는 InterfaceTypeAttribute의 인터페이스는 소스 생성 COM에서 지원되지 않습니다. InterfaceTypeAttribute가 없는 모든 인터페이스는 IUnknown에서 파생된 것으로 간주됩니다. 이는 기본값이 InterfaceIsDual인 기본 제공 COM과 다릅니다.

마샬링 기본값 및 지원

원본 생성 COM에는 기본 제공 COM과는 다른 기본 마샬링 동작이 있습니다.

  • 기본 제공 COM 시스템에서는 암시적 [In, Out] 특성이 있는 blittable 요소의 배열을 제외하고 모든 형식에 암시적 [In] 특성이 있습니다. 원본에서 생성된 COM에서 blittable 요소 배열을 비롯한 모든 형식에는 [In] 의미 체계가 있습니다.

  • [In][Out] 특성은 배열에서만 허용됩니다. 다른 형식에서 [Out] 또는 [In, Out] 동작이 필요한 경우 inout 매개 변수 한정자를 사용합니다.

파생 인터페이스

기본 제공 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에서는 다른 어셈블리에 정의된 -attributed 인터페이스 GeneratedComInterfaceAttribute 에서 GeneratedComInterface파생되는 특성으로 인터페이스를 정의하는 것이 지원되지 않습니다.

.NET 9 이상 버전에서 이 시나리오는 다음과 같은 제한 사항으로 지원됩니다.

  • 기본 인터페이스 형식은 파생 형식과 동일한 대상 프레임워크를 대상으로 컴파일되어야 합니다.
  • 기본 인터페이스 형식이 있는 경우 기본 인터페이스의 멤버를 숨겨서는 안 됩니다.

또한 다른 어셈블리에 정의된 기본 인터페이스 체인에서 생성된 가상 메서드 오프셋에 대한 변경 내용은 프로젝트가 다시 작성될 때까지 파생된 인터페이스에서 고려되지 않습니다.

참고 항목

.NET 9 이상 버전에서는 생성된 COM 인터페이스를 어셈블리 경계를 넘어 상속하여 이 기능 사용의 제한 사항과 문제를 알려줄 때 경고가 내보내집니다. 이 경고를 사용하지 않도록 설정하여 제한 사항을 인정하고 어셈블리 경계를 넘어 상속할 수 있습니다.

마샬링 API

Marshal의 일부 API는 소스 생성 COM과 호환되지 않습니다. 이러한 메서드를 ComWrappers 구현에서 해당 메서드로 대체합니다.

참고 항목