다음을 통해 공유


COM 호출 가능 래핑

COM 클라이언트가 .NET 개체를 호출할 때 공용 언어 런타임은 관리되는 개체와 개체에 대한 COM 호출 가능 래퍼(CCW)를 만듭니다. .NET 개체를 직접 참조할 수 없으므로 COM 클라이언트는 CCW를 관리되는 개체의 프록시로 사용합니다.

런타임은 서비스를 요청하는 COM 클라이언트 수에 관계없이 관리되는 개체에 대해 정확히 하나의 CCW를 만듭니다. 다음 그림과 같이 여러 COM 클라이언트는 INew 인터페이스를 노출하는 CCW에 대한 참조를 보유할 수 있습니다. 이와 같이, CCW는 인터페이스를 구현하는 관리되는 개체에 대한 단일 참조를 보유하며, 해당 개체는 가비지 수집됩니다. COM 및 .NET 클라이언트는 모두 동일한 관리되는 개체에서 동시에 요청을 수행할 수 있습니다.

INew을 노출하는 CCW에 대한 참조를 보유하는 여러 개의 COM 클라이언트입니다.

COM 호출 가능 래퍼는 .NET 런타임 내에서 실행되는 다른 클래스에 표시되지 않습니다. 주요 목적은 관리 코드와 비관리 코드 간의 호출을 마샬링하는 것입니다. 그러나 CCW는 래핑하는 관리되는 개체의 개체 ID 및 개체 수명도 관리합니다.

개체 ID

런타임은 가비지 수집 힙에서 .NET 객체를 위한 메모리를 할당하여 런타임이 필요에 따라 메모리 내에서 객체를 효율적으로 이동할 수 있도록 합니다. 반면, 런타임은 비수집 힙에서 CCW에 대한 메모리를 할당하여 COM 클라이언트가 래퍼를 직접 참조할 수 있도록 합니다.

개체 수명

래핑하는 .NET 클라이언트와 달리 CCW는 기존 COM 방식으로 참조 계산됩니다. CCW의 참조 수가 0에 도달하면 래퍼는 관리되는 개체에 대한 참조를 해제합니다. 남은 참조가 없는 관리되는 개체는 다음 가비지 수집 주기 동안 수집됩니다.

COM 인터페이스 시뮬레이션

CCW는 COM의 인터페이스 기반 상호 작용 적용과 일치하는 방식으로 모든 공용 COM 표시 인터페이스, 데이터 형식 및 반환 값을 COM 클라이언트에 노출합니다. COM 클라이언트의 경우 .NET 개체에서 메서드를 호출하는 것은 COM 개체에서 메서드를 호출하는 것과 동일합니다.

이 원활한 접근 방식을 만들기 위해 CCW는 IUnknownIDispatch와 같은 기존 COM 인터페이스를 제조합니다. 다음 그림과 같이 CCW는 래핑하는 .NET 개체에 대한 단일 참조를 유지 관리합니다. COM 클라이언트와 .NET 개체는 CCW의 프록시 및 스텁 생성을 통해 서로 상호 작용합니다.

CCW가 COM 인터페이스를 제조하는 방법을 보여 주는 다이어그램

.NET 런타임은 관리되는 환경에서 클래스에 의해 명시적으로 구현된 인터페이스를 노출하는 것 외에도 개체를 대신하여 다음 표에 나열된 COM 인터페이스의 구현을 제공합니다. .NET 클래스는 이러한 인터페이스의 자체 구현을 제공하여 기본 동작을 재정의할 수 있습니다. 그러나 런타임은 항상 IUnknownIDispatch 인터페이스에 대한 구현을 제공합니다.

인터페이스 설명
IDispatch 타입에 대한 지연 바인딩을 위한 메커니즘을 제공합니다.
IErrorInfo 오류, 해당 원본, 도움말 파일, 도움말 컨텍스트 및 오류를 정의한 인터페이스의 GUID에 대한 텍스트 설명을 제공합니다(.NET 클래스의 경우 항상 GUID_NULL ).
IProvideClassInfo COM 클라이언트가 관리되는 클래스에 의해 구현된 ITypeInfo 인터페이스에 액세스할 수 있도록 합니다. .NET Core는 COM에서 가져오지 않은 형식에 대해 COR_E_NOTSUPPORTED을 반환합니다.
ISupportErrorInfo COM 클라이언트가 관리되는 개체가 IErrorInfo 인터페이스를 지원하는지 여부를 확인할 수 있도록 합니다. 이 경우 클라이언트가 최신 예외 개체에 대한 포인터를 가져올 수 있습니다. 모든 관리되는 형식은 IErrorInfo 인터페이스를 지원합니다.
ITypeInfo (.NET Framework에만 해당) Tlbexp.exe생성한 형식 정보와 정확히 동일한 클래스에 대한 형식 정보를 제공합니다.
IUnknown COM 클라이언트가 CCW의 수명을 관리하고 형식 강제 변환을 제공하는 IUnknown 인터페이스의 표준 구현을 제공합니다.

관리되는 클래스는 다음 표에 설명된 COM 인터페이스를 제공할 수도 있습니다.

인터페이스 설명
(_classname) 클래스 인터페이스 런타임에 의해 노출되고 명시적으로 정의되지 않은 인터페이스는 관리되는 개체에 명시적으로 노출되는 모든 공용 인터페이스, 메서드, 속성 및 필드를 노출합니다.
IConnectionPointIConnectionPointContainer 대리자 기반 이벤트(이벤트 구독자를 등록하기 위한 인터페이스)를 원본으로 하는 개체에 대한 인터페이스입니다.
IDispatchEx (.NET Framework에만 해당) 클래스가 IExpando를 구현하는 경우 런타임에서 제공하는 인터페이스입니다. IDispatchEx 인터페이스는 IDispatch와 달리 멤버의 열거, 추가, 삭제 및 대/소문자 구분 호출을 가능하게 하는 IDispatch 인터페이스의 확장입니다.
IEnumVARIANT 클래스가 IEnumerable을 구현하는 경우 컬렉션의 개체를 열거하는 컬렉션 형식 클래스의 인터페이스입니다.

클래스 인터페이스 소개

관리 코드에서 명시적으로 정의되지 않은 클래스 인터페이스는 .NET 개체에 명시적으로 노출되는 모든 공용 메서드, 속성, 필드 및 이벤트를 노출하는 인터페이스입니다. 이 인터페이스는 이중 또는 디스패치 전용 인터페이스일 수 있습니다. 클래스 인터페이스는 밑줄 앞에 .NET 클래스 자체의 이름을 받습니다. 예를 들어 클래스 포유동물의 경우 클래스 인터페이스는 _Mammal.

파생 클래스의 경우 클래스 인터페이스는 기본 클래스의 모든 공용 메서드, 속성 및 필드를 노출합니다. 파생 클래스는 각 기본 클래스에 대한 클래스 인터페이스도 노출합니다. 예를 들어 클래스 포유동물이 System.Object를 확장하는 클래스 MammalSuperclass를 확장하는 경우 .NET 개체는 COM 클라이언트에 _Mammal, _MammalSuperclass 및 _Object라는 세 개의 클래스 인터페이스를 노출합니다.

예를 들어 다음 .NET 클래스를 고려합니다.

' Applies the ClassInterfaceAttribute to set the interface to dual.
<ClassInterface(ClassInterfaceType.AutoDual)> _
' Implicitly extends System.Object.
Public Class Mammal
    Sub Eat()
    Sub Breathe()
    Sub Sleep()
End Class
// Applies the ClassInterfaceAttribute to set the interface to dual.
[ClassInterface(ClassInterfaceType.AutoDual)]
// Implicitly extends System.Object.
public class Mammal
{
    public void Eat() {}
    public void Breathe() {}
    public void Sleep() {}
}

COM 클라이언트는 이름이 지정된 _Mammal클래스 인터페이스에 대한 포인터를 가져올 수 있습니다. .NET Framework에서 형식 라이브러리 내보내기(Tlbexp.exe) 도구를 사용하여 인터페이스 정의가 포함된 형식 라이브러리를 _Mammal 생성할 수 있습니다. 형식 라이브러리 내보내기는 .NET Core에서 지원되지 않습니다. 클래스가 Mammal 하나 이상의 인터페이스를 구현하는 경우 인터페이스가 coclass 아래에 표시됩니다.

[odl, uuid(…), hidden, dual, nonextensible, oleautomation]
interface _Mammal : IDispatch
{
    [id(0x00000000), propget] HRESULT ToString([out, retval] BSTR*
        pRetVal);
    [id(0x60020001)] HRESULT Equals([in] VARIANT obj, [out, retval]
        VARIANT_BOOL* pRetVal);
    [id(0x60020002)] HRESULT GetHashCode([out, retval] short* pRetVal);
    [id(0x60020003)] HRESULT GetType([out, retval] _Type** pRetVal);
    [id(0x6002000d)] HRESULT Eat();
    [id(0x6002000e)] HRESULT Breathe();
    [id(0x6002000f)] HRESULT Sleep();
}
[uuid(…)]
coclass Mammal
{
    [default] interface _Mammal;
}

클래스 인터페이스 생성은 선택 사항입니다. 기본적으로 COM interop은 형식 라이브러리로 내보내는 각 클래스에 대한 디스패치 전용 인터페이스를 생성합니다. 클래스에 적용하여 이 인터페이스의 자동 생성을 ClassInterfaceAttribute 방지하거나 수정할 수 있습니다. 클래스 인터페이스는 관리되는 클래스를 COM에 노출하는 작업을 용이하게 할 수 있지만 해당 용도는 제한적입니다.

주의

클래스 인터페이스를 사용하여 직접 명시적으로 정의하는 대신 관리되는 클래스의 향후 버전 관리를 복잡하게 만들 수 있습니다. 클래스 인터페이스를 사용하기 전에 다음 지침을 읽어 보세요.

클래스 인터페이스를 생성하는 대신 COM 클라이언트에서 사용할 명시적 인터페이스를 정의합니다.

COM interop은 클래스 인터페이스를 자동으로 생성하므로 클래스의 사후 버전 변경으로 공용 언어 런타임에서 노출되는 클래스 인터페이스의 레이아웃이 변경될 수 있습니다. COM 클라이언트는 일반적으로 인터페이스 레이아웃의 변경 내용을 처리할 준비가 되지 않으므로 클래스의 멤버 레이아웃을 변경하면 중단됩니다.

이 지침은 COM 클라이언트에 노출되는 인터페이스가 변경되지 않아야 한다는 개념을 강화합니다. 실수로 인터페이스 레이아웃의 순서를 변경하여 COM 클라이언트가 손상되는 위험을 줄이려면 인터페이스를 명시적으로 정의하여 인터페이스 레이아웃에서 클래스의 모든 변경 내용을 격리합니다.

다음 코드 조각과 같이 ClassInterfaceAttribute 를 사용하여 클래스 인터페이스의 자동 생성을 해제하고 클래스에 대한 명시적 인터페이스를 구현합니다.

<ClassInterface(ClassInterfaceType.None)>Public Class LoanApp
    Implements IExplicit
    Sub M() Implements IExplicit.M
…
End Class
[ClassInterface(ClassInterfaceType.None)]
public class LoanApp : IExplicit
{
    int IExplicit.M() { return 0; }
}

ClassInterfaceType.None 값은 클래스 메타데이터를 형식 라이브러리로 내보낼 때 클래스 인터페이스가 생성되지 않도록 합니다. 앞의 예제에서 COM 클라이언트는 인터페이스를 통해서만 클래스에 LoanAppIExplicit 액세스할 수 있습니다.

디스패치 식별자(DispIds)를 캐싱하지 않도록 하십시오

클래스 인터페이스를 사용하는 것은 스크립티드 클라이언트, Microsoft Visual Basic 6.0 클라이언트 또는 인터페이스 멤버의 DispId를 캐시하지 않는 런타임에 바인딩된 클라이언트에 사용할 수 있는 옵션입니다. DispIds는 인터페이스 멤버를 식별하여 지연 바인딩을 사용하도록 설정합니다.

클래스 인터페이스의 경우 DispId 생성은 인터페이스에서 멤버의 위치를 기반으로 합니다. 멤버의 순서를 변경하고 클래스를 형식 라이브러리로 내보내는 경우 클래스 인터페이스에서 생성된 DispId를 변경합니다.

클래스 인터페이스를 사용할 때 런타임에 바인딩된 COM 클라이언트가 중단되지 않도록 하려면 ClassInterfaceAttributeClassInterfaceType.AutoDispatch 값으로 적용합니다. 이 값은 디스패치 전용 클래스 인터페이스를 구현하지만 형식 라이브러리에서 인터페이스 설명을 생략합니다. 인터페이스 설명이 없으면 클라이언트는 컴파일 시간에 DispId를 캐시할 수 없습니다. 클래스 인터페이스의 기본 인터페이스 형식이지만 특성 값을 명시적으로 적용할 수 있습니다.

<ClassInterface(ClassInterfaceType.AutoDispatch)> Public Class LoanApp
    Implements IAnother
    Sub M() Implements IAnother.M
…
End Class
[ClassInterface(ClassInterfaceType.AutoDispatch)]
public class LoanApp
{
    public int M() { return 0; }
}

런타임에 인터페이스 멤버의 DispId를 가져오기 위해 COM 클라이언트는 IDispatch.GetIdsOfNames를 호출할 수 있습니다. 인터페이스에서 메서드를 호출하려면 반환된 DispId를 IDispatch.Invoke에 인수로 전달합니다.

클래스 인터페이스에 이중 인터페이스 옵션을 사용하여 제한합니다.

이중 인터페이스를 사용하면 COM 클라이언트에서 인터페이스 멤버에 대한 초기 및 지연 바인딩을 사용할 수 있습니다. 디자인 타임 및 테스트 중에 클래스 인터페이스를 이중으로 설정하는 것이 유용할 수 있습니다. 수정되지 않는 관리되는 클래스(및 해당 기본 클래스)의 경우 이 옵션도 허용됩니다. 다른 모든 경우에서는 클래스 인터페이스를 이중으로 설정하지 마세요.

드물게 자동으로 생성된 이중 인터페이스가 적절할 수 있습니다. 그러나 버전 관련 복잡성이 더 자주 발생합니다. 예를 들어 파생 클래스의 클래스 인터페이스를 사용하는 COM 클라이언트는 기본 클래스의 변경 내용으로 쉽게 중단될 수 있습니다. 타사에서 기본 클래스를 제공하면 클래스 인터페이스의 레이아웃이 제어할 수 없습니다. 또한 디스패치 전용 인터페이스와 달리 이중 인터페이스(ClassInterfaceType.AutoDual)는 내보낸 형식 라이브러리의 클래스 인터페이스에 대한 설명을 제공합니다. 지연 바인딩된 클라이언트가 컴파일 타임에 DispId를 캐시하도록 장려하는 이러한 설명입니다.

모든 COM 이벤트 알림이 지연 바인딩되어 있는지 확인합니다.

기본적으로 COM 형식 정보는 관리되는 어셈블리에 직접 포함되므로 PIA(기본 interop 어셈블리)가 필요하지 않습니다. 그러나 포함된 형식 정보의 제한 사항 중 하나는 초기 바인딩된 vtable 호출에 의한 COM 이벤트 알림 배달을 지원하지 않지만 런타임에 바인딩된 IDispatch::Invoke 호출만 지원한다는 것입니다.

애플리케이션에서 COM 이벤트 인터페이스 메서드를 초기 바인딩 호출해야 하는 경우, Visual Studio에서 Embed Interop Types 속성을 true로 설정하거나 프로젝트 파일에 다음 요소를 포함할 수 있습니다.

<EmbedInteropTypes>True</EmbedInteropTypes>

참고하십시오