컴포넌트 오브젝트 모델

컴포넌트 오브젝트 모델(Component Object Model, COM)은 마이크로소프트가 개발한 소프트웨어 구성 요소들의 응용 프로그램 이진 인터페이스이다. 마이크로소프트가 1993년에 소개하였다. COM을 이용해 개발된 프로그램들은 프로세스간 통신과 동적 오브젝트 생성이 가능하다. 소프트웨어 개발에서는 COM이라는 용어를 종종 OLE, OLE 자동화, ActiveX, COM+, DCOM 기술을 포함하는 포괄적 개념으로 사용한다. COM이 소개된 건 1993년이지만, 마이크로소프트는 1997년이 되어서야 COM이라는 이름을 강조하기 시작했다.
다양한 플랫폼에서 COM이 구현되었지만, 주로 마이크로소프트 윈도에서 사용된다. COM은 닷넷 프레임워크와 같은 다른 기술로 대체되리라 전망된다.

역사

COM 인터페이스를 만드는 데 관여한 매우 주목할 만한 인물의 하나인 앤서니 윌리엄스(Anthony Williams)는 소프트웨어 컴포넌트 개념들을 포함하고 있는 내부 문서인 〈오브젝트 설계: Unknown 다루기(Object Architecture: Dealing With the Unknown)〉 (또는 〈동적 확장이 가능한 클래스에서의 형식 안전성(Type Safety in a Dynamically Extensible Class)〉)를 1988년에, 〈상속: 상속은 무엇을 의미하며 어떻게 사용하는가(On Inheritance: What It Means and How To Use It)〉를 1990년에 각각 마이크로소프트에 발표하였다. 이 문서들은 COM의 기본을 이루는 데 사용됐던 많은 아이디어들에 대한 토대를 제공하였다.
이러한 많은 아이디어들로부터 1990년 마이크로소프트의 첫 번째 오브젝트 기반 프레임워크인 OLE(Object Linking and Embedding)이 탄생되었다. OLE는 동적 데이터 교환(DDE) 위에 구축되었으며, 특히 복합 문서(Compound Document)를 위해 디자인되었다. OLE는 1991년 워드와 엑셀을 통해 소개되었고, 이후 1992년 윈도 버전 3.1부터 운영 체제에 포함되었다. 복합 문서의 예로는 워드 문서에 포함된 스프레드시트를 들 수 있는데, 이 경우 엑셀에서 스프레드시트를 변경하면 해당 변경사항은 워드 문서 안에 자동으로 반영된다.
1991년 마이크로소프트는 비주얼 베이직 1.0을 통해 비주얼 베이직 확장(Visual Basic Extensions 또는 VBX)를 소개하였다.
1993년 마이크로소프트는 OLE 2와 OLE 2 오브젝트 모델의 기초가 되는 COM을 발표하였다. OLE 1이 주로 복합 문서에 초점을 맞췄다면, COM과 OLE 2는 일반화된 소프트웨어 컴포넌트에 초점을 맞춰 디자인되었다. 1994년에는 VBX 컨트롤의 후속 버전인 OLE 컨트롤(OCX)가 소개되었다. 이 시기 마이크로소프트는 OLE 2를 단지 "OLE"로 지칭하면서, OLE가 더 이상 약어가 아닌 모든 회사의 컴포넌트 기술에 사용될 수 있는 하나의 이름이라고 명시하였다.
1996년 초 마이크로소프트는 인터넷 관련 OLE의 일부를 ActiveX로 이름을 바꿨고, 마이크로소프트 오피스에 사용되는 복합 문서 기술을 제외한 모든 OLE 기술들을 ActiveX로 차츰 이름을 바꿔나갔다. 이듬해 마이크로소프트는 CORBA에 대한 대응으로 DCOM을 소개하였다.

관련 기술

COM은 윈도의 주요 소프트웨어 개발 인터페이스였기 때문에 다수의 지원되는 기술들에 영향을 미쳤다.

COM+

COM+는 COM의 중요한 확장으로서 윈도 2000을 통해 소개되었다. 동시에 마이크로소프트는 DCOM을 별개의 다른 기술로서 강조하지 않았다. 트랜잭션적인 COM 컴포넌트들은 이전 윈도 NT4 환경에서는 마이크로소프트 트랜잭션 서버 애플리케이션 인터페이스를 통해 배치되었지만, 현재는 COM+의 추가된 레이어에서 직접 다루어진다. COM+ 컴포넌트들은 현재 컴포넌트 서비스 애플리케이션 인터페이스를 통해 추가된다.
COM+의 이점 중 하나는 컴포넌트 팜(farm)에서 실행될 수 있다는 점이다. 제대로 작성된 컴포넌트는 한 번 메모리에 올려지면 초기화 루틴을 다시 호출하는 것만으로도 재사용될 수 있다. 컴포넌트는 또한 분산될 수 있는데(즉 다른 기계에서 호출될 수 있는데), 이는 이전에는 DCOM에서만 가능했었다.

.NET

COM 플랫폼은 닷넷 프레임워크에 많은 지위를 빼앗겼다. 그리고 현재 마이크로소프트는 .NET의 마케팅에 중점을 맞추고 있다. .NET의 입장에서 현재 COM은 어느 정도까지는 사용을 피해야 하는 기술이 되었다. 그럼에도 불구하고 COM은 COM을 기반으로 한 인기있는 3D 렌더링 SDK인 다이렉트X 같은 중요한 소프트웨어 기술로 인해 여전히 필수적인 기술로 남아 있다. 또한 COM 컴포넌트는 동일한 기능을 하는 관리되는 .NET 컴포넌트보다 이론적으로 항상 더 좋은 성능을 갖는다. 2013년 현재 마이크로소프트가 COM 또는 COM의 지원을 중단할 계획은 알려진 바가 없다.
트렌잭션 및 큐된 컴포넌트 같은 COM+가 제공하는 여러 서비스들은 엔터프라이즈 .NET 애플리케이션에서는 여전히 중요하다.
COM과 .NET 사이에는 제한된 상호 호환성이 제공된다. COM 오브젝트는 런타임 호출 가능 래퍼(runtime callable wrapper; RCW)의 구현을 통해 .NET에서 사용될 수 있다. COM 호환을 위한 인터페이스 제약을 따르는 .NET 오브젝트는 COM 호출 가능 래퍼(COM callable wrapper; CCW)를 통해 COM 오브젝트에서 사용될 수 있다. COM 및 .NET에서 상대 기술로 제작된 오브젝트들은 일반적인 오브젝트처럼 보이게 된다.
.NET의 원격(remoting) 모델은 COM의 원격 실행에 있는 많은 결점들을 해결하며, 오브젝트들이 프로세스 또는 기계의 경계를 넘어 참조 또는 값 형식으로 투명하게 통제될 수 있도록 허용한다.

기술 세부 사항

COM 프로그래머는 COM 컴포넌트들을 사용하여 소프트웨어를 개발한다. 각각의 COM 컴포넌트들은 클래스 아이디(CLSIDs)로 식별하는데, 클래스 아이디는 전역 고유 식별자(GUID) 형식으로 되어 있다. COM 컴포넌트의 기능은 하나 이상의 인터페이스를 통해 노출된다. 컴포넌트에서 지원하는 인터페이스들은 인터페이스 아이디(IIDs)로 식별하는데, 이것 역시 GUID이다.
COM 인터페이스는 C, C++, 비주얼 베이직과 같은 프로그래밍 언어 및 윈도 플랫폼에서 구현된 여러 종류의 스크립트 언어와 바인딩된다. 컴포넌트에 대한 접근은 모두 인터페이스의 메서드를 통해 이루어지는데, 이를 통해 프로세스간(inter-process) 또는 심지어 컴퓨터간(inter-computer) 프로그래밍(후자는 DCOM을 사용한다.) 같은 기법이 허용된다.

클래스

COM에서 클래스는 coclass라고 불린다.
coclass는 하나 이상의 인터페이스를 구체적으로 구현한 것이다. coclass는 COM 컴포넌트 개발을 지원하는 모든 언어(예를 들어 C++나 비주얼 베이직 등)로 작성할 수 있다.
윈도 개발에 대해 COM이 한 주요 기여 중 하나는 인터페이스를 구현으로부터 분리한다는 개념을 도입했다는 점이다. COM에서는 객체를 인터페이스를 통하지 않고 직접 접근할 수 없기 때문에 반드시 인터페이스와 구현이 분리되어야 한다. 이와 함께 COM은 단일 인터페이스 다중 구현을 지원한다. 단일 인터페이스 다중 구현 개념은 애플리케이션이 실행 중에 인터페이스에 대한 여러 구현 중 하나를 선택하여 인터페이스를 인스턴스화 할 수 있다는 것을 의미한다.

인터페이스 정의 언어와 타입 라이브러리

타입 라이브러리는 COM 타입들을 설명하는 메타데이터를 포함한다. 이러한 COM 타입들에 대한 설명은 인터페이스 정의 언어(Interface Definition Language, IDL)를 통해 작성되며 컴파일 될 수 있도록 텍스트 파일로 저장된다.
COM 컴포넌트 개발에서 IDL을 사용하여 먼저 타입들을 정의하면서 시작하는 건 흔히 있는 일이다. IDL 파일은 개발자가 어떠한 프로그래밍 언어와도 관련되지 않는 방법으로 객체 지향적인 클래스, 인터페이스, 구조체, 열거형 및 다른 사용자 정의 타입들을 정의할 수 있도록 해준다.
IDL 파일은 MIDL 컴파일러에서 타입 라이브러리(.TLB 파일)로 컴파일된다. 이러한 타입 라이브러리에 저장된 바이너리 메타데이터는 VB, VC++, 델파이 등과 같은 다양한 프로그래밍 언어로 처리하게 된다. 이와 같은 처리의 결과로 각각의 언어 컴파일러는 자신의 언어에 맞는 형식(VB를 위한 VB 클래스, VC++를 위한 클래스, 구조체, 매크로 및 typedef 등)을 만들게 되고, 이런 형식들은 .TLB 파일(궁극적으로는 원래 정의된 IDL 파일)에 정의된 coclass를 나타내게 된다.

오브젝트 프레임워크로서의 COM

COM의 기반이 되는 개념들은 객체 지향성에 그 뿌리를 두고 있다. COM은 프로그램을 객체 지향적으로 개발하고 배치하기 위한 인터페이스이다.
COM은 런타임 프레임워크이기 때문에 실행 중에 타입들을 개별적으로 식별하거나 지정할 수 있다. 이를 위해 전역 고유 식별자(GUID)가 사용된다. 각각의 COM 타입에는 실행 시 식별 될 수 있도록 GUID가 할당된다.
COM 타입에 대한 정보를 컴파일시 및 실행시에 접근할 수 있게 하기 위해, COM은 타입 라이브러리를 소개하고 있다. 타입 라이브러리의 효과적인 사용을 통해 COM은 오브젝트 사이의 상호 동작을 지원하는 동적 프레임워크로서의 기능을 수행한다.
IDL에서의 다음과 같은 coclass 정의 예제를 살펴 보자 :
coclass MyObject
{
  [default] interface IMyObject;
  [default, source] dispinterface _IMyObjectEvents;
};
위의 코드는 MyObject라는 이름의 COM 클래스를 선언하고 있다. 이 클래스는 IMyObject라는 이름의 인터페이스를 구현하며, _IMyObjectEvents 이름의 이벤트 인터페이스를 지원한다.
이벤트 인터페이스 부분을 무시하면, 이것은 다음과 같은 C++ 클래스 정의와 개념적으로 동일하다 :
class CSomeObject : public ISomeInterface{
  ...
  ...
  ...};
위의 코드에서 ISomeInterface는 C++ 가상 클래스이다.
이제 다시 MyObject COM 클래스를 보자 : MyObject를 위한 coclass가 IDL에 정의되고, 그것으로부터 타입 라이브러리가 컴파일되었다면, 각각의 언어 컴파일러는 이러한 타입 라이브러리를 읽고 적절히 해석하여, 개발자가 COM이 이해할 수 있는 coclass인 MyObject를 구현하고 궁극적으로 바이너리 실행 코드를 생성할 수 있도록 필요한 모든 코드를 (각각의 언어에 적합한 형태로) 생성해야 할 의무가 있다.
COM coclass가 구현되고 시스템에서 사용할 수 있게 되었다면, 이제 이를 어떻게 인스턴스화 할 지의 문제가 뒤따르게 된다. 현재 C++ 같은 언어에서는, CoCreateInstance() API를 인스턴스화 할 coclass의 CLSID (CLSID_MyObject)와 해당 coclass에서 사용하려는 인터페이스 IID (IID_IMyObject)를 지정하여 사용할 수 있다. CoCreateInstance()를 호출하는 다음과 같은 코드는 :
CoCreateInstance(
  CLSID_MyObject,
  NULL,
  CLSCTX_INPROC_SERVER,
  IID_IMyObject,
  (void**)&m_pIMyObject);
개념적으로 다음의 C++ 코드와 동일하다 :
ISomeInterface* pISomeInterface = NULL;pISomeInterface = new CSomeObject();
첫 번째 경우는 COM 서브 시스템에게 IMyObject 인터페이스를 구현하는 오브젝트에 대한 포인터를 얻어오되 이 인터페이스를 구현하는 CLSID_MyObject 구현을 통해 얻어오고 있다. 두 번째 경우는 ISomeInterface 인터페이스를 구현하는 C++ 클래스의 인스턴스를 CSomeObject 클래스를 통해 생성하고 있다.
이 두개가 (개념적으로) 같다는 것은 명확하다. coclass는 COM 세계에서의 객체 지향적 클래스이다. coclass의 주요 특징은 (1) 원래부터 바이너리 규약이고, 따라서 결과적으로 (2) 프로그래밍 언어에 독립적이다.

레지스트리

윈도에서 COM 클래스, 인터페이스, 타입 라이브러리들은 GUID로써 리스트되어 레지스트리에 저장된다. COM 라이브러리는 레지스트리를 사용하여 COM 오브젝트들의 로컬 라이브러리를 찾거나 원격 서비스의 네트워크 위치를 찾는다.

리플렉션

컴포넌트는 COM 타입 라이브러리를 사용하여 자기 자신에 대한 정보를 기록할 수 있다. 타입 라이브러리는 컴포넌트의 CLSID, 컴포넌트가 구현하는 인터페이스의 IID 및 각 인터페이스에 선언된 메서드 정보 등을 포함한다. 타입 라이브러리는 일반적으로 비주얼 베이직 및 비주얼 스튜디오 같은 RAD 툴들로 말미암아 COM 컴포넌트를 사용하는 프로그래머들을 돕는 데 사용된다.

프로그래밍

COM 프로그래머는 COM 환경의 시작과 종료, COM 오브젝트의 인스턴스화 및 참조 카운트 관리, 버전 정보를 얻기 위한 오브젝트에 대한 쿼리, 개선된 버전 오브젝트의 이점을 얻을 수 있는 코딩, 최신 버전을 사용할 수 없을 경우 우아하게 기능을 줄여 동작할 수 있는 코딩을 할 책임이 있다.

애플리케이션과 네트워크에서의 투명성

COM 오브젝트를 인스턴스화하고 참조하는 작업은 하나의 프로세스 안에서 이루어지거나, 하나의 컴퓨터 안에서 프로세스의 경계를 넘어 이루어지거나, DCOM 기술을 사용하여 네트워크를 통해 원격으로 이루어질 수 있다. 프로세스의 경계를 넘거나 원격으로 사용되는 오브젝트들은 마샬링을 사용하여 메서드 호출을 보내고 결과를 받을 수 있다. 마샬링은 투명하게 동작하기 때문에 오브젝트 및 해당 오브젝트를 사용하는 코드에서는 보이지 않는다. (즉 마샬링을 사용하거나 사용하지 않거나 코드는 동일하다.)

COM에 대한 비판

COM은 꽤나 복잡한 구현이기 때문에, 몇몇 “플러밍(plumbing)” 이슈들로 말미암아 프로그래머들은 당황할 수 있다.

환경 초기화

COM은 COM의 기능을 필요로 하는 모든 스레드에 대해 CoInitialize[Ex]와 CoUninitialize 메서드를 프로그래머가 호출할 것을 요구한다. 추가적으로, OLE 클립보드 또는 드래그 앤 드랍을 사용하는 코드가 있다면, 프로그래머는 OleInitialize 와 OleUninitialize 메서드를 반드시 호출해야 한다. 시스템에 있는 몇몇 스레드들은 COM을 모르는 라이브러리에서 만들어질 수도 있기 때문에, 프로그래머는 자신이 만든 스레드 이외의 스레드에서 COM 라이브러리를 사용할 경우 항상 조심해야 한다.

메시지 펌핑

COM은 초기화 될 때 숨겨진 윈도를 생성하여 스레드 사이 또는 프로세스 사이의 메시지 라우팅에 사용한다. 이 윈도는 자신의 윈도 메시지 큐를 갖고 있고, 큐의 데이터는 정기적으로 체크되어 사용된다. 윈도 오류의 초기 버전들을 갖고 있는 시스템에서 그렇게 하는 것은 시스템 전역적인 데드락을 일으킬 수 있다. 이 문제는 특히 난처한데 왜냐하면 일부 윈도 API는 자신의 구현 일부로서 COM을 초기화하기 때문이다. 이는 구현에 있어 메모리 누수를 야기시킨다.

참조 카운팅

COM 참조 카운팅은 두 개 이상의 오브젝트가 서로 순환 참조하는 문제가 일어날 수 있다. 애플리케이션을 디자인할 때 이 사항을 반드시 고려하여 오브젝트가 고아(orphaned)가 되지 않도록 해야 한다.
COM "이벤트 싱크(event sink)" 모델을 사용하는 오브젝트의 참조 카운트는 항상 0보다 크게 된다. 왜냐하면 이벤트를 발생시키는 오브젝트는 해당 이벤트를 처리하는 오브젝트에 대한 참조가 필요하게 되는데, 이는 오브젝트의 참조가 결코 0이 되지 않도록 한다.
참조 사이클은 일반적으로 대역외 종료(out-of-band termination) 또는 신분 나누기(split identities)를 사용하면 깨지게 된다. 대역외 종료 기법에서 오브젝트는 호출될 경우 갖고 있는 다른 오브젝트에 대해 모든 참조를 버리게(drop) 하는 메서드를 노출하는데, 이 메서드가 호출되면 사이클은 깨지게 된다. 신분 나누기 기법에서는 하나의 구현이 서로 다른 두개의 COM 오브젝트(identities)를 노출하여, COM 오브젝트 사이에 약한 참조를 만든다. 이는 참조 사이클을 방해한다.

DLL 지옥

COM 컴포넌트는 시스템 전역적인 위치, 즉 윈도 레지스트리에 COM 컴포넌트의 위치 정보를 저장하기 때문에, 특정 컴포넌트에 대하여 하나의 버전만을 인스톨하여 사용할 수 있다. 따라서 COM은 DLL 지옥이라는 심각한 문제가 발생할 수 있는데, 이는 두 개 이상의 애플리케이션에서 특정 컴포넌트에 대한 서로 다른 버전을 필요로 할 때 발생하게 된다.
윈도 XP는 "레지스트레이션-프리 COM(Registration-free COM)"이라 불리는 COM 오브젝트를 등록시키는 새로운 COM 오브젝트 등록 모드를 소개하였다. 이 기능은 애플리케이션이 COM 오브젝트들을 설치할 때 COM 등록에 필요한 정보를 전역적인 레지스트리가 아닌 자신의 디렉터리에 저장하여 사용할 수 있도록 한다. 엄밀히 말해 이 기능은 해당 COM 컴포넌트들을 단일 애플리케이션에서 사용할 경우에만 적용이 가능하다. 레지스트레이션-프리 COM 을 사용하면 대부분 DLL 지옥을 피할 수 있다. 다만 최소 윈도 XP 또는 그 이상의 상위 버전의 OS가 필요하며, EXE COM 서버 및 시스템 전역적인 컴포넌트들 즉 MDAC, MSXML, 다이렉트X 및 인터넷 익스플로러 등에는 사용할 수 없다.