COM(3) - AtlCom.doc |
|
C/C++, MFC | 2008/06/09 (월) 05:20 |
|
1. COM개요1.1. COM 의 이점1.1.1. 왜 COM 이 필요한가?컴포넌트 개체 모델(Component Object Model, COM)은 자신의 고유한 기능을 제공하는 단위 어플리케이션 즉, 컴포넌트의 통합 및 커뮤니케이션 방법에 대한 표준을 정의한 사양이다.
1.1.2. 컴포넌트 소프트웨어의 조건1. COM 컴포넌트는 언어 독립적이어야 한다.
2. COM 컴포넌트는 이진(binary) 형태로 제공되어야 한다.
3. COM 컴포넌트는 버전 호환성을 제공해야 한다.
4. COM 컴포넌트는 위치 투명성(location transparency)을 제공해야 한다.
1.2. 인터페이스 개요1.2.1. 인터페이스란?COM 개체가 자신의 기능을 클라이언트에 노출시키는 기본적인 방법이다.
프로그램 상에서 인터페이스는 C++ 와 같은 OOP 언어에서의 순수한 가상 함수(pure virtual function) 만을 멤버로 포함하는 추상적인 클래스(abstract class)로 표현된다. 인터페이스는 가상 함수 테이블(virtual function table, VTBL) 형태의 메모리 구조를 정의한다.
인터페이스를 직접 클래스로 표현하는 대신에 C 언어와 같이 클래스를 지원하지 않는 언어와 함께 사용할 수 있도록 MS Win32 SDK 에서는 OBJBASE.H 헤더 파일에는 다음과 같은 매크로를 제공한다.
__stdcall 이란 MS 컴파일러 확장 예약어로, __stdcall로 표시된 함수는 Pascal 호출규약(calling convention)을 사용한다. Pascal 호출 규약을 사용하는 함수는 호출한 함수로 리턴 하기 전에 스택에서 매개변수를 삭제한다.
C/C++ 호출 규약을 사용하는 함수는 호출한 함수에서 스택에 있는 매개변수를 삭제하게 된다. Visual Basic 과 같은 대부분의 다른 언어에서는 기본적으로 Pascal 호출 규약을 사용한다. 또한, 모든 Win32 API 함수는 Pascal 호출 규약을 사용하므로, Pascal 호출 규약은 표준 호출 규약이 된다.
1.2.2. 인터페이스 메모리 구조인터페이스에서 파생되는 클래스
C++ 문법에서 가상함수를 포함하는 클래스에서 생성된 개체는 가상 함수 테이블 포인터를 갖게 된다.
이 가상 함수 테이블 포인터를 ‘인터페이스 포인터’ 라고 하며, 일단 인터페이스 포인터를 갖는 클라이언트에서는 가상 함수 테이블을 통하여 멤버함수 즉, 메서드를 호출할 수 있게 된다.
1.2.3. 인터페이스 상속과 구현 상속COM 은 인터페이스를 통하여 다형성 (polymorphism)을 지원하게 된다. 그러나 COM 은 상속성(inheritance)을 지원하지 않는다.
구현상속(implementation inheritance)
1. 일반적인 상속방법
2. 언어종속적
3. 기초 클래스가 손상되기 쉽다
인터페이스상속(interface inheritance)
1. 순수한 가상함수만을 포함하는 추상적인 클래스 즉, 인터페이스를 기초클래스로 파생클래스를 구현한다.
2. 구현상속의 단점이 해결됨.
3. COM 에서 사용
4. 문제점은 이미 구현된 코드를 전혀 재사용할 수 없다는 것이다. 이 문제는 COM 의 포함(Containment) 과 통합(Aggregation) 이라는 방법으로 해결된다.
1.3. IUnknown인터페이스1.3.1. IUnknown 인터페이스가 필요한 이유1. 인터페이스는 서로 독립적이다. 따라서 COM 개체는 자신이 제공하는 다른 인터페이스의 포인터를 클라이언트가 구할 수 있는 방법을 제공해야 한다.
2. 여러 클라이언트에서 COM 개체를 동시에 사용할 수도 있다는 사실
3. 이러한 이유로 모든 COM 개체는 반드시 IUnknown 인터페이스에서 파생되어야만 한다. IUnknown 인터페이스는 모든 COM 개체들이 반드시 갖추어야 할 기본적인 서비스에 대한 기준을 제공한다.
4. UNKNWN.H
1.3.2. QueryInterfaceIUnknown 인터페이스는 클라이언트가 COM 개체의 다른 인터페이스를 요청할 때 해당 인터페이스의 포인터를 리턴 하는 QueryInterface 멤버를 포함한다.
1.3.3. AddRef, Release여러 개의 클라이언트에서 COM 개체를 동시에 사용할 수도 있으며 따라서, 어떤 하나의 클라이언트에서 다른 클라이언트에서는 아직도 사용중인 COM 개체를 소멸시키는 경우에는 당연히 문제가 발생한다. 이 문제를 해결하기 위해 COM 개체 스스로가 자신이 몇 번 참조되고 있는가 하는 횟수를 관리하고, 이 레퍼런스 카운터가 0일 때 스스로를 소멸시키는 방법을 사용한다.
위 두 함수는 모두 레퍼런스 카운터를 나타내는 ULONG 값을 리턴 하지만, 디버깅을 위해 사용되는 것일 뿐, 이 리턴 값에 의존하지 말아야 한다.
레퍼런스 카운터가 0일 때 COM 개체 스스로가 자신을 소멸시키게 할 수 있다. 그러나 이들 함수를 사용할 때는 지켜야 할 몇 가지 규칙이 있다.
1. 인터페이스를 리턴 하는 함수에서는 리턴 하기 전에 해당 인터페이스 포인터에 대하여 항상 AddRef 함수를 호출해야 한다.
2. 인터페이스 사용이 끝나면 해당 인터페이스 포인터에 대하여 항상 Release 함수를 호출해야 한다.
3. 인터페이스 포인터를 다른 인터페이스 포인터에 대입한 경우에도 해당 인터페이스 포인터(LValue)에 대하여 AddRef 함수를 호출해야 한다.
1.3.4. IDL인터페이스는 C++의 클래스로 정의하거나, 또는 SDK에서 제공하는 매크로를 사용하여 정의할 수 있다.
COM 에서 인터페이스를 정의할 수 있는 표준 개발 도구가 IDL(Interface Definition Language) 이다. COM 에서 사용하는 IDL은 OSF(Open Software Foundation)에서 빌려와 사용하였던 RPC(Remote Procedure Call)의 IDL을 확장한 것이다. 이와 함께 MS는 IDL 정의 파일을 컴파일 할 수 있는 MIDL(Microsoft IDL) 컴파일러를 제공한다.
IDL과 MIDL 컴파일러를 사용하여 다음과 같은 작업을 할 수 있다.
1. 인터페이스를 정의한 코드를 포함하는 헤더 파일을 생성한다.
2. 로컬서버(local server) 또는 리모트서버(remote server) 컴포넌트의 커스텀 인터페이스에 대한 마샬링(marshaling) 기능을 제공하는 프록시(proxy)와 스텁(stub) 코드를 생성한다.
3. 자동화(automation)에서 사용되는 형식 라이브러리(type library)를 생성한다.
1.3.5. 표준 COM 인터페이스와 커스텀 COM 인터페이스COM 인터페이스는 COM 에 의하여 정의된 표준 COM 인터페이스(standard COM interface)와 개발자에 의해 정의된 커스텀 COM 인터페이스(custom COM interface)로 나뉘어진다.
1. 커스텀 COM 인터페이스: 개발자가 자신의 목적에 따라 명확하게 정의된 인터페이스. 커스텀 COM 인터페이스를 노출하는 COM 컴포넌트가 로컬 서버로 구현될 때 해당 COM 컴포넌트는 마샬링(marshaling) 코드를 제공해야 한다.
2. 표준 COM 인터페이스: 인터페이스가 COM 에 의해 제공되므로 여러분이 명확하게 인터페이스를 정의할 필요가 없다. IUnknown, IClassFactory 등의 인터페이스가 표준 COM 인터페이스에 속한다. 여기에는 OLE 나 ActiveX 기술에 포함되는 OLE Document, ActiveX Document, ActiveX Control, Automation 등에 사용되는 인터페이스가 포함되며, MS에 의하여 윈도우 운영체제 확장 컴포넌트로 제공되는 OLE DB나 Active Directory 등에 사용되는 인터페이스도 넓은 의미에서 표준 COM 인터페이스로 간주할 수 있다. 이들 표준 COM 인터페이스를 노출하는 COM 컴포넌트가 로컬 서버로 구현되는 경우에 해당 COM 컴포넌트는 마샬링코드가 ole32.dll과 oleauto32.dll을 통하여 운영체제 레벨에서 자동적으로 제공되기 때문에 마샬링 코드를 제공할 필요가 없다.
1.4. GUID와 HRESULT1.4.1. GUIDGUID(Globally Unique Identifier) 란 128비트(16바이트)의 크기를 갖는 구조체로, 전세계적으로 시간과 장소에 관계없이 고유하다고 보장할 수 있는 값을 나타내는 식별자로 사용된다. GUID를 UUID(Universally Unique Identifier) 라고도 하며, WTYPES.H 헤더 파일에 다음과 같이 정의되어 있다.
인터페이스ID 즉, IID (Interface Identifier)는 인터페이스를 식별하는데 사용되는 GUID로 다음과 같이 정의된다.
결국 IID와 GUID는 같다.
COM 개체도 자신을 식별하는데 사용되는 GUID를 갖는다. 이것을 클래스 ID(Class Identifier, CLSID)라고 하며 다음과 같이 정의된다.
Visual C++ 에서는 GUID를 생성하는 두 가지 툴을 제공한다. 도스 창에서 사용할 수 있는UUIDGEN.exe 와 Visual C++ 예제 프로그램으로도 제공되는 GUIDGEN.exe 이다.
이들 툴은 COM 라이브러리의 CoCreateGuid 함수를 호출하고, 다시 이 함수는 Win32 RPC API 함수인 UUIDCreate 를 호출하여 내부 알고리즘에 의하여 GUID를 생성한다. 이들 툴은 Visual Studio 설치 디렉토리의 Common 폴더의 Tools 디렉토리에 있다.
일반적으로 인터페이스의 GUID 식별자명은 ‘IID_’로 시작하며,
COM 개체의 GUID식별자명은 ‘CLSID_’로 시작한다.
DEFINE_GUID 매크로는 GUID 구조체 형식에 일치하도록 매크로 확장을 한다.
이 매크로는 INITGUID.H 헤더 파일에 정의되어 있으며, 이 매크로를 사용하기 위해서는 반드시 다음과 같은 순서로 헤더 파일을 #include 해야 한다.
#include <objbase.h>
#include <initguid.h>
OBJBASE.H 헤더 파일에는 GUID를 비교하는 operator== 함수가 정의되어 있으므로 2개의 GUID를 비교하는데 사용할 수 있다. 이 함수와 같은 기능을 하는 함수에는 IsEqualGUID, IsEqualIID, IsEqualCLSID 등이 있다.
GUID는 16바이트 크기를 갖기 때문에 레퍼런스(reference)로 넘겨주는 것이 효율적이다. 이것이 QueryInterface 함수의 첫 번째 매개변수가 REFIID 즉, const IID & 데이터 형을 갖는 이유이다.
COM 라이브러리에서는 GUID를 문자열로 변환하는 StringFromGUID2 함수를 제공한다.
IUnknown 인터페이스의 IID는 UNKNWN.H에 다음과 같이 선언되어 있으며, IUnknown 인터페이스를 비롯한 다른 표준 인터페이스의 IID는 UUID.LIB에 정의된다. 따라서, COM 컴포넌트를 어플리케이션에 UUID.LIB 파일을 링크시켜야 한다.
1.4.2. HRESULT
SUCCEEDED(), FAILED() 매크로를 사용한다.
2. COM 사용2.1. COM 클라이언트 어플리케이션2.1.1. COM 클라이언트, COM서버 어플리케이션COM 은 클라이언트/서버(Client/Server) 모델을 사용한다.
l COM 컴포넌트는 어떤 프로세스에서 생성되는가에 따라 [In-Process Server] 와 [Out-of-Process Server]로 나뉜다.
l [Out-of-Process Server]는 어떤 위치에서 생성되는가에 따라 [Local Server] 와 [Remote Server]로 분류된다.
2.1.2. COM 컴포넌트 등록COM 컴포넌트는 반드시 시스템 레지스트리에 등록되어 있어야 한다. 따라서, COM 컴포넌트의 설치 프로그램에서 시스템 레지스트리에 등록하는 기능을 제공하든가, 아니면 다음과 같은 방법으로 시스템 레지스트리에 직접 등록할 수 있다.
l [In-Process]인 경우, Visual C++에서 제공하는 regsvr32.exe를 사용하여 도스 창에서 다음과 같은 명령을 입력한다.
regsvr32 ServerName.dll regsvr32 ControlName.ocx l [Out-of-Process]인 경우, 직접 실행될 수 있기 때문에 regsvr32.exe를 사용하지 않고 다음과 같이 도스 창에서 ‘/regserver’ 또는 ‘-regserver’ 옵션으로 서버 어플리케이션을 실행한다.
ServerName.exe /RegServer 또는 ServerName.exe -RegServer 2.1.3. COM 컴포넌트 해제l [in-process]: regsvr32 /u ServerName.dll
l [Out-of-Process]:
ServerName.exe /unregserver 또는 ServerName.exe -unregserver 2.1.4. COM 클라이언트 어플리케이션 생성 과정 개요1. COM 라이브러리(COM Library)를 초기화한다.
2. COM 컴포넌트가 제공하는 COM 개체의 CLSID를 구한다.
3. COM 개체의 인스턴스를 생성한다.
4. COM 개체에서 제공하는 인터페이스의 포인터를 구하여 해당 인터페이스가 제공하는 메서드를 호출함으로써 COM 개체가 제공하는 서비스를 사용한다.
5. COM 컴포넌트의 사용이 끝나면 COM 라이브러리의 초기화를 해제한다.
2.2. COM 라이브러리 초기화와 해제2.2.1. COM 라이브러리COM 은 컴포넌트의 통합 및 커뮤니케이션 방법에 대한 표준을 정의한 사양(specification)이다. 이와 함께 COM 은 COM 라이브러리를 통하여 다음과 같은 기능을 포함하는 구현 코드를 제공한다.
1. COM 클라이언트와 서버 어플리케이션의 생성기능을 제공하는 몇 가지 기본적인 API 함수를 제공한다. 클라이언트 어플리케이션에 대해서는 COM 은 COM 개체를 생성하는 기본적인 함수를 제공하며, 서버 어플리케이션에 대해서는 COM 개체를 노출시키는 기능을 제공한다.
2. 클래스ID(CLSID)를 통하여 어떤 서버가 해당 클래스를 구현하고 있고, 그 서버가 어디에 위치하고 있는지를 시스템 레지스트리에서 찾아내는 기능을 제공한다.
3. COM 개체가 로컬 또는 리모트 서버에서 실행되고 있을 때 클라이언트를 대신하여 투명하게 RPC 호출을 해 준다.
4. 어플리케이션이 자신의 프로세스 영역 안에서 메모리를 할당을 제어하는 표준 메커니즘을 제공한다.
즉 COM 라이브러리는 COM 을 사용하는 모든 어플리케이션에서 유용하게 사용될 수 있는 컴포넌트 관리 서비스를 제공한다. COM 라이브러리는 COMPOBJ.DLL 이란 이름으로 윈도우 운영체제에 의하여 제공되며, 윈도우 System 폴더에서 찾을 수 있다.
2.2.2. COM 라이브러리 초기화CoGetMalloc 함수를 포함하는 메모리 할당 함수를 제외한 COM 라이브러리의 모든 함수는 반드시 CoInitialize 함수를 호출하여 COM 라이브러리를 초기화하여야 사용할 수 있다.
2.2.3. COM 라이브러리 초기화 해제CoUninitialize 함수는 CoInitialize 함수와 짝을 이루어 호출되어야 한다. CoInitialize 함수가 여러 번 호출되었다면 설사 CoInitialize 함수가 S_FALSE 를 리턴 했다고 해도 그만큼의 CoUninitialize 함수가 호출되어야 한다.
2.3. COM 개체 인스턴스 생성2.3.1. COM 개체의 CLSID 구하기COM 개체의 인스턴스를 생성하기 위해서는 먼저 해당 COM 개체의 클래스ID(CLSID)를 알아야 한다.
COM 클라이언트 어플리케이션에서 COM 개체의 CLSID 를 구하기 위해서는 다음과 같은 방법을 사용할 수 있다.
1. COM 컴포넌트가 소스 코드 형태로 CLSID 정의 파일을 제공한다면 해당파일을 COM 클라이언트 어플리케이션의 프로젝트에 포함시켜 사용할 수 있다. 그러나 일반적으로 COM 컴포넌트는 이진(binary) 파일 형태로만 제공하기 때문에 자신이 COM 컴포넌트를 생성하거나 SDK형태로 제공하는 경우를 제외하고는 이 방법을 사용하기는 어렵다.
2. 시스템 레지스트리에 등록되어 있는 COM개체에 대해 레지스트리 편집기 또는 Visual C++의 OLE/COM Object Viewer를 사용하여 해당 COM개체의 CLSID를 복사한 후 그 값을 GUID 구조체 형식으로 변환하여 정의하는 소스 코드를 작성하여 COM 클라이언트 어플리케이션에서 사용하는 방법이 있다. 그러나 이 방법도 몇 단계의 수작업 과정을 거쳐야 하며, 이 과정에서 문제점이 발생할 가능성이 많다.
3. 마지막으로 가장 좋은 방법은 CLSID에 대응되는 프로그램ID(ProgID)를 사용하는 것이다. COM 개체의 CLSID는 ProgID라고 하는 좀 더 읽기 쉬운 문자열 식별자와 대응되는 값을 갖는다.
그러나 CLSID가 전세계적으로 시간과 장소에 관계없이 고유하다고 보장할 수 잇는 값을 나타내는 식별자인 반면에, ProgID는 그것을 보장하지 않는다.
일반적으로 ProgID는 다음과 같은 형식을 갖는다.
예) Excel.Sheet.8
ProgID에서 버전 번호가 생략될 때 버전 독립적인 ProgID라고 한다. ProgID는 시스템 레지스트리에 HKEY_CLASSES_ROOT 밑에 저장되며, ProgID 키 밑에는 대응되는 CLSID를 저장하는 CLSID서브 키가 있다. 다시 CLSID 서브 키에 저장된 값은 HKEY_CLASSES_ROOT 키 밑에 있는 CLSID 밑에 서브키로 저장되며, 이 서브 키 밑에는 대응되는 ProgID 값이 저장하는 ProgID 서브키가 있다.
시스템 레지스트리에서 CLSID와 ProgID는 반드시 대칭적으로 일치해야 하며, COM 라이브러리는 CLSID와 ProgID를 상호 변환할 수 있는 CLSIDFromProgID와 ProgIDFromCLSID 함수를 제공한다.
COM 은 각국 언어를 지원하기 위해 내부적으로는 유니코드를 사용한다. 이것은 8비트 코드 체계로 한글과 같이 한 문자를 표현하는데 2바이트 또는 다중바이트를 사용하는 DBCS(Double Byte Character Set) 또는 MBCS(Multi Byte Character Set)와 다르다. CLSIDFromProgID 함수와 같이 COM 라이브러리의 문자열을 매개변수로 받아들이는 모든 API는 유니코드 문자열을 요구하며, 많은 표준 인터페이스에서 리턴 되는 문자열도 유니코드를 사용한다.
LPCOLESTR 데이터 형은 WTYPES.H 헤더파일에 다음과 같이 정의되어 있다.
wchar_t는 유니코드 문자를 저장할 수 있는 16비트 wide char 데이터 형이며, OLESTR 매크로는 문자열을 LPCOLESTR 데이터 형으로 변환시켜준다.
일반문자열과 유니코드 문자열을 상호 변환하는 함수
// Win32API 함수
MultiByteToWideChar(
UINT CodePage, DWORD dwFlags,
LPCSTR lpMultiByteStr, int cchMultiByte,
LPWSTR lpWideCharStr, int cchWideChar);
WideCharToMultiByte(UINT CodePage, DWORD dwFlags,
LPCWSTR lpWideCharStr, int cchWideChar,
LPSTR lpMultiByteStr, int cchMultiByte,
LPCSTR lpDefaultChar, LPBOOL lpUsedDefaultChar);
// C런타임 함수
mbstowcs(wchar_t *, const char *, size_t);
wcstombs(char *, const wchar_t *, size_t);
2.3.2. CoCreateInstance()COM 개체의 인스턴스를 생성하는 함수
2.4. COM 개체 사용CoCreateInstance 함수를 사용하여 COM 개체의 인스턴스를 성공적으로 생성하고
사용하고자 하는 인터페이스의 포인터를 얻었다면
이 인터페이스 포인터를 통하여 인터페이스가 제공하는 메서드를 호출하여
COM 개체가 지원하는 서비스를 사용할 수 있게 된다.
또한, 해당 COM 개체가 다른 인터페이스를 지원한다면
CoCreateInstance 함수에서 리턴된 인터페이스 포인터를 통하여
QueryInterface 메서드를 호출함으로써 지원하는 인터페이스를 요청한 후
다시 해당 인터페이스 포인터를 통하여 메서드를 호출할 수 있다.
인터페이스의 사용이 끝났을 때 Release 메서드를 호출하는 것을 잊지 않도록 한다.
3. COM 개체 구현3.1. COM 인터페이스 정의3.1.1. IDL(Interface Definition Language)와 MIDL 컴파일러인터페이스란 순수한 가상함수만을 포함하는 C++ 클래스로 인터페이스를 정의할 수 있다고 배웠다. 그러나 Win32 환경에서 인터페이스를 정의하는 가장 좋은 방법은 IDL(Interface Definition Language)를 사용하는 것이다. IDL은 C++와 유사한 구문으로 인터페이스를 가장 정확하게 정의할 수 있도록 해준다.
MIDL 컴파일러는 IDL파일을 컴파일하여 C와 C++에서 사용할 수 있는 인터페이스를 정의한 코드를 포함하는 헤더파일을 생성한다. 사실 MIDL 컴파일러는 이것 외에도 두 가지 중요한 기능을 제공한다.
1. 로컬서버 또는 리모트 서버 컴포넌트의 커스텀 인터페이스에 대한 프록시와 스텁 코드를 생성하는 기능을 제공
2. 자동화(Automation)에서 사용되는 형식 라이브러리(type library)를 생성하는 기능 제공
midl AddBack.idl
MIDL로 컴파일러는 4개의 파일을 생성한다.
1. AddBack.h: C와 C++ 에서 사용할 수 있는 IDL 파일에 지정된 모든 인터페이스를 정의한 코드가 저장된다.
2. AddBack_i.c: IDL파일에서 사용되는 모든 GUID, 즉 IID를 정의하는 코드가 포함된다.
3. AddBack_p.c와 DllData.c: 로컬 서버 또는 리모트 서버에 필요한 커스텀 인터페이스에 대한 마샬링 기능을 제공하는 프록시와 스텁 코드를 포함하게 된다.
3.1.2. IDL기본 구문1. 대괄호([]) 안에 인터페이스에 대한 속성 리스트가 놓이게 된다.
2. 인터페이스 속성 리스트 다음에는 인터페이스 본문이 온다.
3. import 예약어는 C언어의 #include 지시어와 동일한 역할을 한다. 콤마(‘,’)를 사용하여 여러 개의 파일을 포함할 수 있으며 세미콜론(‘;’)으로 끝나야 한다. UNKNWN.IDL 파일에는 IUnknown 인터페이스와 IClassFactory 인터페이스의 IDL문이 저장되어 있다.
IDL의 목적 중의 하나는 메서드의 매개변수가 마샬링(marshaling) 될 수 있는 충분한 정보를 제공하며, 필요한 데이터만 프로세스 사이에 넘겨주기 위해 마샬링에 사용되는 프록시(proxy)와 스텁(stub) 코드를 최적화하는 것이다. 이것을 위해 IDL은 정확하게 데이터 형을 설명하기 위한 많은 속성을 제공하게 되며, 그 중의 하나가 in과 out속성이다.
out속성을 갖는 매개변수는 항상 포인터이어야 한다.
in이나 out속성으로 지정된 매개변수가 사용하는 메모리는 호출 측에서 할당하고 해제한다. 그러나 in과 out 속성을 모두 갖는 매개변수가 사용하는 메모리는 초기에는 호출 측에서 할당하지만, 호출되는 측에서 해제하고 다시 할당할 수 있게 된다.
MIDL컴파일러는 object 예약어가 사용된 인터페이스의 모든 메서드는 HRESULT를 리턴 하도록 제한하고 있다. 만약 메서드가 HRESULT 가 아닌 다른 값을 리턴 해야 한다면 out 매개변수를 사용해야 한다. 비 동기 메서드(asynchronous method)에 대해서는 예외로 void 리턴 데이터 형을 허용한다.
3.2. COM 개체 구현3.2.1. COM 개체 구현 방법 비교일반적으로 COM 개체를 구현하는 방법에는 두 가지가 있다.
1. 인터페이스 포함 방법: COM개체 클래스 안에 인터페이스 구현 클래스를 포함시키는 방법, 구문은 복잡하지만 디버깅이 쉽다는 장점, MFC라이브러리가 사용하는 방식
2. 인터페이스 상속 방법: COM개체 클래스를 직접 인터페이스에서 파생시키는 방법, 인터페이스 포함방법에 비해 간단하다. ATL이 사용하는 방식
3.2.2. 인터페이스 상속 방법을 사용한 COM 개체 클래스 정의
3.2.3. IUnknown 인터페이스 메서드 구현
다중 스레드에서 동시에 같은 변수를 사용하는 것을 막기 위해서 다음과 같은 함수를 사용하여 레퍼런스 카운터를 증가 또는 감소 코드를 작성할 수 있다.
3.3. 클래스 팩토리 구현3.3.1. COM 개체 생성 과정
CoCreateInstance (rclsid, pUnkOuter, dwClsContext, riid, ppv)
CoGetClassObject (rclsid, dwClsContext, NULL, riid, ppv)
DllGetClassObject (In-Process Server일 경우)
IClassFactory::CreateInstance (pUnkOuter, riid, ppv)
IClassFactory::Release ()
1. CoGetClassObject(): 레지스트리의 HKEY_CLASSES_ROOT\CLSID\에서 rclsid와 같은 서브 키를 찾아, 해당 서브키 밑에 있는 InprocServer32(인-프로세스 서버인 경우), LocalServer32(로컬 서버인 경우) 서브키 값에 지정된 COM 개체를 포함하고 있는 COM 컴포넌트 서버를 메모리에 로드 한다.
2. DllGetClassObject(): IClassFactory 인터페이스를 구현한 클래스 팩토리 COM 개체의 인스턴스를 생성하고, CoGetClassObject()에서 요청한 IClassFactory 인터페이스 포인터를 리턴 하게 된다.
3. IClassFactory::CreateInstance(): 자신과 관련을 맺고 있는 COM 개체의 인스턴스를 생성하고 QueryInterface()를 호출하여 CreateInstance의 두 번째 매개변수 riid에 지정된 인터페이스 포인터를 구한다음. 해당 인터페이스 포인터를 세 번째 매개변수 ppv에 리턴 하게 된다.
CoGetClassObject 함수는 일반적으로 CoCreateInstance 함수에 의해 호출되지만, CoCreateInstance 함수는 IClassFactory 인터페이스를 지원하는 클래스 팩토리 COM 개체 만을 생성하기 때문에 여러분이 직접 CoGetClassObject 함수를 사용해야 할 경우가 있다. 예를 들어 IClassFactory 인터페이스에 라이센스 기능이 추가된 IClassFactory2 인터페이스를 지원하는 클래스 팩토리 COM 개체를 생성해야 하는 경우에는 여러분은 직접 CoGetClassObject 함수를 사용해야 한다.
3.3.2. 클래스 팩토리와 IClassFactory 인터페이스COM 개체의 인스턴스를 생성하는 역할은 해당 COM 개체와 관련된 클래스 팩토리(Class Factory)라고 하는 또 다른 COM 개체가 수행하며, 클래스 팩토리 COM 개체는 반드시 IClassFactory 인터페이스를 지원해야 한다.
CreateInstance가 CLSID를 받아들이지 매개변수가 없다. 이것은 CoGetClassObject 함수에 매개변수로 넘겨진 CLSID에 대응되는 COM개체의 인스턴스만을 생성한다는 것을 의미한다. 즉, 하나의 클래스 팩토리 COM개체는 하나의 CLSID에 대응되는 COM 개체의 인스턴스만 생성할 수 있다.
3.3.3. 클래스 팩토리 COM 클래스 구현
3.4. 인 – 프로세스 서버 구현3.4.1. Win32에서의 DLLWin32 운영체제의 프로세스(process)는 4GB의 자기자신의 고유의 주소 영역 안에서 실행된다. 이것은 각 프로세스는 자신이 소유한 메모리와 Win32 운영체제가 공유하도록 지정한 메모리 영역에만 접근할 수 있다는 것을 의미한다.
WinMain 함수가 윈도우 어플리케이션의 시작 함수인 것과 마찬가지로, DLL의 시작함수는 DllMain이다. 그러나 DllMain 함수는 종료 함수이기도 하다. 즉, DllMain 함수는 DLL 모듈이 시작할 때도 호출되지만, 반면에 종료할 때도 Win32 운영체제에 의하여 호출된다.
DllMain 함수의 구조
우리가 DLL을 생성할 때 EXE파일이나 다른 DLL에서 호출할 수 있는 함수를 생성하게 된다. 이 DLL 함수가 EXE파일이나 다른 DLL에서 호출할 수 있게 하기 위해서는 해당 함수는 반드시 익스포트(exported) 되어야 한다. C++ 언어를 사용하는 경우에 DLL 함수를 익스포트 하기 위해서는 해당 함수는 extern “C”로 선언되어야 한다. 다음은 익스포트 함수를 선언한 예이다.
이와 함께, DEF 파일을 사용하여 익스포트 함수를 선언한다.
3.4.2. 인-프로세스 서버 구현 개요1. 클라이언트는 생성하고자 하는 COM 개체의 CLSID와 IID를 매개변수로 COM 라이브러리의 CoCreateInstance 함수를 호출한다.
2. COM 라이브러리의 CoCreateInstance 함수는 내부적으로 CoGetClassObject 함수를 호출한다.
3. CoGetClassObject 함수는 시스템 레지스트리에서 클라이언트가 요청한 CLSID에 대응되는 COM 컴포넌트가 저장되어 있는 인-프로세스 서버 정보를 찾는다.
4. CoGetClassObject 함수는 CoLoadLibrary 함수를 호출하여 해당 인-프로세스 서버 DLL을 메모리에 로드하고, 해당 DLL의 DllGetClassObject 익스포트 함수를 호출한다.
5. DllGetClassObject 함수에서는 클라이언트가 요청한 COM 개체의 클래스 팩토리 COM개체를 생성한 후, 해당 클래스 팩토리 COM 개체의 IClassFactory 인터페이스 포인터를 리턴 한다.
6. CoGetClassObject 함수에서는 IClassFactory 인터페이스 포인터를 통하여 구하고자 하는 인터페이스를 매개변수로 CreateInstance 멤버를 호출한다.
7. IClassFactory::CreateInstance 함수는 새로운 COM 개체를 생성하고 클라이언트에서 요청한 인터페이스 포인터를 리턴 한다.
8. CoGetClassObject 함수는 IClassFactory::CreateInstance 함수에서 리턴한 인터페이스 포인터를 다시 CoCreateInstance 함수에 리턴 한다.
9. CoCreateInstance 함수는 CoGetClassObject 함수가 리턴한 인터페이스 포인터를 다시 클라이언트에 리턴 한다.
10. 클라이언트에서는 리턴된 인터페이스 포인터를 통하여 해당 인터페이스에 구현된 메서드를 사용한다.
CoCreateInstance
CoGetClassObject
CoLoadLibrary
DllGetClassObject
IClassFactory::CreateInstance
COM 개체를 포함하는 인-프로세스 서버를 생성하고자 할 때 해당 DLL에는 DllMain 함수 외에도, 클라이언트에서 COM 개체를 사용할 때 호출되는 다음과 같은 4개의 익스포트 함수가 포함되어 있어야 한다.
1. DllGetClassObject: COM 라이브러리가 COM 개체의 클래스 팩토리 COM 개체를 생성할 때 호출.
2. DllRegisterServer / DllUnregisterServer: COM 개체를 시스템레지스트리에 등록할 때 REGSVR32.EXE 파일에 의하여 호출된다.
3. DllCanUnloadNow: COM 라이브러리가 DLL을 언로드할 수 있는지 여부를 요청할 때 COM 라이브러리에 의해 호출된다.
3.4.3. DllGetClassObject 함수 구현COM 개체를 생성하기 위해서는 먼저, 해당 COM 개체와 대응되는 클래스 팩토리 COM 개체를 생성해야 한다. 인-프로세스 서버인 경우에는 COM 라이브러리의 CoGetClassObject 함수에서 DLL의 DllGetClassObject 함수를 호출하고, 이 함수에서 실제로 클래스 팩토리 COM 개체를 생성하게 된다. 따라서, 우리는 인-프로세스 서버에 DllGetClassObject 함수를 구현해야만 한다.
3.4.4. DllCanUnloadNow 함수 구현COM 라이브러리는 사용되지 않는 DLL을 메모리에서 언로드하여 해제시키는 역할을 하는 CoFreeUnusedLibraries 라고 하는 함수를 제공한다.
CoFreeUnusedLibraries 함수는 DLL에 구현된 DllCanUnloadNow 함수를 호출하여 DLL을 해제시켜도 좋은지 여부를 묻는다.
따라서 IClassFactory::CreateInstance 에서는 g_cObjects 전역 변수의 값을 증가시키고, 각 COM 개체 클래스의 Release 함수에서는 감소시키는 코드를 작성함으로써, 전역변수 g_cObjects 즉 컴포넌트 카운터를 관리할 수 있다.
그러나 COM 개체 카운터를 관리하는 것만으로는 충분하지 않을 수도 있다. 이때 IClassFactory::LockServer 멤버는 이것을 위한 장치를 제공한다.
IClassFactory::LockServer구현은 COM 개체 카운터 즉, g_cObjects 값을 증가시키고 감소시키면 된다. 그러나, 이것과는 별도로 로크 카운터(lock counter)를 사용하는 것이 일반적이다.
3.4.5. DllRegisterServer/DllUnregisterServer 함수 구현일반적으로 인-프로세스 서버를 레지스트리에 등록할 때 REGSVR32.EXE를 사용한다. 이 프로그램은 명령 행에서 인-프로세스 서버 파일명을 매개변수로 받아, 해당 DLL의 DllRegisterServer 익스포트 함수를 호출한다.
또한, REGSVR32.exe 프로그램에 /u 옵션을 함께 사용될 경우에는 해당 DLL의 DllUnregisterServer 익스포트 함수를 호출한다.
따라서 우리는 레지스트리에 COM 컴포넌트를 등록 해제하는 DllRegisterServer 와 DllUnregisterServer 함수를 구현해야 할 필요가 있다. 이때 레지스트리를 조사하는 Win32 SDK 함수를 사용한다. Win32 SDK 는 레지스트리의 정보를 조작할 수 있는 다음과 같은 6개의 함수를 제공한다.
1. RegOpenKeyEx
2. RegCreateKeyEx
3. RegSetValueEx
4. RegEnumKeyEx
5. RegDeleteKey
6. RegCloseKey
참고로 이들 함수를 사용하기 위해서는 WINREG.H 또는 WINDOWS.H 헤더 파일을 추가하고 ADVAPI32.LIB 파일을 함께 링크해야 한다.
3.5. 로컬 서버 구현3.5.1. 로컬 서버 구현시 고려 사항인-프로세스 서버는 DLL 로 구현되기 때문에 자신을 로드한 클라이언트 어플리케이션의 프로세스 영역 안에서 실행된다. 따라서, COM 개체가 리턴한 인터페이스 포인터는 클라이언트와 같은 프로세스 영역 안에 있게 된다. 그러나 별도의 .exe 실행파일로 구현되어, 같은 시스템의 다른 프로세스 영역에서 실행되는 로컬 서버나, 네트워크 상의 전혀 다른 시스템의 프로세스 영역에서 실행되는 리모트 서버의 경우는 자기 자신의 고유한 주소 영역을 가지며, 따라서 설사 같은 주소를 가리킨다고 해도 서로 다른 프로세스 영역에서는 각기 다른 물리적인 메모리를 가리키게 되기 때문이다.
따라서 로컬 서버나 리모트 서버는 인-프로세스 서버와는 전혀 다른 방법으로 구현될 필요가 있으며, 서버와 클라이언트를 연결시켜 줄 수 있는 매개체를 필요로 하게 된다. 그러나, 여기에서 중요한 한 가지 사실은 서비스를 제공하는 COM 컴포넌트가 인-프로세스 서버로 구현되든 로컬 서버나 리모트 서버로 구현되든 클라이언트에서는 같은 방법으로 서비스를 제공받을 수 있어야 한다는 것이다.
3.5.2. 표준 마샬링(standard marshaling)COM 은 두 프로세스 사이의 커뮤니케이션(Inter-Process Communication, IPC) 수단으로 LPC(Local Procedure Call)와 RPC(Remote Procedure Call)란 방법을 사용한다. LPC는 같은 시스템 상에서 실행되는 서로 다른 프로세스 사이의 커뮤니케이션 방법이며, RPC 는 광범위한 네트워크 전송 메커니즘을 사용하여 서로 다른 시스템 상에서 실행되는 프로세스가 서로 커뮤니케이션 하는 방법을 제공한다.
서로 다른 프로세스 영역 사이에 인터페이스 포인터가 넘겨질 때 Ole32.dll은 인터페이스를 마샬링 하는데 필요한 프록시와 스텁코드를 로드 한다. 먼저 ole32.dll은 로컬 또는 리모트 COM 개체가 생성되는 프로세스 영역에 스텁을 생성한다. 다음에는 스텁과 연결되는 IPC채널을 생성하고, 마지막으로 클라이언트 프로세스 영역에 프록시를 생성한 후 IPC 채널에 연결시킨다.
IPC 채널은 서로 다른 프로세스 영역에 있는 클라이언트와 COM 개체 사이에 메시지를 전달하는 역할을 수행하며, 프록시와 스텁은 데이터를 네트워크 중립적인 형식(Network Data Representation, NDR)으로 포장하는 작업을 수행하는 일종의 COM 개체이다. 이 NDR 패킷은 IPC채널을 통하여 프록시와 스텁 사이에 전송된다. 프록시는 클라이언트 프로세스 영역에 로드 되며, 스텁은 로컬 또는 리모트 서버의 프로세스 영역에 로드 된다.
프록시는 클라이언트가 커뮤니케이션 하고자 하는 로컬 또는 리모트 서버에 있는 COM 개체와 같은 인터페이스를 노출시킨다. 따라서, 클라이언트에서 로컬 또는 리모트 서버의 COM 개체에 인터페이스 포인터를 요청할 때 클라이언트에게 리턴 되는 인터페이스 포인터는 실제로는 프록시가 노출하는 인터페이스 포인터가 된다. 또한, 이 인터페이스 포인터를 사용하여 메서드를 호출할 때도 실제로는 프록시 안에 있는 코드가 실행된다. 이와 함께, 프록시는 클라이언트로부터 넘겨 받은 매개변수를 전송할 수 있도록 포장하는 작업을 수행한다.
매개변수와 리턴 값을 포장할 때, 로컬 서버의 경우에도 두 프로세스가 모두 이해할 수 있는 표준 형식이 필요하지만, 리모트 서버의 경우에는 좀더 복잡하다. 그것은 일반적으로 같은 정보를 서로 다른 형식으로 나타내는 경우가 많기 때문이다. 특별히 문자나 정수 그리고 부동소수점을 표현하는 방식이 서로 다르다. 두 경우 모두 매개변수와 결과를 전송하는 표준적인 형식이 필요하며, 적절한 변환작업을 수행하는 코드가 클라이언트와 서버에 모두 있어야 한다.
이때 호출 측의 데이터를 전송할 수 있는 표준 형식으로 변환하는 것을 마샬링(marshaling)이라고 하며, 그 반대의 작업 즉, 표준 형식의 데이터를 받는 측의 프로세스에 적절한 형식으로 변환하는 것을 언마샬링(unmarshaling) 이라고 한다.
3.5.3. 커스텀 COM 인터페이스의 프록시/스텁 DLL 생성표준 COM 인터페이스는 COM 에 의하여 정의된 인터페이스이다. 여기에는 자동화(Automation)에서 사용되는 디스패치 인터페이스(dispinterface)와 이중 인터페이스(dual interface)도 포함된다. 표준 COM 인터페이스에 대한 마샬링 코드는 ole32.dll과 oleauto32.dll을 통하여 운영체제 레벨에서 자동적으로 제공되기 때문에 별도의 마샬링 코드를 제공할 필요가 없다.
그러나 커스텀 COM 인터페이스에 대해서는 마샬링 코드를 포함하는 프록시/스텝 DLL을 제공해야 한다. 클라이언트가 커스텀 COM 인터페이스가 서버에서 리턴 될 때 ole32.dll은 레지스트리에서 해당 인터페이스에 대한 프록시/스텁 DLL을 찾아 클라이언트와 서버의 프로세스 영역에 각각 로드 하게 된다.
커스텀 COM 인터페이스에 대하여 마샬링 코드를 포함하는 프록시/스텁 DLL을 생성하는 일은 매우 복잡하고 어려운 일이다. 그러나 IDL 을 사용하여 인터페이스를 정의한 다음, MIDL 컴파일러를 사용하여 프록시와 스텁 코드를 포함하는 DLL을 생성하는 방법으로 편리하게 구현할 수 있다.
MIDL 컴파일러로 IDL 파일을 컴파일하려면 도스 창에서 다음 명령을 입력한다.
도스 창에서 다음과 같은 옵션으로 MIDL 컴파일러를 실행시켜도 같은 결과를 얻게 된다. 생성할 파일명을 변경하고자 한다면 옵션 다음에 원하는 파일명을 입력하면 된다.
Visual C++ 개발환경에게 IDL 파일을 컴파일할 때 MIDL.EXE 를 사용한다는 사실을 알려준다.
[Project/Settings] 왼쪽 트리뷰에서 IDL 파일 노드를 선택하고, 오른쪽 속성 페이지에서는 [Custom Build] 탭을 선택한 후 [Build Command(s)] 리스트에 다음 명령을 추가한다.
또한, [Output File(s)] 리스트에는 다음 명령을 추가한다.
MIDL 컴파일러는 다음과 같은 4개의 파일을 생성하게 된다.
MIDL 컴파일러가 생성한 파일은 C와 C++ 프로그램에서 모두 사용될 수 있다. 그러나 한가지 단점은 이들 생성된 파일의 내용을 전혀 이해할 수 없다는 것이다. 하지만 굳이 이해할 필요는 없기 때문에 이것을 단점이라고 할 수도 없을 것이다.
프록시/스텁 DLL을 생성하기 위해서는 MIDL 컴파일러가 생성한 파일을 컴파일하고 링크해야 한다. 여러분은 Visual C++ 개발 환경에서 Win32 Dynamic Link Library 유형의 프로젝트를 생성한 후 이들 파일을 프로젝트에 추가하면 된다.
이때 이 프로젝트에는 RPC 런타임 임포트 라이브러리(RPCRT4.LIB)가 링크되어야 하며, 생성된 프록시/스텁 코드가 자기 등록(self registration)을 할 수 있게 하기 위해서는 DLLDATA.C에 대하여 REGISTER_PROXY_DLL 매크로를 정의해야 한다. DLLDATA.C파일을 수정하여 직접 이 매크로를 추가하는 것은 바람직하지 않다. 그것은 MIDL 컴파일러가 IDL 파일을 다시 컴파일할 때 완전히 파일 내용을 갱신하기 때문이다. 따라서 [Project Setting] 대화상자에서 DLLDATA.C에 대하여 REGISTER_PROXY_DLL 매크로를 정의하는 것이 좋다.
이와 함께, 인-프로세스 서버가 익스포트 해야 하는 4개의 함수 (DllGetClassObject, DllCanUnloadNow, DllRegisterServer, DllUnregisterServer)와 함께, MIDL이 생성한 GetProxyDllInfo 함수를 익스포트 하기 위해 다음과 같은 .DEF 파일을 작성한 후 프로젝트에 추가해야 한다.
이제 여러분은 이 프로젝트를 컴파일/링크 한 후, REGSVR32.EXE를 사용하여 레지스트리에 등록하면 된다. 성공적으로 프록시/스텁 DLL이 레지스트리에 등록되었다면, 레지스트리의 HKEY_CLASSES_ROOT\Interface 밑에 IDL 파일에 지정된 인터페이스의 IID 서브키가 추가된다. 이 서브 키의 ProxyStubClsid32 키에는 이 인터페이스에 사용될 프록시/스텁 DLL의 CLSID가 저장된다. 다시 HKEY_CLASSES_ROOT\CLSID 밑에 프록시/스텁 DLL의 CLSID 서브 키의 InprocServer32 키에는 해당 프록시/스텁 DLL 파일의 절대경로명이 저장된다.
3.5.4. 효율적인 마샬링을 위한 IDL 속성포인터가 매개변수로 프로세스 영역 사이에 넘겨질 때 잠재적인 비효율성의 원인이 된다. 포인터에 대하여 마샬링 코드가 수행될 때 해당 포인터가 가리키는 메모리의 모든 데이터를 복사하여 서버로 넘겨지게 된다. 또한, 포인터가 out속성을 갖는다면 메모리의 데이터는 다시 서버에서 복사되어 클라이언트로 넘겨져야 한다. 포인터가 복잡한 데이터 구조를 가리킨다면 마샬링 코드는 더욱 복잡해지게 된다. 다음 IDL 속성은 포인터에 대하여 효율적으로 마샬링을 하기 위한 정보를 제공하게 된다.
인터페이스 속성 리스트에 pointer_default 예약어를 사용하여 포인터 매개변수에 다른 속성이 지정되지 않은 경우에 어떻게 처리할 것인가에 대한 디폴트 정보를 제공할 수 있다.
만약 널 문자(‘\0’)로 끝나는 문자열 포인터를 넘겨주기 원한다면 string 속성을 사용하는 것이 좋다. 매개변수에 string 속성이 지정되면 MIDL 컴파일러는 이 매개변수가 문자열이라는 것을 알 수 있고, 따라서 널 문자를 찾아 문자열의 길이를 결정하게 된다. COM 은 문자열에 wchar_t* 또는 LPOLESTR 데이터 형이나 OLESTR 매크로를 사용한다.
IDL은 배열을 명확하게 설명할 수 있게 하는 속성을 제공한다. 이것은 메서드가 호출될 때 클라이언트와 서버 사이에 배열을 어떻게 넘겨줄 것인가를 정확하게 최적화 할 수 있게 해준다.
이들 배열 IDL 속성과 함께 사용되는 값은 괄호 안에 지정되며, 상수나 다른 매개변수의 변수명을 사용할 수 있다. 다차원 배열의 경우에는 괄호 안에 콤마(,)로 구분한다.
MIDL 은 포인터가 가리키는 것이 무엇인지를 정확하게 알아야 해당 포인터가 가리키는 데이터를 마샬링하는 방법을 할 수 있게 된다. 따라서 매개변수에 void*를 절대로 사용할 수 없으며, 일반적인 인터페이스 포인터를 넘겨주어야 할 경우에는 IUnknown*를 사용해야 한다. 이 경우에 iid_is 속성을 사용하여 MIDL 에게 사용하게 될 인터페이스의 IID가 무엇인지를 알려주어야 한다.
COM 은 클라이언트와 서버 모두에서 메모리를 할당하고 해제하는데 사용할 수 있는 하나의 디폴트 메모리 관리자를 제공함으로써 메모리 누수 현상을 방지할 수 있게 한다. COM 의 디폴트 메모리 관리자를 사용하려면 CoTaskMemAlloc, CoTaskMemFree COM 라이브러리 함수를 사용해야 한다.
3.5.5. 로컬 서버 구현 개요로컬 서버는 인-프로세스 서버와는 달리 자신의 프로세스 영역 안에서 실행되므로 WinMain 함수를 갖는다.
인-프로세스 서버의 경우 다음과 같은 4개의 익스포트 함수를 통하여 구현 할 수 있었다.
1. DllGetClassObject
2. DllCanUnloadNow
3. DllRegisterServer
4. DllUnregisterServer
로컬 서버에서는 이들 익스포트 함수가 갖고 있는 기능을 대체할 만한 기능을 제공해야 한다.
다음은 그 예이다.
3.5.6. CoRegisterClassObject / CoRevokeClassObject클라이언트의 CoCreateInstance 요청에 의하여 COM 이 로컬 서버를 로드 할 때, 레지스트리에 저장된 로컬 서버의 경로명에 ‘-Embedding’ 명령행 매개변수를 추가한다. 이때 로컬 서버는 명령 행에서 WinMain 함수에 넘어온 매개변수의 값이 Embedding 옵션을 포함하고 있는지를 검사하여 클래스 팩토리 COM 개체를 등록하는 작업을 수행하여야 한다. 로컬 서버가 종료하면 등록된 클래스 팩토리 COM 개체를 해제해야 한다.
1. COM 은 내부적으로 등록된 클래스 팩토리 COM 개체를 저장하는 ROT(Running Object Table) 이라고 하는 테이블을 관리한다.
2. 클라이언트가 CoGetClassObject 함수를 호출할 때 COM은 먼저 이 ROT 에서 클라이언트가 요청한 CLSID에 대한 클래스 팩토리 COM 개체가 등록되어 있는지를 검사한다.
3. 만약 클래스 팩토리 COM 개체가 이 ROT에 등록되어 있지 않다면 COM 은 레지스트리에서 해당 CLSID와 관련된 로컬 서버에 대한 정보를 꺼내와 ‘Embedding’ 매개변수와 함께 해당 로컬 서버를 실행시킨다.
4. 이때 로컬 서버가 해야 하는 역할은 COM 이 가능한 한 빨리 클래스 팩토리 COM 개체를 찾을 수 있도록 해당 클래스 팩토리 COM 개체를 ROT에 등록하는 일을 수행하는 것이다.
5. 로컬 서버에서 클래스 팩토리 COM 개체를 등록하기 위해서는 COM 라이브러리의 CoRegisterClassObject 함수를 사용한다.
6. 이때 로컬 서버는 메시지 루프에 들어오기 전에 자신이 제공하는 모든 클래스 팩토리를 ROT에 등록해야만 한다.
1. 하나의 로컬 서버 인스턴스가 하나의 컴포넌트에 대하여 단 하나의 인스턴스만 서비스 할 경우
CLSCTX_LOCAL_SERVER, REGCLS_SINGLEUSE 2. 하나의 로컬 서버 인스턴스가 하나의 컴포넌트에 대하여 여러 개의 인스턴스를 서비스 할 경우
CLSCTX_LOCAL_SERVER, REGCLS_MULTI_SEPARATE 이것은 재미있는 상황을 발생시킨다. 예를 들어, 몇 개의 COM 개체를 등록하는 로컬 서버가 있다고 하자. 또한 이 로컬 서버 자신이 등록하고 있는 COM 개체 중의 하나를 사용할 필요가 있다고 하지. 위 문장을 사용하여 클래스 팩토리 COM 개체를 등록했다면 로컬 서버가 자기 자신의 COM 개체를 서비스하기 위해 또 다른 자기자신의 로컬 서버 인스턴스를 로드하게 될 것이다. 이것은 대부분의 상황에서 절대로 효율적이지 못하다. 따라서 로컬 서버를 자신의 인-프로세스 서버로 등록해야 한다. 이것을 하기 위해서는 CLSCTX_LOCAL_SERVER 와 CLSCTX_INPROC_SERVER 플래그를 결합해야 한다.
로컬 서버가 종료하면 ROT테이블에 등록된 클래스 팩토리를 제거해야 한다.
ROT 내부 테이블에 등록된 클래스 팩토리를 제거하기 위해 COM 라이브러리의 CoRevokeClassObject 함수를 사용한다.
CoRevokeClassObject 함수는 dwRegister 라는 DWORD 형의 매개변수 만을 갖는다. 이 매개변수에 지정되어야 하는 값은 CoRevokeClassObject 함수의 실행 결과로 리턴 되는 마지막 매개변수인 DWORD 포인터페이 저장된 값이다. 이 값은 클래스 팩토리에 대한 매직 쿠키(magic cookie)로 사용된다.
쿠키(Cookie)
쿠키란 용어는 어떤 것을 식별할 수 있는 데이터 구조체를 가리키는데 사용된다. 예를 들어, 클라이언트가 서버에게 리소스를 요청하면, 서버는 리소스를 할당하고 쿠키를 클라이언트에게 넘겨준다. 이때 클라이언트는 미래에 리소스를 식별하기 위해 쿠키를 사용할 수 있다. 클라이언트에게 있어서 쿠키란 서버를 제외하고는 아무런 의미가 없는 값일 수도 있다.
3.5.7. 로컬 서버 종료인-프로세스 서버 경우에는 수동적으로 메모리에서 언로드된다. 즉, COM 라이브러리가 CoFreeUnusedLibraries 함수를 호출하면, 인-프로세스 서버의 DllCanUnloadNow 함수가 호출되고, 이 함수에서는 인-프로세스 서버에 사용중인 COM 컴포넌트가 없을 때 S_OK 를 리턴 함으로써 CoFreeUnusedLibraries 함수가 DLL을 메모리에서 언로드하게 한다.
이에 반하여 로컬 서버인 경우에는 다음과 같은 조건을 만족하면 능동적으로 종료된다.
1. COM 개체 카운터가 현재 0이고, 클라이언트가 IClassFactory::LockServer(FALSE)를 호출함으로써 마지막 로크 카운터가 0이 될 때.
2. 로크 카운터가 현재 0이고, 클라이언트가 IUnknown::Release 를 호출하여 마지막 COM 개체 카운터가 0이 될 때
여기서 중요한 사실은 인-프로세스 서버에서는 COM 개체 카운터와 로크 카운터가 모두 0일 때 수동적으로 언로드되지만, 로컬서버에서는 위의 두 경우에 즉, 마지막으로 COM 개체 카운터나 로크 카운터가 감소하여 0 상태가 된 경우에 능동적으로 자신을 종료시켜야 한다는 것이다.
4. Visual C++ COM 컴파일러4.1. COM 재원 C++ 컴파일러 개요4.2. 형식 라이브러리4.3. #import 선행 처리기 지시어4.4. C++ 언어 확장4.5. COM 지원 클래스4.6. 내장 COM 지원 AddFront 예제 프로그램 작성 |
'COM, ATL' 카테고리의 다른 글
자동화에서 타입라이브러리 등록과 해제 코드 (0) | 2008.08.14 |
---|---|
COM 개체 구현 실습 (2) | 2008.08.14 |
com을 위한 기초 (3) | 2008.08.14 |
COM 이란 ... 참고자료....좀 잘못된 부분도 있는듯 하다 (0) | 2008.08.14 |
자동화 타입(BSTR, VARIANT, SAFEARRAY, UDT) (0) | 2008.08.14 |
Why type library marshaling? (0) | 2008.08.14 |
COM Architecture : Interface Marshaling, IDispatch interface (0) | 2008.08.14 |