상호 운용을 위한 COM 구성 요소 빌드
업데이트: 2007년 11월
나중에 COM 기반 응용 프로그램을 작성하려는 경우에는 관리 코드와 효율적으로 상호 운용되도록 코드를 디자인할 수 있습니다. 고급 계획을 통해 비관리 코드에서 관리 코드로의 마이그레이션을 단순하게 할 수도 있습니다.
다음의 권장 사항은 관리 코드와 상호 작용하는 COM 형식을 작성하기 위한 최선의 구현 방법을 요약한 것입니다.
형식 라이브러리 제공
대부분의 경우 공용 언어 런타임에서는 COM 형식을 비롯한 모든 형식에 대해 메타데이터를 필요로 합니다. Windows SDK(소프트웨어 개발 키트)에 포함된 형식 라이브러리 가져오기(Tlbimp.exe)에서는 COM 형식 라이브러리를 .NET Framework 메타데이터로 변환할 수 있습니다. 형식 라이브러리를 메타데이터로 변환하고 나면 관리되는 클라이언트에서 쉽게 COM 형식을 호출할 수 있습니다. 사용을 쉽게 하기 위해 형식 라이브러리에는 항상 형식 정보를 제공합니다.
형식 라이브러리는 별도의 파일로 만들거나 .dll, .exe 또는 .ocx 파일 내에 리소스로 포함시킬 수 있습니다. 또한 메타데이터를 직접 생성할 수도 있습니다. 이렇게 하면 게시자의 키 쌍을 사용하여 메타데이터에 서명할 수 있습니다. 키를 사용하여 서명된 메타데이터는 한정적인 소스를 가지며 호출자가 잘못된 키를 가지고 있을 때 바인딩을 방지함으로써 보안을 향상시킬 수 있습니다.
형식 라이브러리 등록
호출을 올바르게 마샬링하기 위해 런타임에서 특정 형식을 기술하는 형식 라이브러리를 찾아야 하는 경우가 있습니다. 런타임에서 형식 라이브러리를 보려면 런타임에 바인딩하는 경우를 제외하고는 형식 라이브러리가 먼저 등록되어 있어야 합니다.
regkind 플래그를 REGKIND_REGISTER로 설정하고 Microsoft Win32 API LoadTypeLibEx 함수를 호출하면 형식 라이브러리를 등록할 수 있습니다. Regsvr32.exe에서는 .dll 파일 내에 포함된 형식 라이브러리를 자동으로 등록합니다.
가변 길이 배열 대신 안전 배열 사용
COM 안전 배열은 자체 설명됩니다. 런타임 마샬러에서는 런타임에 안전 배열을 검사함으로써 배열의 차수, 크기 및 경계와 배열 내용의 형식을 확인할 수 있습니다. 가변 길이(또는 C 스타일) 배열에는 이와 같은 자체 설명 기능이 없습니다. 예를 들어, 다음의 관리되지 않는 메서드 시그니처에서는 배열 매개 변수에 대해 요소 형식 이외의 정보를 제공하지 않습니다.
HRESULT DoSomething(int cb, [in] byte buf[]);
사실, 배열은 참조로 전달되는 다른 매개 변수와 구별할 수 없습니다. 따라서 Tlbimp.exe에서는 DoSomething 메서드의 배열 매개 변수를 변환하지 않습니다. 대신 배열은 다음 코드에서 보여 주는 것처럼 Byte 형식에 대한 참조로 나타납니다.
Public Sub DoSomething(cb As Integer, ByRef buf As Byte)
public void DoSomething(int cb, ref Byte buf);
상호 운용을 향상시키기 위해 관리되지 않는 메서드 시그니처에서 인수를 SAFEARRAY로 입력할 수 있습니다. 예를 들면 다음과 같습니다.
HRESULT DoSomething(SAFEARRAY(byte)buf);
Tlbimp.exe에서는 SAFEARRAY를 다음의 관리되는 배열 형식으로 변환합니다.
Public Sub DoSomething(buf As Byte())
public void DoSomething(Byte[] buf);
자동화 규격 데이터 형식 사용
런타임 마샬링 서비스에서는 모든 자동화 규격 데이터 형식을 자동으로 지원합니다. 규격에 맞지 않는 형식은 지원되지 않을 수도 있습니다.
형식 라이브러리에 버전 및 로캘 제공
형식 라이브러리를 가져올 때는 형식 라이브러리의 버전 및 로캘 정보도 어셈블리에 전파됩니다. 그러면 관리되는 클라이언트에서는 어셈블리의 특정 버전 또는 로캘에 바인딩하거나 어셈블리의 최근 버전에 바인딩할 수 있습니다. 형식 라이브러리에 버전 정보를 제공하면 클라이언트에서는 사용할 어셈블리를 정확하게 선택할 수 있습니다.
Blittable 형식 사용
데이터 형식은 blittable이거나 blittable이 아닙니다. blittable 형식은 interop 경계 간에 공통되는 표현을 가집니다. 정수 및 부동 소수점 형식은 blittable입니다. blittable 형식의 배열 및 구조체도 blittable입니다. 문자열, 날짜 및 개체는 마샬링 프로세스 도중 변환되는 비 blittable 형식의 예입니다.
interop 마샬링 서비스에서는 blittable 형식과 비 blittable 형식을 모두 지원하지만, 마샬링 도중 변환되어야 하는 형식은 blittable 형식만큼 효율적이지 않습니다. 비 blittable이 아닌 형식을 사용할 때는 해당 형식을 마샬링하는 데 추가 오버헤드가 발생한다는 것을 알고 있어야 합니다.
문자열은 특히 문제가 일어날 가능성이 큽니다. 관리되는 문자열은 유니코드 문자로 저장되므로 유니코드 문자 인수를 기대하는 비관리 코드에 훨씬 더 효율적으로 마샬링될 수 있습니다. 따라서 ANSI 문자로 구성된 문자열은 되도록 사용하지 않는 것이 좋습니다.
IProvideClassInfo 구현
관리되지 않는 인터페이스를 관리 코드로 마샬링할 때 런타임에서는 특정 형식의 래퍼를 만듭니다. 메서드 시그니처는 대개 인터페이스의 형식을 나타내지만 해당 인터페이스를 구현하는 개체의 형식은 알 수 없는 경우가 있습니다. 개체 형식을 알 수 없는 경우 런타임에서는 형식 고유 래퍼보다는 기능이 떨어지지만 일반적인 COM 개체 래퍼를 사용하여 인터페이스를 래핑합니다.
예를 들어, 다음의 COM 메서드 시그니처를 참조하십시오.
interface INeedSomethng {
HRESULT DoSomething(IBiz *pibiz);
}
이 메서드는 가져올 때 다음과 같이 변환됩니다.
Interface INeedSomething
Sub DoSomething(pibiz As IBiz)
End Interface
interface INeedSomething {
void DoSomething(IBiz pibiz);
}
INeedSomething 인터페이스를 구현하는 관리되는 개체를 IBiz 인터페이스에 전달하는 경우, interop 마샬러에서는 초기에 IBiz를 관리 코드에 정의할 때 특정 형식의 개체 래퍼를 사용하여 인터페이스를 래핑하려고 시도합니다. 올바른 래퍼 형식을 식별하려면 마샬러에서 해당 인터페이스를 구현하는 개체의 형식을 알아야 합니다. 마샬러에서 개체 형식을 확인하기 위해 사용하는 한 가지 방법은 IProvideClassInfo 인터페이스를 쿼리하는 것입니다. 개체가 IProvideClassInfo를 구현하는 경우 마샬러에서는 개체 형식을 확인하고 형식화된 래퍼에서 해당 인터페이스를 래핑합니다.
모듈식 호출 사용
관리 코드와 비관리 코드 간의 데이터 마샬링에는 비용이 많이 듭니다. 경계 간 전환을 줄이면 이러한 비용을 줄일 수 있습니다. 전환 횟수를 최소화하는 인터페이스는 일반적으로 경계를 자주 넘으며 경계를 넘을 때마다 적은 수의 작업을 수행하는 인터페이스보다 성능이 우수합니다.
실패 HRESULT를 최소한으로 사용
관리되는 클라이언트에서 COM 개체를 호출할 때 런타임에서는 COM 개체의 실패 HRESULT를 마샬러에서 호출 반환 시 throw하는 예외에 매핑합니다. 관리되는 예외 모델은 예외가 아닌 경우에 대해 최적화되어 있으며 예외가 발생하지 않을 때 예외를 catch하는 데 오버헤드가 거의 발생하지 않습니다. 반면, 예외가 발생하는 경우에는 예외를 catch하는 데 많은 오버헤드가 발생할 수 있습니다.
예외는 꼭 필요한 경우에만 사용하고 알림 목적으로 실패 HRESULT를 반환하지 않도록 합니다. 실패 HRESULT는 예외 상황에 사용할 수 있도록 예약합니다. 실패 HRESULT를 지나치게 사용하면 성능에 영향을 줄 수 있다는 것을 기억해야 합니다.
외부 리소스를 명시적으로 해제
일부 개체는 작동될 때 외부 리소스를 사용합니다. 예를 들어, 데이터베이스 연결에서는 레코드 집합을 업데이트할 수 있습니다. 일반적으로 개체는 작동 기간 동안 외부 리소스를 계속 사용하지만 명시적 해제를 통해 해당 리소스를 즉시 반환할 수 있습니다. 예를 들어, 클래스 소멸자에서 또는 IUnknown.Release를 사용하여 파일을 닫는 대신 파일 개체에 대해 Close 메서드를 사용할 수 있습니다. 코드에 Close 메서드에 해당하는 기능을 제공하면 해당 파일 개체가 계속 존재하더라도 외부 파일 리소스를 해제할 수 있습니다.
관리되지 않는 형식을 재정의하지 않기
기존의 COM 인터페이스를 관리 코드에 구현하는 올바른 방법은 먼저 Tlbimp.exe 또는 같은 기능의 AIP를 사용하여 인터페이스 정의를 가져오는 것입니다. 결과로 생성된 메타데이터에서는 해당 COM 인터페이스에 대해 IID 및 DispId 등이 동일한 호환 가능 정의를 제공합니다.
관리 코드에서 COM 인터페이스를 직접 재정의하지 않도록 합니다. 이 작업에는 시간이 많이 걸리며 기존의 COM 인터페이스와 호환되는 관리되는 개체는 거의 생성되지 않습니다. 대신 Tlbimp.exe를 사용하여 정의 호환성을 유지합니다.
성공 HRESULT를 사용하지 않기
예외를 catch하는 것은 관리되는 응용 프로그램에서 오류 상황을 처리하는 가장 일반적인 방법입니다. COM 형식의 사용을 투명하게 하기 위해 런타임에서는 COM 메서드가 실패 HRESULT를 반환할 때마다 자동으로 예외를 throw합니다.
COM 개체가 성공 HRESULT를 반환하는 경우 런타임에서는 retval 매개 변수에 있는 값을 반환합니다. 기본적으로 HRESULT는 삭제되므로 관리되는 클라이언트에서 성공 HRESULT의 값을 검사하기가 매우 어려워집니다. PreserveSigAttribute 특성을 사용하여 HRESULT를 유지할 수도 있지만 이 프로세스에는 많은 작업이 필요합니다. Tlbimp.exe 또는 같은 기능의 API를 사용하여 생성된 어셈블리에 이 특성을 직접 추가해야 합니다.
따라서 가능하면 성공 HRESULT를 사용하지 않는 것이 좋습니다. 대신 Out 매개 변수를 통해 호출 상태에 대한 정보를 반환할 수 있습니다.
모듈 함수 사용하지 않기
형식 라이브러리에는 모듈에 대해 정의된 함수가 포함될 수 있습니다. 일반적으로 이러한 함수를 사용하면 DLL 진입점에 대한 형식 정보를 제공할 수 있습니다. 그러나 Tlbimp.exe에서는 이러한 함수를 가져오지 않습니다.
기본 인터페이스에 System.Object의 멤버를 사용하지 않기
관리되는 클라이언트와 COM coclass는 런타임에서 제공하는 래퍼의 도움으로 상호 작용합니다. COM 형식을 가져올 때 변환 프로세스에서는 coclass의 기본 인터페이스에 대한 모든 메서드를 System.Object 클래스에서 파생된 래퍼 클래스에 추가합니다. System.Object의 멤버와 이름 충돌이 발생하지 않도록 기본 인터페이스 멤버의 이름을 지정합니다. 충돌이 발생하면 가져온 메서드가 기본 클래스 메서드를 재정의합니다.
기본 인터페이스의 메서드와 System.Object의 메서드가 동일한 기능을 제공하는 경우에는 이것이 문제가 되지 않지만, 기본 인터페이스의 메서드가 의도하지 않은 방식으로 사용되는 경우에는 문제가 될 수 있습니다. 이름 충돌을 방지하려면 기본 인터페이스에는 Object, Equals, Finalize, GetHashCode, GetType, MemberwiseClone, ToString 등의 이름을 사용하지 않습니다.