user: 컴포넌트 오브젝트 모델의 기술 세부 사항에 대해 설명해 줘.

gpt: COM 프로그래머는 COM 컴포넌트들을 사용하여 소프트웨어를 개발한다. 각각의 COM 컴포넌트들은 클래스 아이디로 식별하는데, 클래스 아이디는 전역 고유 식별자 형식으로 되어 있다. COM 컴포넌트의 기능은 하나 이상의 인터페이스를 통해 노출된다. 컴포넌트에서 지원하는 인터페이스들은 인터페이스 아이디로 식별하는데, 이것 역시 GUID이다.

COM 인터페이스는 C, C++, 비주얼 베이직과 같은 프로그래밍 언어 및 윈도 플랫폼에서 구현된 여러 종류의 스크립트 언어와 바인딩된다. 컴포넌트에 대한 접근은 모두 인터페이스의 메서드를 통해 이루어지는데, 이를 통해 프로세스간 또는 심지어 컴퓨터간 프로그래밍 같은 기법이 허용된다.

## 인터페이스
모든 COM 컴포넌트는 표준 IUnknown 인터페이스를 구현한다. 실제 모든 COM 인터페이스는 IUnknown 인터페이스로부터 상속된다. IUnknown 인터페이스는 AddRef, Release, QueryInterface 이렇게 3개의 메서드로 이루어진다.

COM 컴포넌트의 인터페이스는 반사적이며, 대칭적이고, 추이적인 특성을 보여야 한다.

인터페이스는 가상 메소드 테이블에 대한 포인터로 구성된다. 가상 함수 테이블은 인터페이스가 정의하는 함수 선언을 실제로 구현하고 있는 함수들에 대한 포인터의 리스트를 포함하며, 인터페이스에 선언된 함수의 순서와 동일한 순서대로 함수 포인터를 저장한다. 이러한 함수 포인터의 구조체를 전달하는 기법은 OLE 1.0이 자신의 시스템 라이브러리와 통신하기 위해 사용한 방법과 매우 유사하다.

COM은 컴포넌트끼리 서로 통신이 가능하도록 많은 다른 표준 인터페이스를 정의하고 있다. 예를 들어 IStream 인터페이스는 데이터 스트림을 나타내는 컴포넌트로 말미암아 노출된다.  이런 컴포넌트는 스트림 읽기 및 쓰기를 할 수 있는 Read와 Write 메서드를 갖는다. 다른 표준 인터페이스인 IOleObject는 컨테이너에 연결되거나 포함되는 컴포넌트로 말미암아 노출된다. IOleObject는 호출자가 컴포넌트의 테두리 사각형 크기를 결정할 수 있게 하거나 컴포넌트가 'Open', 'Save' 같은 동작을 지원하는지 여부를 확인할 수 있게 하는 등의 기능을 지원하는 메서드를 포함하고 있다.

## 클래스
COM에서 클래스는 **coclass**라고 불린다.

coclass는 하나 이상의 인터페이스를 구체적으로 구현한 것이다. coclass는 COM 컴포넌트 개발을 지원하는 모든 언어로 작성할 수 있다.

윈도 개발에 대해 COM이 한 주요 기여 중 하나는 **인터페이스를 구현으로부터 분리한다**는 개념을 도입했다는 점이다. COM에서는 객체를 인터페이스를 통하지 않고 직접 접근할 수 없기 때문에 반드시 인터페이스와 구현이 분리되어야 한다. 이와 함께 COM은 **단일 인터페이스 다중 구현**을 지원한다. 단일 인터페이스 다중 구현 개념은 애플리케이션이 실행 중에 인터페이스에 대한 여러 구현 중 하나를 선택하여 인터페이스를 인스턴스화 할 수 있다는 것을 의미한다.

## 인터페이스 정의 언어와 타입 라이브러리
타입 라이브러리는 COM 타입들을 설명하는 메타데이터를 포함한다. 이러한 COM 타입들에 대한 설명은 인터페이스 정의 언어를 통해 작성되며 컴파일 될 수 있도록 텍스트 파일로 저장된다.

COM 컴포넌트 개발에서 IDL을 사용하여 먼저 타입들을 정의하면서 시작하는 건 흔히 있는 일이다. IDL 파일은 개발자가 어떠한 프로그래밍 언어와도 관련되지 않는 방법으로 객체 지향적인 클래스, 인터페이스, 구조체, 열거형 및 다른 사용자 정의 타입들을 정의할 수 있도록 해준다.

IDL 파일은 **MIDL** 컴파일러에서 타입 라이브러리로 컴파일된다. 이러한 타입 라이브러리에 저장된 바이너리 메타데이터는 VB, VC++, 델파이 등과 같은 다양한 프로그래밍 언어로 처리하게 된다. 이와 같은 처리의 결과로 각각의 언어 컴파일러는 자신의 언어에 맞는 형식을 만들게 되고, 이런 형식들은 .TLB 파일에 정의된 coclass를 나타내게 된다.

## 오브젝트 프레임워크로서의 COM
COM의 기반이 되는 개념들은 객체 지향성에 그 뿌리를 두고 있다. COM은 프로그램을 객체 지향적으로 개발하고 배치하기 위한 인터페이스이다.

COM은 런타임 프레임워크이기 때문에 실행 중에 타입들을 개별적으로 식별하거나 지정할 수 있다. 이를 위해 전역 고유 식별자가 사용된다. 각각의 COM 타입에는 실행 시 식별 될 수 있도록 GUID가 할당된다.

COM 타입에 대한 정보를 컴파일시 및 실행시에 접근할 수 있게 하기 위해, COM은 타입 라이브러리를 소개하고 있다. 타입 라이브러리의 효과적인 사용을 통해 COM은 오브젝트 사이의 상호 동작을 지원하는 동적 프레임워크로서의 기능을 수행한다.

IDL에서의 다음과 같은 coclass 정의 예제를 살펴 보자 :

위의 코드는 MyObject라는 이름의 COM 클래스를 선언하고 있다. 이 클래스는 IMyObject라는 이름의 인터페이스를 구현하며, _IMyObjectEvents 이름의 이벤트 인터페이스를 지원한다.

이벤트 인터페이스 부분을 무시하면, 이것은 다음과 같은 C++ 클래스 정의와 개념적으로 동일하다 :

위의 코드에서 ISomeInterface는 C++ 가상 클래스이다.

이제 다시 MyObject COM 클래스를 보자 : MyObject를 위한 coclass가 IDL에 정의되고, 그것으로부터 타입 라이브러리가 컴파일되었다면, 각각의 언어 컴파일러는 이러한 타입 라이브러리를 읽고 적절히 해석하여, 개발자가 COM이 이해할 수 있는 coclass인 MyObject를 구현하고 궁극적으로 바이너리 실행 코드를 생성할 수 있도록 필요한 모든 코드를  생성해야 할 의무가 있다.

COM coclass가 구현되고 시스템에서 사용할 수 있게 되었다면, 이제 이를 어떻게 인스턴스화 할 지의 문제가 뒤따르게 된다. 현재 C++ 같은 언어에서는, CoCreateInstance API를 인스턴스화 할 coclass의 CLSID 와 해당 coclass에서 사용하려는 인터페이스 IID 를 지정하여 사용할 수 있다. CoCreateInstance를 호출하는 다음과 같은 코드는 :

개념적으로 다음의 C++ 코드와 동일하다 :

첫 번째 경우는 COM 서브 시스템에게 IMyObject 인터페이스를 구현하는 오브젝트에 대한 포인터를 얻어오되 이 인터페이스를 구현하는 CLSID_MyObject 구현을 통해 얻어오고 있다. 두 번째 경우는 ISomeInterface 인터페이스를 구현하는 C++ 클래스의 인스턴스를 CSomeObject 클래스를 통해 생성하고 있다.

이 두개가  같다는 것은 명확하다. coclass는 COM 세계에서의 객체 지향적 클래스이다. coclass의 주요 특징은  원래부터 바이너리 규약이고, 따라서 결과적으로  프로그래밍 언어에 독립적이다.

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

## 참조 카운팅
COM 규약은 참조 카운팅이라는 기법을 필요로 한다. 이 기법은 COM 오브젝트가 노출하는 인터페이스를 하나 이상의 클라이언트가 사용하고 있다면 오브젝트가 해제되지 않게 한다. 바꿔 말하면 COM 오브젝트는 자신의 인터페이스를 사용 중인 모든 클라이언트가 인터페이스 사용을 끝마쳤을 때 해제된다. COM 오브젝트는 자신의 참조 카운트가 0이 될 때 자신이 현재 사용 중인 모든 메모리를 해제해야 할 책임이 있다는 점에 주의하자.

COM의 가장 기반이 되는 인터페이스인  IUnknown 인터페이스는 **AddRef**와 **Release** 이렇게 두 개의 메서드를 통해 참조 카운팅의 개념을 지원한다. 참조 카운트는 **COM 오브젝트**의 참조 카운트를 말하는 것이며 **COM 인터페이스**가 참조 카운팅 되지 않는다는 점에 주의하자.

COM 오브젝트는 참조 카운팅을 위해 내부적으로 하나의 정수형 값을 사용한다. 이 값은 IUnknown 또는 이를 상속한 다른 COM 인터페이스를 통해 AddRef를 호출하면 증가한다. 이 값은 Release를 호출하면 감소한다. AddRef와 Release는 클라이언트가 COM 객체의 수명에 관여하는 유일한 방법이다. 이 정수값은 COM 오브젝트의 private 멤버이며 직접 접근할 수 있게 되는 경우는 없다.

AddRef의 목적은 COM 오브젝트에게 자신에 대한 추가적인 참조가 만들어졌음을 알려주는 것이다. 따라서 이 참조가 여전히 유효하다면 COM 오브젝트는 해제되지 않고 유지되어야 한다. 반대로 Release의 목적은 COM 오브젝트에게 클라이언트가 더 이상 자신을 필요로 하지 않음을 알려주는 것이다. 이를 통해 만약 참조 카운트가 0이 되었다면, COM 오브젝트는 해제되게 된다.

어떤 프로그래밍 언어는  참조 카운팅을 자동으로 지원하여 COM 오브젝트 개발자가 소스 코드에 참조 카운팅 관련 코드를 직접 작성하지 않아도 되게 한다. C++와 같은 저 수준의 프로그래밍 언어는 정수형 멤버를 선언하는 등 소스 코드에 직접 참조 카운팅 관련 코드를 작성해 주는 것이 필요하다.

다음은 AddRef 및 Release 호출과 관련하여 COM 오브젝트가 적절히 참조 카운팅되도록 돕는 일반적인 가이드라인이다:

참조 카운팅의 원리는 단순하지만 COM 개발자들 특히 신입 COM 개발자들은 참조 카운팅을 관리하는 소스 코드를 작성할 때 종종 오류를 일으킨다. 이는 참조 카운트를 부정확하게 하여, 어떠한 클라이언트도 사용하지 않는 COM 오브젝트가 해제되지 않고 메모리 누수를 일으키거나, 이미 해제된 COM 오브젝트에 대해 메서드를 호출하게 하여 런타임 오류를 일으킨다.

이런 문제들을 줄이기 위해 마이크로소프트는 C++ 개발자들을 위해 ATL을 소개하였다. ATL은 높은 수준의 COM 개발 패러다임을 지원한다. 또한 COM 클라이언트 애플리케이션 개발자가 직접 참조 카운팅을 관리하지 않도록 하는 스마트 포인터 오브젝트를 지원한다.

## 인스턴스화
COM은 COM 오브젝트를 인스턴스화하는 작업에 클래스 팩토리를 사용하도록 표준화하였다. COM 오브젝트를 생성하기 위해서는 반드시 다음 두 개의 항목 조합이 있어야 한다:
각각의 COM 오브젝트는 반드시 유일한 클래스 아이디가 할당되어 있어야 한다. 또한 각각의 COM 오브젝트는 반드시 자신의 클래스 팩토리가 있어야 한다. 클래스 팩토리는 그 자신도 역시 COM 오브젝트로서  인터페이스를 구현하는 오브젝트이다. 이러한 클래스 팩토리의 역할은 다른 오브젝트들을 생성하는 것이다.

보통 클래스 팩토리 오브젝트는 COM 오브젝트를 포함하고 있는 바이너리 실행 파일와 같은 파일에 포함된다. 클래스 팩토리 오브젝트의 소스 코드는 COM 오브젝트 개발자가 직접 작성할 수도 있지만,  코드 생성 프로그램에서도 생성할 수 있다. 클래스 팩토리를 사용하여 오브젝트를 생성하려면, 대상이 되는 오브젝트의 클래스 아이디가 반드시 있어야 한다. 클래스 팩토리는 클래스 아이디를 사용하여 해당 오브젝트를 인스턴스화한다.

클래스 팩토리가 하나 이상의 클래스에 대한 오브젝트를 생성할 수 있다는 점을 알아두자. 즉 서로 다른 클래스 아이디를 가지는 오브젝트들이 같은 클래스 팩토리 오브젝트에서 만들어질 수 있다. 그러나 이 부분은 COM 오브젝트 및 클래스 팩토리 개발자들에게만 관계되는 내부 사항이다. COM 시스템은 이 부분에 대해 투명하다.

왜 클래스 팩토리가 필요한지 의문이 생길 수 있다. 클래스 팩토리는 오브젝트의 생성에 대한 책임을 별개의 오브젝트에 위임함으로써, 개발자에게 오브젝트 생성에 대한 엄청난 유연성을 부여한다. 예를 들어 싱글턴 오브젝트들은 클래스 팩토리를 통해 매우 쉽게 구현될 수 있다.

클라이언트 애플리케이션이 클래스 팩토리 오브젝트를 얻을 수 있도록, COM 서버는 반드시 클래스 팩토리를 적절하게 노출해야 한다. 클래스 팩토리는 COM 서버 코드의 특징에 따라 다르게 노출된다. DLL 기반 서버는 반드시 
 ISomeObject* pISomeObject = NULL;

 if 
 pIClassFactory -> CreateInstance
 (
 NULL,
 IID_ISomeObject,
 &pISomeObject
 );

 pIClassFactory -> Release;

 pIClassFactory = NULL;

위에서는 가장 기본적인 레벨에서 오브젝트를 인스턴스화하기 위한 클래스 팩토리의 사용 방법에 대해 설명하였다. 좀 더 고수준의 인스턴스화도 또한 가능한데, 이들 중 일부는 윈도 API에 대한 직접적인 접근 없이도 사용할 수 있다.

예를 들어 애플리케이션은  메서드를 사용하여 COM 오브젝트를 생성한다.

비주얼 베이직은 오브젝트 인스턴스화를 위해 전역 함수  메서드를 호출하여 오브젝트를 생성하는 작업을 캡슐화한다.

다른 언어들도 자신만의 고수준 오브젝트 생성 방법을 제공할 수 있다. 그러나  인터페이스는 여전히 가장 기반이 되는 오브젝트 생성 기법이다.

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

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

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

## COM 스레딩
COM의 스레딩은 **아파트먼트**라는 단위로 이루어진다. 하나의 **아파트먼트**에는 하나 이상의 COM 컴포넌트가 포함되어 있다. 아파트먼트에는 단일 쓰레드, 다중 쓰레드, 쓰레드 뉴트럴 등 세 종류가 있다.

아파트먼트는 아파트먼트에 참여하는 모든 스레드와 오브젝트에 대해 다음과 같은 지침을 지키도록 규정하고 있다.

COM에는 세 종류의 아파트먼트가 있는데 각각 **싱글 쓰레디드 아파트먼트**, **멀티 쓰레디드 아파트먼트** 그리고 **쓰레드 뉴트럴 아파트먼트**이다. 각각의 아파트먼트는 하나의 메커니즘을 나타내는데, 이 메커니즘을 통해 여러 스레드가 오브젝트의 내부 상태 정보를 접근할 경우 동기화가 이루어지기도 한다.

싱글 쓰레디드 아파트먼트는 매우 일반적으로 사용되는 모델이다. 여기서의 COM 오브젝트는 일반 데스크톱 애플리케이션의 사용자 인터페이스가  동작하는 방식과 유사한 방식으로 동작한다. STA 모델에서는 하나의 오브젝트 전용 스레드에서 오브젝트의 메서드들이 실행된다. 따라서 해당 아파트먼트의 외부 스레드로부터 메서드 호출이 있으면, 그 메소드 호출이 마샬되어 시스템에서 자동으로 큐에 쌓이게 된다. 따라서 경쟁 조건 또는 동기화 문제에 대해 걱정하지 않아도 된다. 이는 각각의 메서드 호출이 항상 이전에 호출되었던 메서드가 끝난 다음에 차례대로 실행되기 때문이다.

COM 오브젝트의 메서드가 자체적으로 동기화를 수행하는 경우, COM 오브젝트의 호출 메서드들에 다중 스레드를 전용으로 사용하는 것이 허용된다. 이를 멀티 쓰레디드 아파트먼트라 부른다. STA에 있는 스레드로부터 MTA 오브젝트에 대한  호출이 있는 경우 마샬된다. 프로세스는 STA 및 MTA에 속하는 COM 오브젝트를 섞어서 사용할 수 있다.

쓰레드 뉴트럴 아파트먼트에서의 스레드들은 오브젝트의 호출 메서드들에 대해 전용으로 사용될 필요는 없다.