Transcript inside_com

Inside

COM

Microsoft

s Component Objects Model 강사 : 이준근 ([email protected])

Chapter

1.

컴포넌트(Components)

1. 컴포넌트

– 일반적으로 애플리케이션은 하나의 2진 파일로 구성된다.

– 컴파일러가 애플리케이션을 만들면, 애플리케이션은 다음 버전 을 다시 컴파일하여 만들기 전까지는 변경되지 않는다.

– 실행파일이 현실의 변화에 맞게 변화가 가능하려면 하나의 덩어 리로 된 애플리케이션을 각각의 조각, 즉 컴포넌트로 분리하여야 한다.

Monolithic Application Component Application Component A Component C Component B Component D – 새로운 컴포넌트는 애플리케이션을 구성하는 현재 컴포넌트를 대체할 수 있다.

1. 컴포넌트

– 컴포넌트는 작은 애플리케이션과도 같다.

– 컴포넌트는 컴파일되고, 링크되고, 사용될 수 있는 코드의 2진파 일 형태로 구성된다.

– 애플리케이션을 변경하고 강화하는 것은 이러한 구성원중의 하 나를 새로운 버전으로 교체하는 것이다.

– COM (Component Object Model)은 컴포넌트를 만들고 이 컴포 넌트에서 애플리케이션을 구축하는 방법에 대한 명세이다.

2. 컴포넌트의 장점 • 1. 애플리케이션의 커스터마이징

– 개별적인 컴포넌트는 사용자의 필요를 만족시키기 쉬운 형태의 컴포넌트로 쉽게 대체할 수 있기 때문에 기업 아키텍처에 아주 적합하다.

• 2. 컴포넌트 라이브러리

– 컴포넌트 아키텍처의 중대한 규약중의 하나는 빠른 속도로 애플 리케이션을 개발하는 것이 가능하다.

– 마치 레고 블록처럼 애플리케이션을 만들기 위해서 소프트웨어 부품을 조립할 수 있도록 해준다.

• 3. 분산 컴포넌트

– 로컬 시스템 상에서 애플리케이션은 실제로 컴포넌트들이 접속 가능한 지역에 위치하였는지를 상관하지 않아도 된다.

– 원격 컴포넌트들 또한 그들이 접속 가능한 위치에 있는지를 문제 삼지 않는다.

3. 컴포넌트 요구사항 • 1. 컴포넌트는 동적으로 링크되어야 한다.

– DLL로 만들어져야 한다.

– 궁극적인 목표는 애플리케이션이 실행하는 동안 엔드 유저가 애 플리케이션에서 사용하는 컴포넌트를 대체하도록 하는 것이다.

• 2. 컴포넌트는 어떻게 구현되었는지에 대한 구체적인 사 항을 캡슐화해야 한다.

– 컴포넌트를 새로운 컴포넌트로 대체하기 위해서는, 시스템은 기 존의 컴포넌트와의 연결을 해제하고 새로운 컴포넌트와 연결을 설정해야 한다.

– 새로운 컴포넌트는 기존의 컴포넌트와 같은 방법으로 연결되어 야 한다.

– 클라이언트는 인터페이스를 이용하여 다른 컴포넌트와 연결된다.

– 컴포넌트가 인터페이스의 변경없이 다시 만들어진다면, 클라이 언트는 변경될 필요가 없다.

– 컴포넌트와 클라이언트는 그들의 인터페이스를 절대로 변경시켜 서는 안된다.

3. 컴포넌트 요구사항

– 클라이언트에서 컴포넌트를 자유로이 상용할 수 있으려면 • 1. 컴포넌트는 구현에 사용된 컴퓨터 언어를 숨겨야 한다.

• 2. 컴포넌트는 2진 파일 형식으로 전달되어야 한다.

• 3. 컴포넌트는 기존의 사용자를 그대로 유지하면서 업그레이드되어 야 한다.

• 4. 클라이언트는 원격 컴포넌트도 로컬 컴포넌트를 다루는 것과 같 은 방법으로 다룰 수 있어야 한다.

4. COM 컴포넌트는

… – COM컴포넌트는 Win32 DLL혹은 EXE파일로서 분산된 실행 코 드로 구성된다.

– COM컴포넌트는 동적으로 링크된다. COM은 동적으로 컴포넌트 를 링크하기 위해서 DLL을 사용한다.

– COM컴포넌트는 완전히 언어 독립적이다.

– COM컴포넌트는 2진 파일 형태로 전달된다.

– COM컴포넌트는 기존의 클라이언트에 아무런 변화를 가하지 않 고 업그레이드 할 수 있다.

– COM컴포넌트는 네트워크 상에서 투명하게 재위치 될 수 있다.

원격 시스템의 컴포넌트는 로컬 시스템에서의 컴포넌트와 유사 하게 다루어 진다.

5. COM 라이브러리

– COM은 COM라이브러리인 API를 가진다. 이것은 모든 클라이언 트와 컴포넌트에게 유용한 컴포넌트관리 서비스를 제공한다.

– COM라이브러리는 대부분의 중요한 작업이 모든 컴포넌트에 대 해서 동일할 것을 보장하도록 작성되었다.

Chapter

2.

인터페이스(Interface)

1. 인터페이스

– 인터페이스는 두 가지 다른 객체에 대한 연결을 제공한다.

– DLL에 대한 인터페이스는 DLL에 의해서 노출되는 함수의 집합 이다.

– COM에서 인터페이스는 함수 포인터의 배열을 가지고 있는 특정 메모리 구조체이다.

– 각각의 배열의 요소는 컴포넌트에 의해서 구현된 함수에 대한 주 소를 포함하고 있다.

– C++에서는 추상 베이스 클래스를 사용하여 COM인터페이스를 구현한다.

2. 인터페이스는 모든 것이다.

– 인터페이스는 COM에서 모든 것이다.

– 클라이언트에게 컴포넌트는 인터페이스의 집합이다.

– 클라이언트는 오직 인터페이스 만을 통해서 COM컴포넌트와 통 신한다.

– 컴포넌트가 애플리케이션에서 제거되고 다른 컴포넌트로 대체되 어도, 새로운 컴포넌트가 기존의 컴포넌트와 같은 인터페이스를 지원하는 한, 애플리케이션은 제대로 작동할 것이다.

• 인터페이스를 사용하는 두 가지 이유

– 1. 인터페이스는 시스템의 어떠한 변경에 의해서도 영향을 받지 않도록 한다.

– 2. 인터페이스는 클라이언트가 같은 방법으로 다른 컴포넌트를 처리할 수 있도록 한다.

3. COM인터페이스 구현하기

#include using namespace std; class IX { public: virtual void FX1() = 0; virtual void FX2() = 0; }; class IY { public: virtual void FY1() = 0; virtual void FY2() = 0; }; class CA : public IX, public IY { public: virtual void FX1() { cout << "virtual void FX1()" << endl; }

3. COM인터페이스 구현하기

}; void main() } virtual void FX2() { cout << "virtual void FX2()" << endl; } virtual void FY1() { cout << "virtual void FY1()" << endl; } virtual void FY2() { cout << "virtual void FY2()" << endl; { CA c1; IX * px = &c1; px->FX1(); px->FX2(); IY * py = &c1; py->FY1(); py->FY2(); }

3. COM인터페이스 구현하기

– IX,IY는 완전한 COM인터페이스는 아니다. COM인터페이스이기 위해서는 IX,IY는 IUnknown이라는 인터페이스를 상속받아야 한 다.

#include #include using namespace std; interface IX { virtual void FX1() = 0; virtual void FX2() = 0; }; interface IY { virtual void FY1() = 0; virtual void FY2() = 0; }; IX IY

3. COM인터페이스 구현하기

– COM 인터페이스는 C++순수 추상 베이스 클래스로 구현된다.

– 하나의 COM컴포넌트는 다중 상속을 지원한다.

– C++클래스는 다중 인터페이스를 지원하는 컴포넌트를 구현하기 위해서 다중 상속을 사용한다.

4. 인터페이스의 내부

– COM 인터페이스는 순수 추상 베이스 클래스를 사용하여 C++로 구현된다.

– 순수 추상 베이스 클래스는 COM이 인터페이스를 요청하는 특정 메모리 구조체를 정의한다.

interface IX { virtual void FX1() = 0; virtual void FX2() = 0; virtual void FX3() = 0; virtual void FX4() = 0; }; &Fx1 pIX vtbl pointer &Fx2 &Fx3 &Fx4

4. 인터페이스의 내부

– 추상 베이스 클래스에 대한 포인터는 vtbl을 가리키는 vtbl포인터 를 가리킨다.

– 우연히도 설계를 결정할 때 COM인터페이스의 메모리 구성이 C++컴파일러가 추상 베이스 클래스를 위해 만드는 메모리 구성 과 동일하게 되었다.

– 모든 COM인터페이스는 IUnknown이라는 이름의 인터페이스에 서 상속받아야 한다.

– 이것은 vtbl의 첫 번째 세 개의 엔트리가 모든 COM인터페이스에 서 동일하다는 것을 의미한다.

– 인터페이스의 진짜 강력한 힘은 모든 클래스가 하나의 인터페이 스를 상속받는 다는 것이다.

– 이들 인터페이스가 클라이언트에 의해서 같은 방법으로 다루어 질 수 있다.

4. 인터페이스의 내부

#include #include using namespace std; interface IX { virtual void FX1() = 0; virtual void FX2() = 0; }; class CA : public IX { public: virtual void FX1() { cout << "class CA :virtual void FX1()" << endl; } virtual void FX2() { cout << "class CA :virtual void FX2()" << endl; } };

4. 인터페이스의 내부

class CB : public IX { public: virtual void FX1() { cout << "class CB :virtual void FX1()" << endl; } virtual void FX2() { cout << "class CB :virtual void FX2()" << endl; } }; void main() { CA c1; IX * px = &c1; px->FX1(); px->FX2(); CB c2; px = &c2; px->FX1(); px->FX2(); }

Chapter

3.

쿼리인터페이스 (QueryInterface)

1. 쿼리인터페이스

– COM클라이언트는 컴포넌트에 의해서 지원되는 인터페이스를 알지 못한다.

– 컴포넌트가 특정 인터페이스를 지원하는지를 알아내기 위해서는 클라이언트는 실행시에 컴포넌트에게 해당 인터페이스에 대해서 질문을 하게 된다.

– 컴포넌트에 대해서 클라이언트가 아는 것은 매우 제한적이다.

– 클라이언트가 컴포넌트에 대해서 아는 것이 적을 수록 컴포넌트 는 클라이언트에 영향을 미치지 않고 변경될 수 있다.

2. 인터페이스 쿼리하기

– 클라이언트는 항상 인터페이스를 통해서 컴포넌트와 통신한다.

– 클라이언트는 컴포넌트에 다른 인터페이스를 요청할 때도 인터 페이스를 사용한다.

– 인터페이스를 요청하기 위해서 클라이언트는 IUnknown인터페 이스를 사용한다.

interface IUnknown { virtual HRESULT __stdcall QueryInterface(const IID & iid, void ** ppv) = 0; virtual ULONG __stdcall AddRef() = 0; virtual ULONG __stdcall Release() = 0; }; – 클라이언트는 컴포넌트가 임의의 인터페이스를 지원하는지 여부 를 결정하기 위해서 QueryInterface라는 함수를 호출한다.

– 모든 COM인터페이스는 IUnknown에서 상속받기 때문에 모든 인터페이스는 그것의 vtbl의 첫 번째 세 개의 함수로 QueryInterface, AddRef, Release를 갖는다.

– 인터페이스가 vtbl에 첫 번째 세 개의 엔트리로 이 세 개의 함수 를 가지고 있지 않다면,COM인터페이스가 아니다.

2. 인터페이스 쿼리하기

IX Client pIX vtbl pointer CA QueryInterface AddRef Release Fx QueryInterface AddRef Release Fx

3. IUnknown 포인터 얻기 • 클라이언트는 어떻게 IUnknown 인터페이스를 얻을까?

– 컴포넌트를 만들고 IUnknown포인터를 리턴하는 CreateInstance라는 이름의 함수를 사용한다.

– IUnknown * CreateInstance() – 클라이언트는 new 연산자를 사용하지 않고 CreateInstance를 사용한다.

• QueryInterface

– IUnknown은 클라이언트가 컴포넌트가 특정 인터페이스를 지원 하는지에 대해서 알 수 있도록 하는 QueryInterface라는 이름의 멤버 함수를 갖는다.

– 컴포넌트가 특정 인터페이스를 지원한다면, QueryInterface는 그 인터페이스에 대한 포인터를 리턴한다.

virtual HRESULT __stdcall QueryInterface(const IID & iid, void ** ppv) = 0;

4. QueryInterface 사용하기

– pI라는 IUnknown포인터가 주어졌을 때, – 다른 인터페이스를 사용할 수 있는지를 알기 위해서 그 인터페이 스의 ID를 넘겨서 QueryInterface를 호출한다.

– QueryInterface가 성공한다면, 그 포인터를 사용할 수 있다.

// COMTest1.cpp : 콘솔 응용 프로그램에 대한 진입점을 정의합니다.

// #include "stdafx.h" #include #include using namespace std; interface IX : public IUnknown { virtual void FX1() = 0; virtual void FX2() = 0; }; const IID IID_IX = {0xc7e6e08c, 0xa100, 0x4ba8, {0x98, 0xcc, 0x84, 0xc6, 0x76, 0x25, 0x70, 0x3e}};

class CA : public IX { public: HRESULT __stdcall QueryInterface(const IID & iid, void ** ppv) { if (iid == IID_IUnknown) { *ppv = static_cast(this); } else if (iid == IID_IX) { *ppv = static_cast(this); } else { *ppv = NULL; return E_NOINTERFACE; } static_cast(*ppv)->AddRef(); return S_OK; } virtual ULONG __stdcall AddRef() { return S_OK; }

virtual ULONG __stdcall Release() { return S_OK; } virtual void FX1() { cout << "class CA :virtual void FX1()" << endl; } virtual void FX2() { cout << "class CA :virtual void FX2()" << endl; } }; void main() { IUnknown * pIUnknown = static_cast(new CA); pIUnknown->AddRef(); IX * pIX = NULL; HRESULT hr = pIUnknown->QueryInterface(IID_IX,(void **)&pIX); if (SUCCEEDED(hr)) { pIX->FX1(); } }

5. 다중 상속과 캐스팅

CA::this – 일반적으로 다른 타입으로 포인터를 캐스팅하는 것은 그 값을 변 경시키지 않는다.

– 다중 상속을 지원하기 위해서 C++는 클래스 포인터의 값을 변경 시킨다.

– class CA : public IX, public IY – CA는 IX와 IY양쪽을 상속받기 때문에 IX와 IY포인터를 사용할 수 있는 곳이면 CA에 대한 포인터를 사용할 수 있다.

Virtual Function Table IX vtbl pointer (IX *)CA::this (IY *)CA::this IY vtbl pointer instance data for CA QueryInterface AddRef Release Fx QueryInterface AddRef Release Fy IX IY CA

5. 다중 상속과 캐스팅

– CA의 this포인터는 IX가상 함수 테이블 포인터를 가리킨다.

– 그러므로 IX포인터 대신 CA의 this포인터를 사용할 수 있다.

– 하지만 CA의 this포인터는 IY vtbl포인터를 가리키지 않는다.

– 그러므로 CA의 this포인터는 IY포인터가 넘겨지기를 원하는 함 수에 넘겨지기 전에 변경되어야 한다.

– CA의 this포인터를 변경시키려면 컴파일러는 IY vtbl포인터의 오 프셋을 CA의 this 포인터에 추가시킨다.

– QueryInterface함수에서 ppv에 값을 저장하기 전에 this포인터 를 캐스트를 하여야 한다.

– static_cast(this) != static_cast(this) – 그러므로 인터페이스의 포인터를 리턴할 경우에는 언제나 this를 사용하여 Cast후 리턴하여야 한다.

5. QueryInterface의 규칙 • 모든 QueryInterface함수는 반드시 다음의 규칙을 따라 야 한다.

– 1. 항상 같은 IUnknown을 얻는다.

– 2. 이전에 임의의 인터페이스를 얻을 수 있었다면 그 인터페이스 를 계속 얻을 수 있다.

– 3. 자신이 가지고 있는 인터페이스는 얻을 수 있다.

– 4. 언제라도 자신이 시작한 곳으로 되돌아 갈 수 있다.

– 5. 그 외의 다른 곳으로부터 얻을 수 있었다면 어디에서라도 얻 을 수 있다.

5. QueryInterface의 규칙 • 1. 항상 같은 IUnknown을 얻는다.

– IUnknown에 컴포넌트의 인스턴스를 요청할 때는 언제나 어떻게 인터페이스를 쿼리하였는지에 관계없이 같은 포인터 값을 얻을 수 있다.

// COMTest1.cpp : 콘솔 응용 프로그램에 대한 진입점을 정의합니다.

// #include "stdafx.h" #include #include using namespace std; interface IX : public IUnknown { virtual void FX1() = 0; virtual void FX2() = 0; }; interface IY : public IUnknown { virtual void FY1() = 0; virtual void FY2() = 0; }; const IID IID_IX = {0xc7e6e08c, 0xa100, 0x4ba8, {0x98, 0xcc, 0x84, 0xc6, 0x76, 0x25, 0x70, 0x3e}}; const IID IID_IY = {0xa047393, 0x78f, 0x4025, { 0xbf, 0x10, 0xe4, 0xd6, 0xe3, 0xfe, 0x59, 0xa4 }}; class CA : public IX,public IY { public:

HRESULT __stdcall QueryInterface(const IID & iid, void ** ppv) { if (iid == IID_IUnknown) { *ppv = static_cast(this); } else if (iid == IID_IX) { *ppv = static_cast(this); } else if (iid == IID_IY) { *ppv = static_cast(this); } else { *ppv = NULL; return E_NOINTERFACE; } static_cast(*ppv)->AddRef(); return S_OK; } virtual ULONG __stdcall AddRef() { return S_OK; } virtual ULONG __stdcall Release() { return S_OK; }

virtual void FX1() { cout << "class CA :virtual void FX1()" << endl; } virtual void FX2() { cout << "class CA :virtual void FX2()" << endl; } virtual void FY1() { cout << "class CA :virtual void FY1()" << endl; } virtual void FY2() { cout << "class CA :virtual void FY2()" << endl; } }; void main() { IUnknown * pIUnknown = static_cast(new CA); pIUnknown->AddRef(); IX * pIX = NULL; HRESULT hr = pIUnknown->QueryInterface(IID_IX,(void **)&pIX); if (SUCCEEDED(hr)) { pIX->FX1(); }

} IY * pIY = NULL; hr = pIUnknown->QueryInterface(IID_IY,(void **)&pIY); if (SUCCEEDED(hr)) { pIY->FY1(); } IUnknown * pI1 = NULL; IUnknown * pI2 = NULL; pIX->QueryInterface(IID_IUnknown,(void **)&pI1); pIY->QueryInterface(IID_IUnknown,(void **)&pI2); if (pI1 == pI2) { cout << "같은 컴포넌트 입니다." << endl; }

5. QueryInterface의 규칙 • 2. 이전에 임의의 인터페이스를 얻을 수 있었다면 그 인 터페이스를 계속 얻을 수 있다.

– QueryInterface가 일단 주어진 인터페이스에 대해서 성공한다면 같은 컴포넌트에서 계속되는 호출에 있어서 항상 성공할 것이다.

• 3. 자신이 가지고 있는 인터페이스는 얻을 수 있다.

– IX 인터페이스를 갖고 있다면 IX 인터페이스에 그것을 요청할 수 있고 IX포인터를 다시 얻을 수 있다.

• 4. 언제라도 자신이 시작한 곳으로 되돌아 갈 수 있다.

– IX인터페이스 포인터를 가지고 IY인터페이스 요청에 성공하면 IY 인터페이스를 가지고도 IY인터페이스를 얻을 수 있다.

void main() { IUnknown * pIUnknown = static_cast(new CA); pIUnknown->AddRef(); IX * pIX = NULL; HRESULT hr = pIUnknown->QueryInterface(IID_IX,(void **)&pIX); if (SUCCEEDED(hr)) { pIX->FX1(); } IY * pIY = NULL; hr = pIUnknown->QueryInterface(IID_IY,(void **)&pIY); if (SUCCEEDED(hr)) { pIY->FY1(); } IX * pI1 = NULL; IY * pI2 = NULL; pIX->QueryInterface(IID_IY,(void **)&pI2); pI2->QueryInterface(IID_IX,(void **)&pI1); if (pI1 == pIX) { cout << "같은 인터페이스입니다." << endl; } }

5. QueryInterface의 규칙 • 5. 그 외의 다른 곳으로부터 얻을 수 있었다면 어디에서 라도 얻을 수 있다.

– IX인터페이스에서 IY인터페이스를 얻을 수 있고, IY인터페이스에 서 IZ인터페이스를 얻을 수 있다면 또한 IX인터페이스에서 IZ인 터페이스를 얻을 수 있다.

6. 새로운 버전의 컴포넌트를 다루기

– COM에서 인터페이스는 변경되지 않는다.

– 인터페이스가 노출되고 클라이언트가 그 인터페이스를 사용하면 그것은 절대로 변경되지 않는다.

– 모든 인터페이스는 유일한 인터페이스ID(unique interface identifier: IID)를 갖는다.

– 인터페이스가 변경된다기 보다는 정확히 새로운 인터페이스를 만들고 이 새로운 인터페이스는 새로운 IID를 갖는것이다.

• 다음의 조건 중 하나라도 변경된다면 새로운 인터페이스 를 만들어야 한다.

– 인터페이스에서 함수의 수 – 인터페이스에서 함수의 순서 – 함수에서 파라미터의 수 – 함수에서 파라미터의 순서 – 함수에서 파라미터의 타입

6. 새로운 버전의 컴포넌트를 다루기

– 함수에서 리턴 값 – 함수에서 파라미터의 의미 – 인터페이스에서 함수의 의미

Chapter

4.

참조 카운팅 (Reference Counting)

1. Lifetime 컨트롤

– 컴포넌트는 클라이언트가 직접 Delete하지 않고, 인터페이스를 사용하기를 요구할 때, 그리고 그 인터페이스의 사용을 마쳤을 때 컴포넌트에게 알려주는 것이다.

– 클라이언트가 컴포넌트의 인터페이스의 사용을 마쳤을 때를 추 적하는 것은 AddRef와 Release이다.

– 클라이언트가 컴포넌트에서 인터페이스를 얻을 때 참조 카운트 를 증가시킨다.

– 클라이언트가 한 인터페이스에 대한 사용이 끝났을 때 참조 카운 트를 감소시킨다.

– 참조 카운트가 0이 되면 컴포넌트는 스스로 메모리에서 해제된 다.

– 클라이언트는 현존하는 인터페이스에 대한 다른 참조를 만들 때 참조 카운트를 증가시킨다.

2. 참조 카운트 규칙 • 1. 리턴하기 전에 AddRef를 호출하라.

– 인터페이스를 리턴하는 함수는 항상 리턴하기 전에 포인터에 대 한 AddRef를 호출해야 한다.

– QueryInterface와 CreateInstance함수

• 2. 작업을 마친 후에는 Release를 호출하라.

– 하나의 인터페이스를 사용하여 작업을 마쳤을 때에는 반드시 그 인터페이스에 대한 Release를 호출해야 한다.

• 3. 할당한(=) 후에 AddRef를 호출하라.

– 다른 인터페이스 포인터에 대해서 인터페이스 포인터를 할당하 였다면 AddRef를 호출하라.

IUnknown * CreateInstance() { IUnknown * pIUnknown = static_cast(new CA); pIUnknown->AddRef(); return pIUnknown; } void main() { IUnknown * pIUnknown = CreateInstance(); IX * pIX = NULL; HRESULT hr = pIUnknown->QueryInterface(IID_IX,(void **)&pIX); if (SUCCEEDED(hr)) { pIX->FX1(); pIX->Release (); } pIUnknown->Release(); }

IUnknown * CreateInstance() { IUnknown * pIUnknown = static_cast(new CA); pIUnknown->AddRef(); return pIUnknown; } void main() { IUnknown * pIUnknown = CreateInstance(); IX * pIX = NULL; HRESULT hr = pIUnknown->QueryInterface(IID_IX,(void **)&pIX); pIUnknown->Release(); if (SUCCEEDED(hr)) { pIX->FX1(); IX * pIX2 = pIX; pIX2->AddRef(); pIX->FX2(); pIX2->Release(); pIX->Release(); } }

#include "stdafx.h" #include #include using namespace std; interface IX : public IUnknown { virtual void FX1() = 0; virtual void FX2() = 0; }; interface IY : public IUnknown { virtual void FY1() = 0; virtual void FY2() = 0; }; const IID IID_IX = {0xc7e6e08c, 0xa100, 0x4ba8, {0x98, 0xcc, 0x84, 0xc6, 0x76, 0x25, 0x70, 0x3e}}; const IID IID_IY = {0xa047393, 0x78f, 0x4025, { 0xbf, 0x10, 0xe4, 0xd6, 0xe3, 0xfe, 0x59, 0xa4 }}; class CA : public IX,public IY { public: CA() : m_cRef(0) { cout << "객체 생성" << endl; }

~CA() { cout << "객체 소멸" << endl; } HRESULT __stdcall QueryInterface(const IID & iid, void ** ppv) { if (iid == IID_IUnknown) { *ppv = static_cast(this); } else if (iid == IID_IX) { *ppv = static_cast(this); } else if (iid == IID_IY) { *ppv = static_cast(this); } else { *ppv = NULL; return E_NOINTERFACE; } static_cast(*ppv)->AddRef(); return S_OK; } virtual ULONG __stdcall AddRef() { m_cRef++; return m_cRef; }

private: }; virtual ULONG __stdcall Release() { if (--m_cRef == 0) { delete this; return 0; } return m_cRef; } virtual void FX1() { cout << "class CA :virtual void FX1()" << endl; } virtual void FX2() { cout << "class CA :virtual void FX2()" << endl; } virtual void FY1() { cout << "class CA :virtual void FY1()" << endl; } virtual void FY2() { cout << "class CA :virtual void FY2()" << endl; } long m_cRef;

IUnknown * CreateInstance() { IUnknown * pIUnknown = static_cast(new CA); pIUnknown->AddRef(); return pIUnknown; } void main() { IUnknown * pIUnknown = CreateInstance(); IX * pIX = NULL; HRESULT hr = pIUnknown->QueryInterface(IID_IX,(void **)&pIX); pIUnknown->Release(); if (SUCCEEDED(hr)) { pIX->FX1(); IX * pIX2 = pIX; pIX2->AddRef(); pIX->FX2(); pIX2->Release(); pIX->Release(); } }

3. 참조 카운트 규칙 • 1. 외부 전달 파라미터 규칙 (Out Parameter Rule)

– 외부 전달 파라미터로 혹은 리턴 값으로 새로운 인터페이스 포인 터를 리턴하는 함수는 인터페이스 포인터에 대한 AddRef를 호 출해야 한다.

• 2. 내부 전달 파라미터 규칙(In Parameter Rule)

– 함수에 넘겨진 인터페이스 포인터는 그 함수가 이미 호출자의 수 명 안에 중첩되어 있기 때문에 AddRef와 Release를 호출할 필 요가 없다.

• 3. 양방향 전달 파라미터 규칙(In-Out Parameter Rule)

– 함수는 다른 인터페이스 포인터를 가지고 내부 전달 파라미터를 덮어쓰기 전에 양방향 전달 파라미터를 통해서 넘어온 인터페이 스 포인터에 대한 Release를 호출해야 한다.

– 그 함수는 또한 호출자에게 리턴하기 전에 외부 전달 파라미터에 대한 AddRef를 호출해야 한다.

3. 참조 카운트 규칙 • 4. 지역 변수 규칙

– 함수의 수명 동안만 존재하는 인터페이스 포인터의 로컬 복사는 AddRef와 Release쌍을 필요로 하지 않는다.

Chapter

5.

동적 링크 (Dynamic Linking)

// COMDLL1.cpp : DLL 응용 프로그램에 대한 진입점을 정의합니다.

// #include "stdafx.h" #include #include using namespace std; BOOL APIENTRY DllMain( HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved { return TRUE; } ) interface IX : public IUnknown { virtual void FX1() = 0; virtual void FX2() = 0; }; interface IY : public IUnknown { virtual void FY1() = 0; virtual void FY2() = 0; }; const IID IID_IX = {0xc7e6e08c, 0xa100, 0x4ba8, {0x98, 0xcc, 0x84, 0xc6, 0x76, 0x25, 0x70, 0x3e}}; const IID IID_IY = {0xa047393, 0x78f, 0x4025, { 0xbf, 0x10, 0xe4, 0xd6, 0xe3, 0xfe, 0x59, 0xa4 }};

class CA : public IX,public IY { public: CA() : m_cRef(0) { cout << "객체 생성" << endl; } ~CA() { cout << "객체 소멸" << endl; } HRESULT __stdcall QueryInterface(const IID & iid, void ** ppv) { if (iid == IID_IUnknown) { *ppv = static_cast(this); } else if (iid == IID_IX) { *ppv = static_cast(this); } else if (iid == IID_IY) { *ppv = static_cast(this); } else { *ppv = NULL; return E_NOINTERFACE; } static_cast(*ppv)->AddRef(); return S_OK; }

virtual ULONG __stdcall AddRef() { m_cRef++; return m_cRef; } virtual ULONG __stdcall Release() { if (--m_cRef == 0) { delete this; return 0; } return m_cRef; } virtual void FX1() { cout << "class CA :virtual void FX1()" << endl; } virtual void FX2() { cout << "class CA :virtual void FX2()" << endl; }

virtual void FY1() { cout << "class CA :virtual void FY1()" << endl; } virtual void FY2() { cout << "class CA :virtual void FY2()" << endl; } long m_cRef; private: }; extern "C" __declspec(dllexport) IUnknown * CreateInstance() { IUnknown * pIUnknown = static_cast(new CA); pIUnknown->AddRef(); return pIUnknown; }

// COMTest1.cpp : 콘솔 응용 프로그램에 대한 진입점을 정의합니다.

// #include "stdafx.h" #include #include using namespace std; interface IX : public IUnknown { virtual void FX1() = 0; virtual void FX2() = 0; }; interface IY : public IUnknown { virtual void FY1() = 0; virtual void FY2() = 0; }; const IID IID_IX = {0xc7e6e08c, 0xa100, 0x4ba8, {0x98, 0xcc, 0x84, 0xc6, 0x76, 0x25, 0x70, 0x3e}}; const IID IID_IY = {0xa047393, 0x78f, 0x4025, { 0xbf, 0x10, 0xe4, 0xd6, 0xe3, 0xfe, 0x59, 0xa4 }}; typedef IUnknown * (*CREATEFUNCPTR)();

IUnknown * CallCreateInstance(char * name) { HINSTANCE hComponent = ::LoadLibrary(name); if (hComponent == NULL) { cout << "DLL이 없습니다." << endl; return NULL; } CREATEFUNCPTR CreateInstance = (CREATEFUNCPTR)::GetProcAddress(hComponent,"CreateInstance"); if (CreateInstance == NULL) { cout << "CreateInstance함수가 존재하지 않습니다." << endl; return NULL; } return CreateInstance(); }

void main() { IUnknown * pIUnknown = CallCreateInstance("COMDLL1.DLL"); if (pIUnknown == NULL) { return; } IX * pIX = NULL; HRESULT hr = pIUnknown->QueryInterface(IID_IX,(void **)&pIX); pIUnknown->Release(); if (SUCCEEDED(hr)) { pIX->FX1(); IX * pIX2 = pIX; pIX2->AddRef(); pIX->FX2(); pIX2->Release(); pIX->Release(); } }

Chapter

6.

HRESULT, GUID, 그리고 레지스트리

1. HRESULT

C R Facility Serverity Return Code typedef LONG HRESULT; – COM컴포넌트는 HRESULT를 리턴한다.

– HRESULT는 5부분으로 나누어진 32비트 값이다.

– severity code • 00 – Success, 01 – Informational, 10 – Warning, 11 – Error – C - is the Customer code flag – R - is a reserved bit – facility 코드는 운영체제의 부분에 대한 정보를 갖고 있다.

1. HRESULT

• • • • • • • • • • • • • • • • // Define the facility codes // #define FACILITY_WINDOWS #define FACILITY_STORAGE #define FACILITY_SSPI #define FACILITY_SETUPAPI #define FACILITY_RPC #define FACILITY_WIN32 #define FACILITY_CONTROL #define FACILITY_NULL 10 0 #define FACILITY_MSMQ 14 #define FACILITY_MEDIASERVER 13 8 3 9 15 1 7 #define FACILITY_INTERNET #define FACILITY_ITF #define FACILITY_DISPATCH #define FACILITY_CERT 12 4 2 11

1. HRESULT

– SUCCEEDED와 FAILED매크로를 사용하여 성공과 실패를 알아낸다.

일반적인 HRESULT 값 이름 S_OK NOERROR S_FALSE E_UNEXPECTED E_NOTIMPL E_NOINTERFACE E_OUTOFMEMORY E_FAIL 의미 함수가 성공하였다.

S_OK와 같다 함수가 성공하였으나 BOOL값으로 false를 리턴하였다.

예상치 못한 에러 멤버 함수가 구현되지 않았다.

컴포넌트는 요청한 인터페이스를 지원하지 않는다.

컴포넌트는 요청된 메모리를 할당할 수 없다.

지정되지 않은 에러

1. HRESULT

– 임의의 HRESULT가 FACILITY_WIN32 facility코드를 가지고 있다면 Return코드는 Win32에러이다.

– 0x80070103 : • 7 => Facility Code • 16진수 103 => 십진수 259L (Win32에러코드 259이다.) ERROR_NO_MORE_ITEMS 에러 • FormatMessage를 이용하여 에러 정보를 얻어낼 수 있다.

– HRESULT는 여러 개의 실패 코드뿐만 아니라 여러 성공 코드도 갖는다.

– 이것은 SUCCEEDED와 FAILED매크로를 사용하여 성공, 실패를 얻어낼 수 있다.

– HRESULT 값은 S_OK혹은 E_FAIL과 직접적으로 비교하지 말고 SUCCEDED와 FAILED매크로를 사용한다.

• 자신의 HRESULT코드를 정의하기 – 자신의 리턴코드를 지정하려면 FACILITY_ITF facility코드를 정의에 포 함하여야 한다.

– 이 facility코드는 클라이언트에게 이 특정 리턴 코드는 이 특정 인터페 이스에 지정되어 있는 것이라는 것을 알린다.

1. HRESULT

– 자신의 HRESULT 값을 정의하는 데에는 일반적인 규칙이 있다.

– 1. 0x0000부터 0x01FF사이의 값을 할당하지 말아야 한다. 이 값은 COM에 정의된 FACILITY_ITF코드로 예약 되어 있다.

– 2. FACILITY_ITF 에러 코드를 전달하지 말라.

– 3. 가능한 한 보편적인 COM성공과 실패 코드를 사용하라.

– 4. 자신의 HRESULT를 정의하지 말라.대신 함수에서 외부 전달 파리미 터를 사용하라.

– HRESULT를 만들려면 MAKE_HRESULT매크로를 사용한다.

– 이것은 serverity, facility, 그리고 리턴 값을 주면 된다.

– MAKE_HRESULT(SERVERITY_ERROR,FACILITY_ITF,100) – MAKE_HRESULT(SERVERITY_ERROR,FACILITY_ITF,101)

2. GUID

– IID는 GUID라고 하는 128비트 구조체로 정의된 타입이다.

– 인터페이스가 유일하다고 보장할 수 있는 방법을 GUID를 통하 여 제공한다.

– 이름은 중복될 수 있다.

// {1454E83A-0FE3-4914-A0DB-81ED38EF99CF} static const IID IID_IX = { 0x1454e83a, 0xfe3, 0x4914, { 0xa0, 0xdb, 0x81, 0xed, 0x38, 0xef, 0x99, 0xcf } }; – 하나의 문장으로 GUID를 선언하고 정의하기를 원한다면 OBJBASE.H에 정의되어 있는 DEFINE_GUID매크로를 사용한다.

// {4E5C137C-A140-43f4-808D-F68F0C5FCB4E} DEFINE_GUID(IID_IX, 0x4e5c137c, 0xa140, 0x43f4, 0x80, 0x8d, 0xf6, 0x8f, 0xc, 0x5f, 0xcb, 0x4e); – OBJBASE.H는 위의 매크로를 다음과 같이 치환한다.

– extern “ C ” const GUID IID_IX – INITGUID.H 헤더를 추가로 포함하면

3. 레지스트리

2. GUID

extern “ C ” const GUID IID_IX = { 0x4e5c137c, 0xa140, 0x43f4, {0x80, 0x8d, 0xf6, 0x8f, 0xc, 0x5f, 0xcb, 0x4e} }; – IID 선언과 구현이 필요한 곳에는 OBJBASE.H와 INITGUID.H 헤더를 포함하고 – IID 선언만 필요한 파일에서는 OBJBASE.H만 포함하면 된다.

__inline BOOL operator==(const GUID& guidOne, const GUID& guidOther) { return !memcmp(&guidOne,&guidOther,sizeof(GUID)); } – 컴포넌트를 식별하기 위해서도 GUID를 사용한다.

– CoCreaetInstance는 컴포넌트를 식별하기 위해서 GUID를 사용 한다.

– GUID는 16바이트(128비트)이기 때문에 값으로 전달하면 리소 스 낭비가 심하다.그러므로 참조로 넘겨준다.

3. 레지스트리

– 컴포넌트를 생성하는 CoCreateInstance()는 생성할 컴포넌트를 식별하기 위해서 파일 이름을 이용하지 않고 CLSID값을 이용한 다.

– COM은 HKEY_CLASSES_ROOT의 CLSID키를 사용한다.

– HKEY_CLASSES_ROOT의 CLSID키에는 시스템에 설치된 모든 컴포넌트에 대한 CLSID가 나열되어 있다.

– CLSID키에는 InprocServer32를 갖는다.

– InprocServer32에는 DLL의 파일이름을 갖는다.

– InprocServer32라는 이름은 DLL이 인프로세스 서버이기 때문이 다.

3. 레지스트리

– 레지스트리의 HKEY_CLASSES_ROOT에서 보는 대부분의 subkey는 ProgID이다.

– ProgID는 프로그래머가 알아보기 쉬운 문자열을 CLSID에 매핑한 다.

– VisualBasic과 같은 컴퓨터 언어는 CLSID가 아닌 ProgID로 컴포 넌트를 식별한다.

– ProgID는 유일하다는 것을 보장하지는 않는다. 그래서 잠재적으 로 이름이 충돌할 수 있다.( ProgID는 작업이 쉽다.) • .. – CLSIDFromProgID와 ProgIDFromCLSID라는 함수를 제공한다.

void main() { CLSID clsid; CLSIDFromProgID(L"bidispl.bidispl",&clsid); wchar_t wtemp[1024]; StringFromGUID2(clsid,wtemp,1024); char temp[1024]; wcstombs(temp,wtemp,1024); cout << temp << endl; }

3. 레지스트리

– 컴포넌트를 등록하기 위해서 REGSVR32.EXE를 호출한다.

– 이 함수는 LoadLibrary로 DLL을 로드하고, GetProcAddress를 호 출하여 함수의 주소를 얻고 DllRegisterServer를 호출한다.

4. COM 라이브러리 함수 • COM 라이브러리 초기화하기

– 프로세스는 COM라이브러리를 초기화하기 위해서 CoInitialize를 호출해야 한다.

– 프로세스가 COM라이브러리로 작업을 마쳤을 때 CoUninitialize 를 호출해야 한다.

– HRESULT CoInitialize(void * reserved) // must be NULL – void CoUninitialize(); – COM라이브러리는 프로세스당 단 한 번만 초기화해야 한다.

5. 문자열을 GUID로 변환하기

– 레지스트리는 CLSID의 표현으로 된 문자열을 포함한다.

– CLSID의 문자열에서 CLSID로 변환하는 함수가 필요하다.

– StringFromGUID2 StringFromCLSID StringFromIID StringFromGUID2 CLSIDFromString IIDFromString 텍스트 문자열 변환에서 타입 안정적인 CLSID 텍스트 문자열 변환에서 타입 안정적인 IID GUID를 텍스트 문자열로 변환 텍스트 문자열을 타입 안정적인 CLSID로 변환 텍스트 문자열을 타입 안정적인 IID로 변환

Chapter

7.

클래스 팩토리 (Class Factory)

1. CoCreateInstance

– 모든 컴포넌트는 클래스 팩토리를 이용하여 만들어진다.

– CoCreateInstance는 컴포넌트를 만들 때 클래스 팩토리를 사용 한다.

– COM 라이브러리는 컴포넌트를 만들기 위해 CoCreateInstance 라는 이름의 함수를 갖고 있다.

– CoCreateInstance는 CLSID를 인자로 취하고 컴포넌트에 해당하 는 인스턴스를 만든 컴포넌트의 인스턴스를 위한 인터페이스를 리턴한다.

STDAPI CoCreateInstance ( REFCLSID rclsid , LPUNKNOWN pUnkOuter , DWORD REFIID dwClsContext , riid , //Reference to the identifier of the interface LPVOID * ppv );

1. CoCreateInstance

– rclsid : 만들어질 컴포넌트의 CLSID이다.

– pUnkOuter : 통합에서 사용한다.

– dwClsContext : • CLSCTX_INPROC_SERVER : – 클라이언트는 같은 프로세스에서 실행하는 컴포넌트를 받아들일 것이다.

– 컴포넌트는 DLL로 구현되어야 한다.

• CLSCTX_INPROC_HANDLER : – 클라이언트는 인프로세스 핸들러를 사용할 것이다.

– 인프로세스 핸들러는 컴포넌트의 부분만을 구현하는 프로세스 내부에 있 는 컴포넌트이다.

• CLSCTX_LOCAL_SERVER : – 클라이언트는 다른 프로세스이지만 같은 기계에서 실행하는 컴포넌트를 사용할 것이다.

• CLSCTX_REMOTE_SERVER : – 클라이언트는 다른 기계에서 실행하는 컴포넌트를 받아들일 것이다.

• 대부분의 경우에 클라이언트는 컴포넌트가 어떠한 컨텍스트에 있는 지 신경쓰지 않아도 된다.

• CLSCTX_INPROC : – CLSCTX_INPROC_SERVER | CLSCTX_INPROC_HANDLER

1. CoCreateInstance

• CLSCTX_ALL : – CLSCTX_INPROC_SERVER | CLSCTX_INPROC_HANDLER | CLSCTX_LOCAL_SERVER | CLSCTX_REMOTE_SERVER • CLSCTX_SERVER : – CLSCTX_INPROC_SERVER | CLSCTX_LOCAL_SERVER | CLSCTX_REMOTE_SERVER – riid : 컴포넌트에서 사용하기 원하는 인터페이스의 IID이다. 이 인 터페이스에 대한 포인터는 마지막 파라미터로 리턴된다.

IX * pIX = NULL; HRESULT hr = ::CoCreateInstance(CLSID_Compontnt1,NULL,CLSCTX_ALL, IID_IX,(void **)pIX); if (SUCCEEDED(hr)) { pIX->Fx(); pIX->Release(); }

2. 클래스 팩토리(Class Factory)

– 내부적으로 CoCreateInstance는 직접 COM컴포넌트를 만들지 않는다.

– CoCreateInstance가 직접 COM컴포넌트를 만든다면 만들 수 있 는 컴포넌트가 제한되고, 여러 가지 다양한 방법으로의 객체이 불 가능할 것이다.

– 대신 CoCreateInstance는 필요한 컴포넌트를 만드는 클래스 팩 토리를 호출하여 만든다.

– 클래스 팩토리는 다른 컴포넌트를 만드는 작업을 하는 컴포넌트 이다.

– 클래스 팩토리는 오직 지정도니 CLSID에 해당하는 컴포넌트만을 만든다.

– 컴포넌트를 만드는 표준 인터페이스는 IClassFactory이다.

– CoCreateInstance로 만들어진 컴포넌트는 IClassFactory인터페 이스를 사용하여 만든 것이다.

– 클래스 팩토리를 사용하여 컴포넌트를 만드는 첫 번째 단계는 클 래스 팩토리 자체를 만드는 것이다.

2. 클래스 팩토리(Class Factory) • CoGetClassObject 사용하기

– CoCreateInstance는 CLSID를 가지고 해당하는 컴포넌트의 인터 페이스에 대한 포인터를 리턴한다.

– CLSID를 가지고 그 CLSID에 대한 클래스 팩토리에 포함된 인터 페이스의 포인터를 리턴하는 함수를 필요로 한다.

– 그 함수가 CoGetClassObject이고 COM라이브러리에 포함되어 있다.

2. 클래스 팩토리(Class Factory)

STDAPI CoGetClassObject( REFCLSID rclsid , DWORD dwClsContext , COSERVERINFO * pServerInfo , REFIID riid , LPVOID * ppv ); – rclsid : 필요한 컴포넌트의 CLSID – dwClsContext : – pServerInfo : DCOM에서 사용된다.

– riid : 클래스 팩토리의 ID – ppv : 클래스 팩토리의 Pointer – CoGetClassObject는 필요한 컴포넌트의 클래스 팩토리를 리턴하 지만, 컴포넌트 자체에 대한 포인터는 리턴하지 않는다.

2. 클래스 팩토리(Class Factory)

interface IClassFactory : public IUnknown { HRESULT __stdcall CreateInstance(IUnknown * pUnkOuter,REFIID riid, void ** ppvObject); HRESULT __stdcall LockServer(BOOL fLock); }; – IClassFactory::CreateInstnace() • pUnkOuter : 통합에서 사용하는 IUnknown의 포인터 • riid : 팩토리가 만들어야 하는 컴포넌트에서 제공하는 인터페이스의 IID값 • ppvObject : 인터페이스의 포인터를 리턴 받을 변수

2. 클래스 팩토리(Class Factory)

STDAPI CoCreateInstance ( REFCLSID DWORD { dwClsContext , REFIID riid rclsid , LPUNKNOWN , LPVOID * ppv ) *ppv = NULL; IClassFactory * pIFactory = NULL; pUnkOuter , HRESULT hr = CoGetClassObject(rclsid, dwClsContext, NULL, IID_IClassFactory,(void **)pIFactory); if (SUCCEEDED(hr)) { hr = pIFactory->CreateInstance(pUnkOuter,riid,ppv); } return hr; pIFactory->Release(); }

2. 클래스 팩토리(Class Factory)

– 클래스 팩토리는 하나의 CLSID에 해당하는 컴포넌트를 만든다.

• IClassFactory::CreateInstance가 CLSID를 파라미터로 받지 않는다.

• 클래스 팩토리가 만들어야 하는 CLSID는 이미 지정되어 있다.

– 특정 CLSID에 대한 클래스 팩토리는 그 컴포넌트를 구현하는 같 은 개발자에 의해서 만들어 진다.

– 클래스 팩토리는 레지스트리에 등록될 필요가 없다.

– 클래스 팩토리는 컴포넌트를 만드는 과정을 캡슐화한다.

• CoCreateInstance에서는 클래스의 CreateInstance함수만 호출하면 되지 컴포넌트 객체를 만드는 방법에 대해서는 알 필요가 없다.

– 클래스 팩토리 인스턴스는 하나의 CLSID에 해당하는 컴포넌트를 만든다.

– 클래스 팩토리의 목적은 컴포넌트를 만들고 클라이언트와 컴포넌 트가 가지고 있는 특정 요구로부터 가능한 한 분리될 수 있도록 이러한 지식을 캡슐화 하는 방법을 제공한다.

3. 클래스 팩토리 구현하기

• DllGetClassObject 사용하기

– CoCreateInstance는 컴포넌트의 클래스 팩토리를 만들기 위해 서 ::CoGetClassObject함수를 호출한다.

– CoGetClassObject는 클래스 팩토리를 만드는 DllGetClassObject함수를 호출한다.

• DllGetClassObject함수는 Dll Entry함수이다.

HRESULT DllGetClassObject( REFCLSID rclsid , REFIID riid , LPVOID* ppv ); – rclsid : 클래스 팩토리가 만들어야 하는 컴포넌트의 클래스ID – riid : 클래스 팩토리 인터페이스의 IID값 – ppv : 클래스 팩토리 인터페이스를 리턴 받을 변수

3. 클래스 팩토리 구현하기

COM Library CoGetClassObejct Class Factory생성 Client Call CoGetClassObject DllGetClassObject pIClassFactory Returns IClassFactory IClassFactory::CreateInstance호출 Returns IX IClassFactory 객체생성 pIX IX Calls IX::Fx

// COMDLL1.cpp : DLL 응용 프로그램에 대한 진입점을 정의합니다.

// #include "stdafx.h" #include #include #include #include using namespace std; static HMODULE g_hModule = NULL; static long g_cComponents = 0; static long g_cServerLocks = 0; BOOL APIENTRY DllMain( HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) { if (ul_reason_for_call == DLL_PROCESS_ATTACH) { g_hModule = (HMODULE)hModule; } return TRUE; }

interface IX : public IUnknown { virtual void FX1() = 0; virtual void FX2() = 0; }; interface IY : public IUnknown { virtual void FY1() = 0; virtual void FY2() = 0; }; const IID IID_IX = {0xc7e6e08c, 0xa100, 0x4ba8, {0x98, 0xcc, 0x84, 0xc6, 0x76, 0x25, 0x70, 0x3e}}; const IID IID_IY = {0xa047393, 0x78f, 0x4025, { 0xbf, 0x10, 0xe4, 0xd6, 0xe3, 0xfe, 0x59, 0xa4 }}; const IID CLSID_Component1 = {0x3f8e8b4b, 0xaee2, 0x4c99, { 0xad, 0x31, 0x98, 0x5c, 0x39, 0x8e, 0xd6, 0xc4 } }; class CA : public IX,public IY { public: CA() : m_cRef(0) { cout << "객체 생성" << endl; } ~CA() { cout << "객체 소멸" << endl; }

HRESULT __stdcall QueryInterface(const IID & iid, void ** ppv) { if (iid == IID_IUnknown) { *ppv = static_cast(this); } else if (iid == IID_IX) { *ppv = static_cast(this); } else if (iid == IID_IY) { *ppv = static_cast(this); } else { *ppv = NULL; return E_NOINTERFACE; } static_cast(*ppv)->AddRef(); return S_OK; } virtual ULONG __stdcall AddRef() { m_cRef++; return m_cRef; }

private: }; virtual ULONG __stdcall Release() { if (--m_cRef == 0) { delete this; return 0; } return m_cRef; } virtual void FX1() { cout << "class CA :virtual void FX1()" << endl; } virtual void FX2() { cout << "class CA :virtual void FX2()" << endl; } virtual void FY1() { cout << "class CA :virtual void FY1()" << endl; } virtual void FY2() { cout << "class CA :virtual void FY2()" << endl; } long m_cRef;

class CFactory : public IClassFactory { public: CFactory() : m_cRef(0) { cout << "팩토리 생성" << endl; } ~CFactory() { cout << "팩토리 소멸" << endl; } HRESULT __stdcall QueryInterface(const IID & iid, void ** ppv) { if (iid == IID_IUnknown || iid == IID_IClassFactory) { *ppv = static_cast(this); } else { *ppv = NULL; return E_NOINTERFACE; } static_cast(*ppv)->AddRef(); return S_OK; } virtual ULONG __stdcall AddRef() { m_cRef++; return m_cRef; }

private: }; virtual ULONG __stdcall Release() { if (--m_cRef == 0) { delete this; return 0; } return m_cRef; } virtual HRESULT __stdcall CreateInstance(IUnknown * pUnknownOuter, const IID & iid, void ** ppv) { if (pUnknownOuter != NULL) { return CLASS_E_NOAGGREGATION; } CA * pA = new CA; if (pA == NULL) { return E_OUTOFMEMORY; } HRESULT hr = pA->QueryInterface(iid,ppv); return hr; } HRESULT __stdcall LockServer(BOOL bLock) { if (bLock) g_cServerLocks++; else g_cServerLocks--; return S_OK; } long m_cRef;

extern "C" STDAPI DllCanUnloadNow() { if ((g_cComponents == 0) && (g_cServerLocks == 0)) { return S_OK; } else { return S_FALSE; } } extern "C" STDAPI DllGetClassObject(const CLSID & clsid, const IID & iid, void ** ppv) { if (clsid != CLSID_Component1) { return CLASS_E_CLASSNOTAVAILABLE; } CFactory * pFactory = new CFactory; if (pFactory == NULL) { return E_OUTOFMEMORY; } HRESULT hr = pFactory->QueryInterface(iid,ppv); if (FAILED(hr)) delete pFactory; return hr; }

BOOL SetRegKeyValue(LPTSTR pszKey, LPTSTR pszSubkey, LPTSTR pszValue) { BOOL bOk = FALSE; LONG ec; HKEY hKey; TCHAR szKey[256]; lstrcpy(szKey, pszKey); if (NULL != pszSubkey) { lstrcat(szKey, TEXT("\\")); lstrcat(szKey, pszSubkey); ec = RegCreateKeyEx(HKEY_CLASSES_ROOT,szKey,0,NULL,REG_OPTION_NON_VOLATILE,KEY_ALL_ACCESS,NULL,&hKey,NULL); if(ERROR_SUCCESS == ec) { ec = RegSetValueEx(hKey,NULL,0,REG_SZ,(BYTE *)pszValue,(lstrlen(pszValue)+1)*sizeof(TCHAR)); } if (ERROR_SUCCESS == ec) { bOk = TRUE; } } return bOk; RegCloseKey(hKey); }

extern "C" STDAPI DllRegisterServer() { HRESULT hr = NOERROR; TCHAR szID[129]; TCHAR szCLSID[129]; TCHAR szModulePath[MAX_PATH]; wchar_t wszCLSID[129]; GetModuleFileName(g_hModule,szModulePath,sizeof(szModulePath)/sizeof(TCHAR)); StringFromGUID2(CLSID_Component1,wszCLSID,128); wcstombs(szID,wszCLSID,128); lstrcpy(szCLSID,TEXT("CLSID\\")); lstrcat(szCLSID,szID); SetRegKeyValue(TEXT("CA.CA.1"),NULL,TEXT("CA Component")); SetRegKeyValue(TEXT("CA.CA.1"),TEXT("CLSID"),szID); SetRegKeyValue(szCLSID,NULL,TEXT("CA Component")); SetRegKeyValue(szCLSID,TEXT("ProgID"),TEXT("CA.CA.1")); SetRegKeyValue(szCLSID,TEXT("InprocServer32"),szModulePath); return hr; }

extern "C" STDAPI DllUnregisterServer() { HRESULT hr = NOERROR; TCHAR szID[129]; TCHAR szCLSID[129]; TCHAR szTemp[129]; wchar_t wszCLSID[129]; StringFromGUID2(CLSID_Component1,wszCLSID,128); wcstombs(szID,wszCLSID,128); lstrcpy(szCLSID, TEXT("CLSID\\")); lstrcat(szCLSID, szID); RegDeleteKey(HKEY_CLASSES_ROOT,TEXT("CA.CA.1\\CLSID")); RegDeleteKey(HKEY_CLASSES_ROOT,TEXT("CA.CA.1")); wsprintf(szTemp,TEXT("%s\\%s"),szCLSID, TEXT("InprocServer32")); RegDeleteKey(HKEY_CLASSES_ROOT,szTemp); wsprintf(szTemp, TEXT("%s\\%s"),szCLSID, TEXT("ProgID")); RegDeleteKey(HKEY_CLASSES_ROOT,szTemp); RegDeleteKey(HKEY_CLASSES_ROOT,szCLSID); return hr; }

Client COM Library CoCreateInstance CoGetClassObject DLL Class Factory Component DllGetClassObject new CFactory IClassFactory::CerateInstance(IID_ID) new CA IClassFactory::Release pIX->Fx()

4. 컴포넌트 등록하기

– DllRegisterServer와 DllUnregisterServer는 WIndows 레지스트리 에 컴포넌트를 등록하고 해지한다.

– regsvr32 – s Cmd.dll

– regsvr32는 DllRegisterServer함수를 호출한다.

– 사용되지 않는 컴포넌트를 메모리에 남겨 두는 것은 비효율적이 다.

– COM라이브러리는 CoFreeUnusedLibrary라는 이름의 함수를 구 현하였다.

– 클라이언트는 정기적으로 아이들 시간에 CoFreeUnsuedLibrary 를 호출해야 한다.

– CoFreeUnusedLibrary는 DLL에게 DllCanUnloadNow를 호출하여 언로드될 수 있는지를 물어본다.

– DllCanUnloadNow는 DLL이 어떠한 객체를 원하고 있는지의 여부 를 COM에게 알려준다.

– DLL이 컴포넌트를 서비스하고 있지 않다면 CoFreeUnusedLibrary는 DLL을 언로드할 수 있다.

4. 컴포넌트 등록하기

– DLL은 여전히 컴포넌트를 서비스하고 있는지 여부를 결정하기 위 해서 DLL은 그들의 카운터를 유지한다.

– static long g_cComponents = 0; – IClassFactory::CreateInstance 혹은 컴포넌트의 생성자에서 g_cComponents를 증가한다.

– 컴포넌트의 소멸자에서 g_cComponents를 감소시킨다.

– DllCanUnloadNow는 g_cComponents가 0이라면 TRUE를 리턴 한다.

– g_cComponents는 컴포넌트의 개수를 가지고 있다. 클래스팩토 리의 개수는 참고되지 않고 있다.

– 그러므로 실행하고 있는 클래스 팩토리를 갖는 DLL을 언로드하는 것은 클라이언트에게 문제를 일으킨다.

– 외부에서 IClassFactory포인터를 사용하고 있을 때 DLL이 언로드 되는 것은 문제를 일으킨다.

4. 컴포넌트 등록하기

– IClassFactory의 LockServer는 클라이언트에게 작업을 완료할 때 까지 메모리에서 DLL을 UnLoad하는 것을 막는다.

• LockServer(TRUE), LockServer(FALSE) – LockServer는 단지 g_cComponents 카운트를 증가하고 감소하 는 간단한 문제이다.

Chapter

8.

컴포넌트의 재사용 포함과 통합

1. 구현상속과 인터페이스 상속

– COM은 구현 상속을 지원하지 않는다.

– COM은 인터페이스 상속을 지원한다.

• 구현 상속의 단점

– 1. 구현 상속은 하나의 객체를 다른 객체에 종속되게 하므로 COM 은 구현 상속을 지원하지 않는다.

• 부모 객체의 구현이 바뀐다면, 상속받은 객체는 사용될 수 없고 수정 되어야 한다.

• 컴포넌트의 변화로부터 클라이언트를 안전하게 계속 사용할 수 있도 록 하려면 구현 상속을 사용하면 안된다.

– 2. 컴포넌트의 변화가 애플리케이션에 문제를 발생시키는 것을 막 기 위해서 COM은 구현 상속을 지원하지 않는다.

• COM은 컴포넌트 포함을 사용하여 구현 상속을 흉내 내므로 결과적 으로 모든 기능을 지원한다.

2. 포함과 통합

– 포함과 통합은 어떤 컴포넌트가 다른 컴포넌트를 사용하는 기술 이다.

• 통합에는 외부 컴포넌트와 내부 컴포넌트가 존재한다.

• 외부 컴포넌트는 내부 컴포넌트를 포함하거나 또는 통합한다.

• 포함의 개요

– COM의 포함은 C++의 포함과 비슷하다.

– COM의 포함은 인터페이스레벨에서 완성된다.

– 외부 컴포넌트는 내부 컴포넌트의 인터페이스 포인터를 포함한다.

– 외부 컴포넌트는 내부 컴포넌트의 인터페이스를 사용해서 자신의 인터페이스를 구현한다.

– 외부 컴포넌트는 내부 컴포넌트를 호출함으로써 내부 컴포넌트에 의해 제공되는 인터페이스를 다시 구현할 수 있다.

2. 포함과 통합

Outer Component IX IY Inner Component IZ

class CA : public IX,public IY { public: virtual HRESULT __stdcall QueryInterface(const IID & iid, void ** ppv); virtual ULONG __stdcall AddRef(); virtual ULONG __stdcall Release(); virtual void __stdcall Fx() { cout << "Fx" << endl; } virtual void __stdcall Fy() { cout << m_pIY->Fy() << endl; } CA(); ~CA(); HRESULT Init(); private: long m_cRef; IY * m_pIY; }; CA::CA() : m_cRef(1), m_pIY(NULL) { InterlockedIncrement(&g_cComponents); }

CA::~CA() { InterlockedDecrement(&g_cComponents); if (m_pIY != NULL) { m_pIY->Release(); } } HRESULT CA::Init() { HRESULT hr = ::CoCreateInstance(CLSID_Component2, NULL, CLSCTX_INPROC_SERVER, IID_IY, (void **)&m_pIY); if (FAILED(hr)) { return E_FAIL; } else { return S_OK; } }

HRESULT __stdcall CFactory::CreateInstance(IUnknown * pUnknownOuter, { if (pUnknownOuter != NULL) { return CLASS_E_NOAGGREGATION; } CA * pA = new CA; if (pA == NULL) { return E_OUTOFMEMORY; } HRESULT hr = pA->Init(); if (FAILED(hr)) { pA->Release(); return hr; } hr = pA->QueryInterface(iid, ppv); pA->Release(); return hr; }

2. 포함과 통합

– 포함의 가장 큰 사용 이유 중 하나는 이전의 인터페이스에 새로운 코드를 추가함으로써 인터페이스를 확장하는데 있다.

– 단점은 외부컴포넌트는 내부 컴포넌트의 모든 인터페이스를 모두 구현해 주어야 한다.

{

3. 포함 구현하기

class CA : public IX,public IY public: CA() : m_cRef(0) { cout << "객체 생성" << endl; m_pIY = NULL; } ~CA() { cout << "객체 소멸" << endl; if (m_pIY != NULL) { m_pIY->Release(); } } HRESULT Init() { HRESULT hr = ::CoCreateInstance(CLSID_Component2,NULL,CLSCTX_INPROC_SERVER,IID_IY,(void **)&m_pIY); if (FAILED(hr)) { return E_FAIL; } else { return S_OK; } }

HRESULT __stdcall QueryInterface(const IID & iid, void ** ppv) { if (iid == IID_IUnknown) { *ppv = static_cast(this); } else if (iid == IID_IX) { *ppv = static_cast(this); } else { *ppv = NULL; return E_NOINTERFACE; } static_cast(*ppv)->AddRef(); return S_OK; } virtual ULONG __stdcall AddRef() { m_cRef++; return m_cRef; } virtual ULONG __stdcall Release() { if (--m_cRef == 0) { delete this; return 0; } return m_cRef; }

private: virtual void FX1() { cout << "class CA :virtual void FX1()" << endl; } virtual void FX2() { cout << "class CA :virtual void FX2()" << endl; } virtual void FY1() { m_pIY->FY1(); } virtual void FY2() { m_pIY->FY2(); } long m_cRef; IY * m_pIY; };

HRESULT __stdcall CFactory::CreateInstance(IUnknown * pUnknownOuter, const IID & iid, void ** ppv) { if (pUnknownOuter != NULL) { return CLASS_E_NOAGGREGATION; } CA * pA = new CA; if (pA == NULL) { return E_OUTOFMEMORY; } HRESULT hr = pA->Init(); if (FAILED(hr)) { pA->Release(); return hr; } hr = pA->QueryInterface(iid,ppv); return hr; }

3. 포함 구현하기

– 포함의 장점은 쉽다.

– 포함하는 컴포넌트에서 제공하는 인터페이스를 모두 Outer Component에 노출해주어야 한다.

2. 포함과 통합

• 통합

– 통합은 포함의 특별한 경우이다.

– 클라이언트는 내부 인터페이스에 대하여 외부 컴포넌트에게 직접 문의 한다.

– 외부 컴포넌트는 내부 컴포넌트의 인터페이스를 직접 구현하는 대신에 내부 컴포넌트에게 인터페이스를 문의하고 인터페이스를 직접 넘겨준다.

– 외부 컴포넌트는 클라이언트에게 내부 컴포넌트의 인터페이스 포 인터를 곧바로 넘겨준다.

– 그 후 클라이언트는 직접 내부 컴포넌트에 속한 인터페이스를 호 출한다.

– 이런 방법으로 외부 컴포넌트는 다시 구현해야 하거나 또는 인터 페이스에 포함된 함수를 연결해 주는 번거로움을 피할 수 있다.

2. 포함과 통합

– 클라이언트는 두 개의 다른 컴포넌트와 작업을 수행하고 있다는 것을 신경 쓸 필요가 없다.

– 통합은 외부와 내부 컴포넌트들이 마치 하나의 컴포넌트로 행동 하게 한다.

Outer Component IX Inner Component IY

class CA : public IX,public IY { public: virtual HRESULT __stdcall QueryInterface(const IID & iid, void ** ppv); virtual ULONG __stdcall AddRef(); virtual ULONG __stdcall Release(); private: virtual void __stdcall Fx() { cout << "Fx" << endl; } CA(); ~CA(); HRESULT Init(); long m_cRef; IUnknown * m_pUnknownInner; };

HRESULT __stdcall CA::QueryInterface(const IID & iid, void ** ppv) { if (iid == IID_IUnknown) { *ppv = static_cast(this); } else if(iid == IID_IX) { *ppv = static_cast(this); } else if(iid == IID_IY) { return m_pUnknownInner->QueryInterface(iid,ppv); } else { *ppv = NULL; return E_NOINTERFACE; } reinterpret_cast(*ppv)->AddRef(); return S_OK; }

2. 포함과 통합

– 통합의 목적은 클라이언트가 내부 컴포넌트에 의해 구현된 인터 페이스가 외부 컴포넌트에 의해 구현된 인터페이스인양 속이는 것이다.

– 내부 컴포넌트에 의해 일반적인 방법으로 구현된 인터페이스 포 인터를 클라이언트에게 넘겨주면, 클라이언트는 그 컴포넌트를 잘못 사용하게 된다.

– 외부 컴포넌트는 자신의 QueryInterface를 가지고 있는 반면, 내 부 컴포넌트의 인터페이스는 내부 컴포넌트에서 구현된 QueryInterface를 호출하게 된다.

– 외부 컴포넌튼가 IX인터페이스를 구현하고 내부 컴포넌트가 IY인 터페이스를 구현했을 때 • 외부 컴포넌트에서 IY를 구하여 IY인터페이스를 통하여 IX인터페이스 를 구하면 E_NOINTERFACE를 되돌리게 된다. – 문제는 내부 컴포넌트의 IUnknown 인터페이스에 있다.

– 클라이언트는 결합된 컴포넌트의 구현에 완전히 독립적이어야 한 다. 클라이언트는 외부 컴포넌트가 내부 컴포넌트를 통합하였는 지 알 필요가 없고, 내부 컴포넌트의 IUnknown을 알 필요가 없다.

4. 통합의 구현

– 통합의 목적은 클라이언트가 내부 컴포넌트에 의해 구현된 인터 페이스가 외부 컴포넌트에 의해 구현된 인터페이스인양 속이는 것이다.

– 클라이언트는 결합된 컴포넌트의 구현에 완전히 독립적이어야 한 다.

– 클라이언트는 외부 컴포넌트가 내부 컴포넌트를 통합하였는지 알 필요가 없고 내부 컴포넌트의 IUnknown을 알 필요도 없다.

– 외부 unknown을 내부 컴포넌트가 사용하는 가장 간단한 방법은 외부 unknown을 곧바로 호출하는 것이다.

– 외부 unknown을 곧바로 호출하기 위해서는, 내부 컴포넌트에서 외부 unknown에 대한 포인터를 가지고 있는 것이다.

– 외부 컴포넌트는 내부 컴포넌트에게 pUnknownOuter 파라미터를 사용하여 자신의 IUnknown인터페이스 포인터를 넘겨준다.

– 만약 외부 unknown포인터가 NULL이 아니라면 그 컴포넌트는 통 합된 것이다.

4. 통합의 구현

– CreateInstance에 넘겨진 IUnknown포인터를 가지고, 컴포넌트는 자신이 통합되었는지, 누가 자신을 통합하였는지를 알게 된다.

– 컴포넌트가 통합이 되지 않았다면, 자신의 IUnknown의 구현을 사 용하면 된다.

– 만약 컴포넌트가 통합되었다면 외부 unknown을 호출하여 주면 된다.

– 통합을 지원하기 위해서는 내부 컴포넌트는 두 개의 Iunknown 인터페이스를 가져야 한다.

– nondelegation unknown은 일반적인 방법으로 내부 컴포넌트의 IUnknown을 구현한다.

– delegating unknown은 외부 컴포넌트나 nondelegation unknown을 호출하는 IUnknown멤버 함수를 가르킨다.

– 내부 컴포넌트가 통합되지 않은 경우라면, delegating unknown 이 nondelegation unknown을 가르킨다.

– 내부 컴포넌트가 통합되어 있으며, delegatin unknown은 외부 컴 포넌트에 의해 구현된 외부 unknown을 가르키게 된다.

4. 통합의 구현

• Delegation과 Nondelegating Unknown

– 통합을 지원하기 위해서는 내부 컴포넌트는 두 개의 IUnknown인 터페이스를 가져야 한다.

– nondelegation unknown은 일반적인 방법으로 내부 컴포넌트의 IUnknown을 구현한다.

– delegation unknown은 외부 컴포넌트나 nondelegation unknown을 호출하는 IUnknown멤버 함수를 가르킨다.

– 내부 컴포넌트가 통합되지 않은 경우라면, delegation unknown 이 nondelegation unknown을 가르킨다.

– 내부 컴포넌트가 통합되어 있으면, delegation unknown은 외부 컴포넌트에 의해 구현된 외부 unknown을 가르키게 된다.

QueryInterface AddRef Release Fy Nonaggregated Component Delegating IUnknown implementation Nondelegating IUnknown implementation

IX QueryInterface AddRef Release Fx aggregated Component Outer IUnknown implementation IY QueryInterface AddRef Release Fy Delegating IUnknown implementation Nondelegating IUnknown implementation

struct INondelegatingUnknown { virtual HRESULT __stdcall NondelegationgQueryInterface( const IID &, void ** ) = 0; virtual ULONG __stdcall NondelegatingAddRef() = 0; virtual ULONG __stdcall NondelegatingRelease() = 0; }; class CB : public IY, public INondelegatingUnknown { public: virtual HRESULT __stdcall QueryInterface( const IID &, void ** ppv) { return m_pUnknownOuter->QueryInterface(iid,ppv); } virtual ULONG __stdcall AddRef() { return m_pUnknownOuter->AddRef(); } virtual ULONG __stdcall Release() { return m_pUnknownOuter->Release(); } virtual ULONG __stdcall NondelegatingAddRef(); virtual ULONG __stdcall NondelegatingRelease(); virtual HRESULT __stdcall NondelegatingQueryInterface(const IID & iid, void ** ppv); CB(IUnknown * pUnknownOuter) : m_pUnknownOuter(pUnknownOuter) { } ~CB();

private: long m_cRef; IUnknown * m_pUnknownOuter; }; HRESULT __stdcall CB::NondelegatingQueryInterface(const IID & iid, void ** ppv) { if (iid == IID_IUnknown) { *ppv = static_cast(this); } else if(iid == IID_IY) { *ppv = static_cast(this); } else { *ppv = NULL; return E_NOINTERFACE; } reinterpret_cast(*ppv)->AddRef(); return S_OK; }

4. 통합의 구현

– 내부 컴포넌트의 this 포인터를 INondelegationUnknown 포인터 로 캐스트된다.

– nondelegation unknown은 항상 IID_IUnknown에 대한 요구가 들 어왔을 때 항상 자신의 포인터를 리턴한다.

– 컴포넌트가 통합되었을 때, delegation unknown은 모든 QueryInterface, AddRef, Release호출을 외부 객체에 되돌린다.

– 클라이언트가 IUnknown포인터를 필요로 할 때, 클라이언트는 항 상 외부 컴포넌트의 IUnknown의 포인터를 갖게 된다.

– 단지 외부 컴포넌트 만이 내부 컴포넌트의 nondelegation unknown을 가리키는 포인터를 가질 수 있다.

class CB: public IY, public INondelegationUnknown { public: virtual HRESULT __stdcall QueryInterface(const IID & iid, void ** ppv) { return m_pUnknownOuter->QueryInterface(iid, ppv); } virtual ULONG __stdcall AddRef() { return m_pUnknownOuter->AddRef(); } virtual ULONG __stdcall Release() { return m_pUnknownOuter->Release(); } virtual HRESULT __stdcall NondelegationQueryInterface(const IID & iid, void ** ppv); virtual ULONG __stdcall NondelegationAddRef(); virtual ULONG __stdcall NondelegationRelease(); virtual void __stdcall Fy() { cout << "Fy" << endl; } CB(IUnknown * m_pUnknownOuter); ~CB(); private: long m_cRef; IUnknown * m_pUnknownOuter; };

4. 내부 컴포넌트 만들기

HRESULT CA::Init() { IUnknown * pOutknown = this; HRESULT hr = CoCreateInstance(CLSID_Component2, pOutknown , CLSCTX_INPROC_SERVER,IID_IUnknown,(void **)&m_pUnknownInner); if (FAILED(hr)) { return E_FAIL; } return S_OK; } – 통합했을 때, 첫 번째로 외부 컴포넌트는 Init()에서 내부 컴포넌트 를 만들어야 한다.

– 외부 컴포넌트는 외부 IUnknown을 내부 컴포넌트에 넘겨주어야 한다.

– 내부 컴포넌트의 클래스 팩토리는 nondelegation IUnknown을 넘 겨준다.

– 외부 컴포넌트는 내부 컴포넌트의 QueryInterface를 호출하기 위 해서 이 포인터가 필요하다.

– 외부 컴포넌트는 나중에 사용할 내부 컴포넌트의 nondelegating unknown을 저장해 놓아야 한다.

4. 내부 컴포넌트 만들기

– 외부 컴포넌트의 IClassFactory::CreateInstance에서 CA::Init를 호출한다.

• 내부 컴포넌트의 IClassFactory::CreateInstance

– 내부 컴포넌트의 IClassFactory는 IUnknonw대신에 INondelegationUnknown을 사용하는 것으로 수정되어야 한다.

– pUnknownOuter가 NULL이 아닐 때 무조건 실패를 리턴하지 않 고, IID_IUnknown이 아닌 것을 구하려 한다면 CFactory::CreateInstance는 CLASS_E_NOAGGREGATION을 리 턴할 것이다.

– 그러므로 내부 컴포넌트를 만든 후 내부 컴포넌트에서 IID_IY를 구해야 한다.

HRESULT __stdcall CFactory::CreateInstance(IUnknown * pUnknownOuter, const IID & iid, void ** ppv) { if ((pUnknownOuter != NULL) && (iid != IID_IUnknown)) { return CLASS_E_NOAGGREGATION; } CB * pB = new CB(pUnknownOuter); if (pB == NULL) { return E_OUTOFMEMORY; } HRESULT hr = pB->NondelegatingQueryInterface(iid,ppv); if (FAILED(hr)) { pB->Release(); return hr; } return hr; } CB::CB(IUnknown * pUnknownOuter) : m_cRef(1) { g_cComponents++; if (pUnknownOuter == NULL) { m_pUnknownOuter = reinterpret_cast(static_cast(this)); } else { m_pUnknownOuter = pUnknownOuter; } }

HRSULT __stdcall CA::Init() { IUnknown * pUnknownOuter = this; HRESULT hr = ::CoCreateInstance(CLSID_Component2,pUnknownOuter,CLSCTX_INPROC_SERVER, IID_IUnknwon,(void **)&m_pUnknownInner); if (FAILED(hr)) { return E_FAIL; } hr = m_pUnknownInner->QueryInterface(IID_IY,(void **)&m_pIY); if (FAILED(hr)) { m_pUnknownInner->Release(); return E_FAIL; } pUnknownOuter->Release(); return S_OK; }

완전한 소스 #include #include static HMODULE g_hModule = NULL; static long g_cComponents = 0; static long g_cServerLocks = 0; const char g_szFriendlyName[] = "Inside COM, Chapter 8 Example 2, Component 1"; const char g_szVerIndProgID[] = "InsideCOM.Chap08.Ex2.Cmpnt1"; const char g_szProgID[] = "InsideCOM.Chap08.Ex2.Cmpnt1.1"; class CA : public IX { public: virtual HRESULT __stdcall QueryInterface(const IID & iid, void ** ppv); virtual ULONG virtual ULONG __stdcall AddRef(); __stdcall Release(); virtual void __stdcall Fx() { cout << "Fx" << endl; } CA(); ~CA(); HRESULT __stdcall Init(); private: long m_cRef; IUnknown * m_pUnknownInner; };

CA::CA() : m_cRef(1), m_pUnknownInner(NULL) { InterlockedIncrement(&g_cComponents); } CA::~CA() { InterlockedDecrement(&g_cComponents); m_cRef = 1; IUnknwon * pUnknownOuter = this; pUnknownOuter->AddRef(); if (m_pUnknownInner != NULL) { m_pUnknownInner->Release(); } } HRESULT __stdcall CA::Init() { IUnknown * pUnknownOuter = this; HRESULT hr = ::CoCreateInstance(CLSID_Component2,pUnknownOuter, CLSCTX_INPROC_SERVER, { IID_IUnknown,(void **)&m_pUnknownInner); if(FAILED(hr)) return E_FAIL; } return S_OK; }

HRESULT __stdcall CA::QueryInterface(const IID & iid, void ** ppv) { if (iid == IID_IUnknown) { *ppv = static_cast(this); } else if(iid == IID_IX) { *ppv = static_cast(this); } else if(iid == IID_IY) { return m_pUnknownInner->QueryInterface(iid,ppv); } else { *ppv = NULL; return E_NOINTERFACE; } reinterpret_cast(*ppv)->AddRef(); return S_OK; } ULONG __stdcall CA::AddRef() { return ::InterlockedIncrement(&m_cRef); } ULONG __stdcall CA::Release() { if (InterlockedDecrement(&m_cRef) == 0) { delete this; return 0; }

class CFactory: public IClassFactory { public: virtual HRESULT __stdcall QueryInterface(const IID & iid, void ** ppv); virtual ULONG __stdcall AddRef(); virtual ULONG __stdcall Release(); virtual HRESULT __stdcall CreateInstance(IUnknown * pUnknownOuter, const IID & iid, void ** ppv); virtual HRESULT __stdcall LockServer(BOOL bLock); CFactory() : m_cRef(1) { } ~CFactory() { } private: long m_cRef; }; HRESULT __stdcall CFactory::QueryInterface(REFIID iid, void ** ppv) { IUnknown * pI; if (( iid == IID_IUnknown) || (iid == IID_IClassFactory)) { pI = static_cast(this); } else { *ppv = NULL; return E_NOINTERFACE; } pI->AddRef(); *ppv = pI;

ULONG __stdcall CFactory::AddRef() { return ::InterlockedIncrement(&m_cRef); } ULONG __stdcall CFactory::Release() { if (InterlockedDecrement(&m_cRef) == 0) { delete this; return 0; } return m_cRef; } HRESULT __stdcall CFactory::CreateInstance(IUnknown * pUnknownOuter, { HRESULT hr = E_FAIL; CA * pA = new CA; if (pA == NULL) { return E_OUTOFMEMORY; } hr = pA->Init(); if (FAILED(hr)) { pA->Release(); return hr; } hr = pA->QueryInterface(iid,ppv); pA->Release(); return hr;

if (bLock) { InterlockIncrement(&g_cServerLocks); } else { InterlockDecrement(&g_cServerLocks); } return S_OK; } STDAPI DllCanUnloadNow() { if ((g_cComponents == 0) && (g_cServerLocks == 0)) { return S_OK; } else { return S_FALSE; } } STDAPI DllGetClassObject(const CLSID & clsid, const IID & iid, { if (clsid != CLSID_Component1) { return CLASS_E_CLASSNOTAVAILABLE; } CFactory * pFactory = new CFactory; if (pFactory == NULL) { return E_OUTOFMEMORY; void ** ppv)

STDAPI DllRegisterServer() { return RegisterServer(g_hModule, CLSID_Component1, g_szFriendlyName, g_szVerIndProgID, g_szProgID); } STDAPI DllUnregisterServer() { return UnregisterServer(CLSID_COmponent1, g_szVerIndProgID,g_szProgID); } BOOL APIENTRY DllMain(HANDLE hModule, DWORD dwReason, void * lpReserved) { if (dwReason == DLL_PROCESS_ATTACH) { g_hModule = hModule; } return TRUE; }

#include #include static HMODULE g_hModule = NULL; static long g_cComponents = 0; static long g_cServerLocks = 0; const char g_szFriendlyName[] = "Inside COM, Chapter 8 Example 2, Component 2"; const char g_szVerIndProgID[] = "InsideCOM.Chap08.Ex2.Cmpnt2"; const char g_szProgID[] = "InsideCOM.Chap08.Ex2.Cmpnt2.1"; struct INondelegationUnknown { public: virtual HRESULT __stdcall NondelegatingQueryInterface(const IID & iid, void ** ppv) = 0; virtual ULONG __stdcall NondelegatingAddRef() = 0; virtual ULONG __stdcall NondelegatingRelease() = 0; }; class CB : public IY, public INondelegatingUnknown { virtual HRESULT __stdcall QueryInterface(const IID & iid, void ** ppv) { return m_pUnknownOuter->QueryInterface(iid,ppv); } virtual HRESULT __stdcall AddRef() { return m_pUnknownOuter->AddRef(); }

virtual HRESULT __stdcall Release() { return m_pUnknownOuter->Release(); } virtual HRESULT __stdcall NonDelegatingQueryInterface(const IID & iid, void ** ppv); virtual HRESULT __stdcall NonDelegatingAddRef(); virtual HRESULT __stdcall NonDelegatingRelease(); virtual void __stdcall Fy() { cout << "Fy" << endl; } CB(IUnknown * m_pUnknownOuter); ~CB(); private: long m_cRef; IUnknown * m_pUnknownOuter; }; HRESULT __stdcall CB::NonDelegatingQueryInterface(const IID & iid, void ** ppv) { if (iid == IID_IUnknown) { *ppv = static_cast(this); } else if (iid == IID_IY) { *ppv = static_cast(this); } else { *ppv = NULL; return E_NOINTERFACE; } reinterpret_cast(*ppv)->AddRef();

class CFactory: public IClassFactory { public: virtual HRESULT __stdcall QueryInterface(const IID & iid, void ** ppv); virtual ULONG __stdcall AddRef(); virtual ULONG __stdcall Release(); virtual HRESULT __stdcall CreateInstance(IUnknown * pUnknownOuter, const IID & iid, void ** ppv); virtual HRESULT __stdcall LockServer(BOOL bLock); CFactory() : m_cRef(1) { } ~CFactory() { } private: long m_cRef; }; HRESULT __stdcall CFactory::QueryInterface(REFIID iid, void ** ppv) { if (( iid == IID_IUnknown) || (iid == IID_IClassFactory)) { *ppv = static_cast(this); } else { *ppv = NULL; return E_NOINTERFACE; } reinterpret_cast(*ppv)->AddRef(); return S_OK; }

ULONG __stdcall CFactory::AddRef() { return ::InterlockedIncrement(&m_cRef); } ULONG __stdcall CFactory::Release() { if (InterlockedDecrement(&m_cRef) == 0) { delete this; return 0; } return m_cRef; } HRESULT __stdcall CFactory::CreateInstance(IUnknown * pUnknownOuter, { if ((pUnknownOuter != NULL) && (iid != IID_IUnknown)) { return CLASS_E_NOAGGREGATION; } CB * pB = new CB(pUnknownOuter); if (pB == NULL) { return E_OUTOFMEMORY; } HRESULT hr = pB->NonDelegatingQueryInterface(iid,ppv); pB->NonDelegatingRelease(); return hr; }

HRESULT __stdcall CFactory::LockServer(BOOL bLock) { if (bLock) { InterlockIncrement(&g_cServerLocks); } else { InterlockDecrement(&g_cServerLocks); } return S_OK; } STDAPI DllCanUnloadNow() { if ((g_cComponents == 0) && (g_cServerLocks == 0)) { return S_OK; } else { return S_FALSE; } }

if (clsid != CLSID_Component1) { return CLASS_E_CLASSNOTAVAILABLE; } CFactory * pFactory = new CFactory; if (pFactory == NULL) { return E_OUTOFMEMORY; } HRESULT hr = pFactory->QueryInterface(iid,ppv); pFactory->Release(); return hr; } STDAPI DllRegisterServer() { return RegisterServer(g_hModule, CLSID_Component1, g_szFriendlyName, g_szVerIndProgID, g_szProgID); } STDAPI DllUnregisterServer() { return UnregisterServer(CLSID_COmponent1, g_szVerIndProgID,g_szProgID); } BOOL APIENTRY DllMain(HANDLE hModule, DWORD dwReason, void * lpReserved) { if (dwReason == DLL_PROCESS_ATTACH) { g_hModule = hModule; } return TRUE; }

Chapter

9.

더 쉽게 만들기

1. 클라이언트측의 단순화

• 스마트 인터페이스 포인터

– 스마트 인터페이스 포인터를 이용하여 컴포넌트에 접근하라.

– 스마트 인터페이스 포인터는 참조 계수를 다루는 부분을 알아서 수행한다.

– 스마트 인터페이스 포인터가 유효 범위 밖으로 벗어날 때, 인터페 이스는 해제 된다.

• 무엇이 스마트 포인터인가?

– 스마트 포인터는 연산자 ->를 오버라이드 한다.

– 사용자가 스마트 포인터의 연산자 ->를 호출하면, 스마트 포인터 는 포함한 포인터가 가리키는 객체를 넘겨준다.

#include using namespace std; class CFoo { public: virtual void Bar() { cout << "Bar() " << endl; } }; class CFooPointer { public: CFooPointer(CFoo * p) { m_p = p; } CFoo * operator->() { return m_p; } private: }; void main() CFoo * m_p; { CFoo f; CFooPointer spF(&f); spF->Bar(); }

Chapter

10.

EXE에 있는 서버

1. 다른 프로세스

– 모든 EXE는 자신의 프로세스에서 실행된다.

– 모든 프로세스는 독립된 주소 영역을 갖고 있다.

– 각 EXE는 자기 자신의 프로세스를 갖는 반면, DLL은 링크되는 EXE의 프로세스 안에서 위치하게 된다.

– EXE는 out-of-process서버라고 하며 DLL은 in-procss서버라고 한다.

– 기본적으로 인터페이스는 함수 포인터 배열이다. 클라이언트는 인터페이스와 관계된 메모리에 접근할 수 있어야 한다.

– 컴포넌트가 DLL안에 있으면 클라이언트와 컴포넌트가 같은 주소 영역에 있으므로 클라이언트는 쉽게 메모리에 접근할 수 있다.

– 클라이언트와 컴포넌트가 다른 주소 영역에 있다면, 클라이언트 는 컴포넌트의 프로세스 안에 메모리를 접근할 수 없을 것이다.

– 프로세스 영역을 넘나드는 인터페이스를 만들기 위해서는 • 1. 프로세스는 다른 프로세스의 함수를 호출할 수 있어야 한다.

• 2. 프로세스는 다른 프로세스에게 데이터를 넘겨줄 수 있어야 한다.

• 3. 클라이언트는 in-proc 컴포넌트를 사용하던지 out-of-proc컴포 넌트를 사용하던지 신경 쓸 필요가 없어야 한다.

2. 로컬 프로시저 콜

– COM은 로컬 프로시저 콜 방법을 이용하여 프로세스들 간의 통신 을 한다.

– LPC는 같은 컴퓨터 상에서 다른 프로세스들 사이에 통신하는 방 법이다.

– OS는 각 프로세스에 대한 논리 주소 영역에 적합한 물리적 메모 리 주소를 알고 있다.

– OS는 어떤 프로세저에 있는 함수이든 호출할 수 있다.

– LPC는 OS차원에서 구현되어있다.

3. 마샬링

– 클라이언트 주소 영역의 함수에 넘겨줄 파라미터를 컴포넌트의 주소 영역으로 옮겨주는 방법이 마샬링이다.

– 어떤 프로세스의 데이터는 다른 프로세스의 주소 영역에 복사될 필요성이 있다.

– LPC 메커니즘은 어떤 프로세스에서 다른 프로세스로 데이터를 복 사하는 것을 책임진다.

– 포인터를 마샬링 할 때는 포인터에 의해 참조되는 구조체를 다른 프로세스에 복사한다.

– 포인터가 인터페이스 포인터라면, 포인터에 의해 참조되는 메모 리는 복사가 되면 안된다.

– 컴포넌트를 마샬링하기 위해선 IMarshal이라는 이름을 가진 인터 페이스를 구현해야 한다.

– 함수의 호출 전이나 후에 파라미터를 마샬, 언마샬하기 위해 IMarshal에 있는 멤버 함수를 호출한다.

– COM라이브러리는 대부분의 인터페이스에서 잘 작동하는 표준 IMarshal버전을 제공한다.

4. Proxy/Stub DLL

– 클라이언트는 컴포넌트를 흉내내는 DLL과 통신을 한다.

– 이 DLL은 클라이언트를 위해 마샬링을 하고 LPC를 호출한다.

– COM에서 이 컴포넌트를 프록시(proxy)라고 부른다.

– COM의 개념상, 프록시는 다른 컴포넌트와 같이 작동하는 컴포넌 트이다.

– 클라이언트의 주소 영역을 접근할 수 있어야 하므로 프록시는 반 드시 DLL이되어야 한다.

– 프록시는 인터페이스 함수들에 넘겨줄 데이터를 마샬링한다.

– 컴포넌트는 클라이언트에서 전송된 데이터를 언마샬하기 위해 스 텁이라는 이름의 DLL을 요구한다.

EXE EXE Client Server 마샬링 Proxy LPC Stub 언마샬링

4. Proxy/Stub DLL

– EXE에 컴포넌트를 넣으려면, 프록시와 스텁 코드를 작성해야 한 다.

– 프로세스 경계를 뛰어 넘어 호출하기 위해서는 LPC를 배워야 한 다.

– 클라이언트에서 컴포넌트로, 그리고 그 반대로 데이터를 마샬링 하기 위해서는 IMarshal을 구현해야 한다.

– 이 모든 작업을 할 필요가 없다.

5. IDL / MIDL

– IDL(Interface Description Language )로 인터페이스에 대한 설 명을 기술하는 것으로, 프록시와 스텁 DLL을 만들어 주는 MIDL컴 파일러가 있다.

import "oaidl.idl"; import "ocidl.idl"; [ object, uuid(77A6C990-956D-4764-BED6-E23C4A2A0C63), dual, helpstring("IADD Interface"), pointer_default(unique) ] interface IADD : IDispatch { [id(1), helpstring("method ADD")] HRESULT ADD([in] long x, [in] long y, [out, retval] long * pResult); [propget, id(2), helpstring("property XValue")] HRESULT XValue([out, retval] long *pVal); [propput, id(2), helpstring("property XValue")] HRESULT XValue([in] long newVal); [propget, id(3), helpstring("property YValue")] HRESULT YValue([out, retval] long *pVal); [propput, id(3), helpstring("property YValue")] HRESULT YValue([in] long newVal); };

5. IDL / MIDL

– object : 이 인터페이스가 COM인터페이스라는 것을 의미한다.

– uuid : 이 인터페이스의 IID이다.

– helpstring : 타입 라이브러리에 넣기 위해 사용하는 것이다.

– pointer_default : MIDL컴파일러가 포인터에 주어진 다른 속성이 없을 때, 기본적으로 어떻게 포인터를 다룰 것인가를 알려준다.

• IDL의 목적 중의 하나는 풍부한 정보를 제공하여 올바르게 함수 파라 미터들을 마샬되게 하는 것이다.

• 이것을 수행하려면, IDL은 포인터와 같은 것을 어떻게 다루어야 하는 지를 알아야한다.

• ref : 포인터를 레퍼런스처럼 다룬다. 포인터들은 호출 후나 호출 전 이나 같은 메모리를 가리킨다.

• unique : 이 포인터들은 NULL이 될 수 없다. 함수 내에서 변경될 수 있다. (read only) • ptr : NULL이 될 수도 있고, 함수 내에서 변경될 수도 있다.

– MIDL은 in과 out파라미터를 프록시와 스텁을 최적화하기 위해 사 용한다.

5. IDL / MIDL

– 파라미터가 in으로 표시된다면, MIDL은 이 파라미터가 클라이언 트에서 컴포넌트로만 이동할 필요가 있는 파라미터로 생각한다.

– 스텁 코드는 어떤 정보도 되돌릴 필요가 없다.

– out 키워드는 MIDL에게 컴포넌트에서 클라이언트로 데이터를 되 돌릴 때만 사용된다는 것을 알린다.

– 프록시는 out파라미터를 마샬할 필요가 없고 컴포넌트로 보낼 필 요도 없다.

– out파라미터는 포인터여야 한다.

– 데이터의 부분을 마샬하기 위해서는 복사할 데이터의 크기를 알 아야 한다.

– 함수에 string식별자를 사용하여 MIDL은 파라미터가 문자열이라 는 것을 알고 마지막 NULL문자를 찾아서 문자열의 길이를 알아낼 수 있다.

5. IDL / MIDL • IDL의 import키워드

– import키워드는 다른 IDL파링들로부터 정의를 포함하기 위해 사 용된다.

– #include와 비슷하나, 재정의의 문제점을 발생시키지 않고 원하 면 얼마든 import를 이용하여 파일을 임포트할 수 있다.

• IDL 에서 구조체

– MIDL은 포인터가 가리키는 것이 무언인지 확실히 알 필요가 있으 므로 포인터가 참조하는 데이터를 마샬링하는 방법을 알아야 한 다. 이런 이유로 void *를 사용하면 안된다.

6. 로컬 서버 구현

– in-proc 컴포넌트를 연결하기 위해서는 CLSCTX_INPROC_SERVER를 사용한다.

– out-of-proc컴포넌트를 연결하기 위해서는 CLSCTX_LOCAL_SERVER를 사용한다.

– EXE는 lock카운트를 감시할 수 있고, lock카운트가 0으로 감소하 면 스스로 해제된다.

– 그러므로 EXE는 DllCanUnloadNow를 구현할 필요가 없다.

– EXE는 적절한 커맨드 라인 파라미터가 발생했을 때, CFactory::RegiserAll이나 CFactory::UnregisterAll을 호출하면 된 다.

– 클라이언트가 CoGetClassObject를 올바른 파라미터와 함께 호출 하였을 때, COM은 먼저 클라이언트에서 요구된 CLSID에 대해 클 래스 팩토리의 내부 테이블을 살펴본다. 만약 클래스 팩토리가 내 부 테이블에 없다면 COM은 레지스트리를 살펴보고 연관된 EXE 를 수행한다.

} {

6. 로컬 서버 구현

– 실행된 EXE 바로 클래스 팩토리를 등록시켜야 한다.

• CoRegisterClassObject CFactoryData * pStart = &g_FactoryDataArray[0]; const CFactoryData * pEnd = &g_FactoryDataArray[g_cFactoryDataEntries - 1]; for(CFactoryData * pData = pStart; pData <= pEnd; pData ++) { pData->m_pIClassFactory = NULL; pData->m_dwRegister = NULL; IClassFactory * pIFactory = new CFactory(pData); DWORD dwRegister; HRESULT hr = ::CoRegisterClassObject(*pData->m_pCLSID, static_cast(pIFactory), CLSCTX_LOCAL_SERVER,REGCLS_MULTIPLEUSE,dwRegister); if (FAILED(hr)) { pIFactory->Release(); return FALSE; } } return TRUE pData->m_pIClassFactory = pIFactory; pData->m_dwRegister = dwRegister;

6. 로컬 서버 구현

– 실행된 EXE 바로 클래스 팩토리를 등록시켜야 한다.

• CoRegisterClassObject – 이 작업을 위해서 CFactory는 StartFactories를 추가하여야 한다.

6. 로컬 서버 구현

BOOL CFactory::StartFactories() { CFactoryData * pStart = &g_FactoryDataArray[0]; const CFactoryData * pEnd = &g_FactoryDataArray[g_cFactoryDataEntries - 1]; for(CFactoryData * pData = pStart; pData <= pEnd; pData ++) { pData->m_pIClassFactory = NULL; pData->m_dwRegister = NULL; IClassFactory * pIFactory = new CFactory(pData); DWORD dwRegister; HRESULT hr = ::CoRegisterClassObject(*pData->m_pCLSID, { static_cast(pIFactory), CLSCTX_LOCAL_SERVER,REGCLS_MULTIPLEUSE,dwRegister); if (FAILED(hr)) pIFactory->Release(); } return TRUE return FALSE; } pData->m_pIClassFactory = pIFactory; pData->m_dwRegister = dwRegister; }

6. 로컬 서버 구현 • CoRegisterClassObject클래스

– EXE컴포넌트 서버가 여러 개의 컴포넌트를 제공하려면 다음과 같 이 REGCLS_MULTI_SEPARATE를 사용한다.

• 클래스 팩토리 제거하기

– 서버가 종료될 때, 서버는 내부 테이블로부터 클래스 팩토리를 없 애야 한다.

– COM라이브러리 함수 CoRevokeClassObject는 이런 일을 수행 한다.

– CFactory멤버 함수 StopFactories는 EXE에 의해 제공되는 모든 클래스 팩토리에 대해 CoRevokeClassObject를 호출한다.

6. 로컬 서버 구현

void CFactory::StopFactories() { CFactoryData * pStart = &g_FactoryDataArray[0]; const CFactoryData * pEnd = &g_FactoryDataArray[g_cFactoryDataEntries - 1]; for(CFactoryData * pData = pStart; pData <= pEnd; pData ++) { DWORD dwRegister = pData->m_dwRegister; if (dwRegister != 0) { ::CoRevokeClassObject(dwRegister); } IClassFactory * pIFactory = pData->m_pIClassFactory; if (pIFactory != NULL) { pIFactory->Release(); } } }

6. 로컬 서버 구현 • LockServer의 수정

– DLL은 유효 기간의 조절을 할 수 없다.

– EXE가 DLL을 로드하고, EXE가 DLL을 언로드한다.

– EXE는 조절이 가능하고 스스로를 로드, 언로드할 수 있다.

– EXE는 스스로 언로드를 해야 하므로 어떤 것도 EXE를 언로드하 려 해서는 안된다.

– lock카운트가 0이 됐을 때 EXE를 끝낼 수 있도록 LockServer를 수정해야 한다.

static void CFactory::CloseExe() { if (CanUnloadNow() == S_OK) { ::PostThreadMessage(s_dwThreadID, WM_QUIT, 0,0); } }

6. 로컬 서버 구현

HRESULT __stdcall CFactory::LockServer(BOOL bLock) { if (bLock) { ::InterlockIncrement(&s_cServerLocks); } else { ::InterlockDecrement(&s_cServerLocks); } CloseExe(); return S_OK; }

Chapter

11.

디스패치 인터페이스와 자동화

1. 자동화

– 인터프리터와 매크로 언어의 클라이언트는 컴포넌트를 컨트롤하 기 위해서 자동화를 사용한다.

– 자동화는 COM위에서 만들어진다.

– 자동화 서버는 IDispatch 인터페이스를 구현한 COM컴포넌트이 다.

– 자동화 컨트롤러는 IDispatch인터페이스를 통해 자동화 서버와 통신하는 COM클라이언트이다.

– 자동화 컨트롤러는 자동화 서버에 의해 구현된 함수를 직접 호출 하지 않는다.

– 자동화 컨트롤러는 자동화 서버에 있는 함수를 곧바로 호출하기 위해 IDispatch인터페이스의 멤버 함수를 사용한다.

1. 새로운 통신 방법

– IDispatch는 클라이언트와 컴포넌트가 통신할 수 있는 새로운 방 법을 제공한다.

– 매크로 언어는 vtbl에서 호출할 함수의 offset를 구할 수 없다.

– 매크로 언어는 컴포넌트의 ProgID,함수의 이름,함수의 파라미터 들을 이용하여 함수를 호출한다.

2. IDispatch

interface IDispatch : public IUnknown { public: virtual HRESULT STDMETHODCALLTYPE GetTypeInfoCount( /* [out] */ UINT *pctinfo) = 0; virtual HRESULT STDMETHODCALLTYPE GetTypeInfo( /* [in] */ UINT iTInfo, /* [in] */ LCID lcid, /* [out] */ ITypeInfo **ppTInfo) = 0; virtual HRESULT STDMETHODCALLTYPE GetIDsOfNames( /* [in] */ REFIID riid, /* [size_is][in] */ LPOLESTR *rgszNames, /* [in] */ UINT cNames, /* [in] */ LCID lcid, /* [size_is][out] */ DISPID *rgDispId) = 0; }; virtual /* [local] */ HRESULT STDMETHODCALLTYPE Invoke( /* [in] */ DISPID dispIdMember, /* [in] */ REFIID riid, /* [in] */ LCID lcid, /* [in] */ WORD wFlags, /* [out][in] */ DISPPARAMS *pDispParams, /* [out] */ VARIANT *pVarResult, /* [out] */ EXCEPINFO *pExcepInfo, /* [out] */ UINT *puArgErr) = 0;

2. IDispatch

– IDispatch에서 가장 중요한 함수는 GetIDsOfNames와 Invoke이 다.

– GetIDsOfNames : 함수의 이름을 읽고 그것의 디스패치 ID를 리 턴한다.

• DISPID는 long값이다.

– Invoke : 함수를 수행할 때 DISPID를 넘겨 받아 함수를 호출한다.

pVtbl &QueryInterface &AddRef &Release &GetTypeInfoCount &GetIDsOfNames &Invoke 1 2 3 1 2 3

Foo

” “

Bar

” “

FooBar

&Foo &Bar &FooBar

2. 이중인터페이스

– 이중 인터페이스는 모든 함수를 Invoke뿐만 아니라 vtbl을 통해 곧바로 사용 가능하게 하는 디스핀터페이스이다.

pVtbl &QueryInterface &AddRef &Release &GetTypeInfoCount &GetIDsOfNames &Invoke pVtbl 1 2 3

Foo

” “

Bar

” “

FooBar

&Foo &Bar &FooBar

3. IDispatch의 사용

– Dim Cmp As Obejct – Set Cmp = CreateObject( “ InsideCOM.Chap11.Cmp1

” ); – Cmp.Fx

4. 타입 라이브러리

– COM에서는 컴포넌트, 인터페이스, 메소드, 프로퍼티, 인자 구조 체에 대한 타입 정보를 제공하기 위해서 타입 라이브러리를 사용 한다.

– 타입 라이브러리는 C++ 헤더 파일과 비슷하다.

– 타입 라이브러리는 프로그램에 입각하여 접근될 수 있는 IDL파일 의 컴파일된 버전이다.

– 타입 라이브러리를 사용할 수 있으면 VB는 이중 인터페이스의 vtbl부분을 통하여 컴포넌트에 접근할 수 있다.

4. 타입 라이브러리

library COMTEST8Lib { importlib("stdole32.tlb"); importlib("stdole2.tlb"); [ uuid(51BC248E-8E30-4AD9-A5EC-87F477AF3906), helpstring("_IADDEvents Interface") ] dispinterface _IADDEvents { properties: methods: [id(1), helpstring("method XValueChanged")] HRESULT XValueChanged(); [id(2), helpstring("method YValueChanged")] HRESULT YValueChanged(); }; [ uuid(B65AF9BE-0F5A-4812-86D7-5274AC8C087E), helpstring("ADD Class") ] coclass ADD { [default] interface IADD; [default, source] dispinterface _IADDEvents; }; };

4. 타입 라이브러리

– library : 이 블록에 있는 모든 것은 컴파일 되어 타입라이브러리로 들어간다.

– coclass : 컴포넌트를 정의한다.

• coclass ADD • { – [default] interface IADD; – [default, source] dispinterface _IADDEvents; • }; • IADD와 _IADDEvents를 포함하는 타입라이브러리를 만든다.

Inside

ATL/COM

Microsoft

s Component Objects Model 강사 : 이준근 ([email protected])

Chapter

1.

COM 프로그래밍 개요

1. COM의 이점

– COM은 컴포넌트의 통합 및 커뮤니케이션 방법에 대한 표준을 정의 한 사양이다.

– 하나의 어플리케이션을 여러 개의 컴포넌트로 분할하여 만들면 재사 용이 쉬워지며, 유지보수가 용이해진다.

– 분산COM(Distributed COM,DCOM)으로 이들 컴포넌트를 전용서버 에 배치시키고 클라이언트에서 이 서버에 있는 컴포넌트를 사용함으 로써, 사용자에게 변경된 기능을 포함하는 컴포넌트를 분배하지 않 고도 클라이언트 어플리케이션이 변경된 컴포넌트의 기능을 사용할 수 있다.

– 현재 마이크로소프트에서 발표하는 모든 기술의 기반은 COM이다.

2. 컴포넌트 소프트웨어의 조건

– 1. 각 컴포넌트들은 서로 다른 언어로 개발될 수 있어야 한다.

• 서로 다른 언어로 작성된 컴포넌트가 서로 무리 없이 커뮤니케이션을 하 려면 어떤 표준적인 방법이 필요하다.

– 2. 컴포넌트를 사용하는 어플리케이션은 컴포넌트가 설치되고 실행 하는 위치에 관계없이 같은 방법으로 해당 컴포넌트를 사용할 수 있 어야 한다.

– 3. 컴포넌트 버전관리가 쉬워야 한다.

– 4. 한 업체에 종속적이지 않아야 한다.

• COM은 개방 산업 표준으로 정착되어 UNIX운영체제를 비롯하여 IBM메 인 프레임용 COM/DCOM이 테스트 중이다.

• COM컴포넌트의 조건

– 1. COM은 언어 독립적이어야 한다.

– 2. COM컴포넌트는 이진(binary)형태로 제공되어야 한다.

– 3. COM컴포넌트는 버전 호환성을 제공해야 한다.

– 4. COM컴포넌트는 위치 투명성을 제공해야 한다.

3. COM과 ActiveX

– 마이크로소프트의 컴포넌트 소프트웨어 기술은 OLE(Object Linking and Embededding)에서부터 시작한다.

– OLE는 복합 다큐먼트를 통하여 어플리케이션을 통합하는 기술이다.

• 복합 다큐먼트 : 여러 어플리케이션에서 생성한 개체를 연결 또는 포함 시킴으로써 생성된 다큐먼트를 의미한다.

– OLE1은 DDE(Dynamic Data Exchange)기술을 사용하였기 때문에 속도와 유연성에 있어 문제가 있었다.

– OLE2는 DDE대신 COM을 이용하여 재작성되었다.

– OLE2는 COM을 기반으로 하는 마이크로소프트의 첫 번째 기술이다.

– 마이크로소프트는 OLE2기술과 함께 OLE자동화(OLE Automation) 과 OLE컨트롤(OLE Control)이라는 기술을 발표한다.

– OLE2는 인터넷환경에서 문제점을 노출시킨다.

• OLE컴포넌트는 실제로 그 기능이 필요하든 하지 않든 간에 많은 인터페 이스를 기본적으로 포함해야 한다.

• 느린 속도의 인터넷에서는 거대한 OLE컴포넌트는 다운로드에 부담스러 워졌다.

3. COM과 ActiveX

– 1996년 초 마이크로소프트는 ActiveX라는 새로운 기술을 발표한다.

• ActiveX컨트롤에는 IUnknown인터페이스와 사용자 시스템의 레지스트 리에 자신을 등록할 수 있는 자동등록 등의 최소 기능만을 필요로 한다.

• 비동기 모니커 개념이 포함되어 이미지와 같이 많은 양의 데이터를 사용 자 인터페이스를 방해하지 않고 사용자 시스템에 다운로드할 수 있도록 한다.

4. 인터페이스란?

– 인터페이스란 COM개체가 자신의 기능을 클라이언트에 노출시키는 기본적인 방법이다.

– 하나의 인터페이스는 서비스를 제공하는 하나 이상의 메서드를 노출 한다.

– COM개체는 반드시 하나의 IUnknown인터페이스를 제공해야 한다.

IUnknown IX IY

COM 개체

4. 인터페이스란?

– 인터페이스는 COM개체와 이것을 사용하는 클라이언트와의 약속이 다.

– 인터페이스는 C++와 같은 OOP언어에서 순수 가상 함수만을 멤버 로 포함하는 추상 클래스로 표현된다.

• 인터페이스는 가상 함수 테이블형태의 메모리 구조를 정의한다.

#define interface struct #define STDMETHOD(method) virtual HRESULT STDMETHODCALLTYPE method #define STDMETHOD_(type,method) virtual type STDMETHODCALLTYPE method #define PURE = 0 #define THIS_ #define THIS void #define DECLARE_INTERFACE(iface) interface iface #define DECLARE_INTERFACE_(iface, baseiface) interface iface : public baseiface class IFoo { public: HRESULT __stdcall Method1() = 0; HRESULT __stdcall Method2() = 0; HRESULT __stdcall Method3() = 0; };

4. 인터페이스란?

DECLARE_INTERFACE(IFoo) { STDMETHOD (Method1) (THIS) PURE; STDMETHOD (Method2) (THIS) PURE; STDMETHOD (Method3) (THIS) PURE; };

4. 인터페이스 메모리 구조

#include class CImplIFoo : public IFoo { public: STDMETHODIMP Method1() {} STDMETHODIMP Method2() {} STDMETHODIMP Method3() {} }; vptp 인터페이스 포인터 데이터 Method1함수 포인터 Method2함수 포인터 Method3함수 포인터 Method4함수 포인터 void Method1() { } void Method2() { } void Method3() { } void Method4() { }

4. 인터페이스란?

– COM인터페이스의 메모리구조는 C++컴파일러가 추상적인 클래스 에 대하여 생성한 메모리 구조와 완전히 일치한다.

4. 인터페이스 상속과 구현 상속

– 클라이언트 어플리케이션은 COM개체가 제공하는 인터페이스에 대 한 포인터를 통하여 COM개체의 인터페이스에 정의된 메소드에 접 근하게 된다.

– 상속은 인터페이스 상속과 구현 상속이 있다.

class Base { public: void Func1() { cout << "Base::Func1" << endl; } void Func2() { cout << "Base::Func2" << endl; } void Func3() { cout << "Base::Func3" << endl; } }; class Derived : public Base { public: void Func1() { cout << "Derived::Func1" << endl; } void Func2() { cout << "Derived::Func2" << endl; } }; – 구현상속은 언어 독립적이다.

– 기초 클래스가 수정되면 Derived클래스에서 Func2 멤버 함수를 재 정의함으로써 Base클래스의 기능이 전혀 달라진다.

4. 인터페이스 상속과 구현 상속

class Base { public: virtual void Func1() { cout << "Base::Func1" << endl; } virtual void Func2() { cout << "Base::Func2" << endl; } virtual void Func3() { cout << "Base::Func3" << endl; Func2(); } }; class Derived : public Base { public: void Func1() { cout << "Derived::Func1" << endl; } void Func2() { cout << "Derived::Func2" << endl; } }; – COM에서는 구현상속 대신에 인터페이스상속을 사용한다.

– 인터페이스상속의 문제점은 구현된 코드를 전혀 재사용할 수 없다는 것이다. 이 문제를 해결하기 위해서 COM은 포함과 Aggregation이 라는 방법을 사용한다.

5. IUnknown 인터페이스

– 클라이언트가 하나의 인터페이스 포인터를 구했을 때 호출할 수 있 는 메서드는 해당 인터페이스의 메서드 뿐이다.

– 그 인터페이스 포인터를 사용하여 다른 인터페이스에 있는 메서드를 호출할 수는 없다.

– COM개체는 자신이 제공하는 다른 인터페이스의 포인터를 클라이언 트가 구할 수 있는 방법을 제공한다.

– 여러 클라이언트가 COM개체를 동시에 사용하면 COM개체 소멸에 문제가 있다. 아무도 사용하지 않을 때 COM개체를 소멸하여야 한다.

– 클라이언트에서 직접 COM개체를 소멸하는 대신에, COM개체 스스 로 자신이 소멸되어야 할 때를 결정해야 한다.

– 모든 COM개체는 반드시 IUnknown인터페이스에서 파생되어야 한 다.

– IUnknown인터페이스는 모든 COM개체가 반드시 제공하여야 하는 기본적인 서비스에 대한 기준을 제공한다.

– IUnknown은 UNKNWN.H에 다음과 같이 선언 되어 있다.

5. IUnknown 인터페이스

interface IUnknown { virtual HRESULT __stdcall QueryInterface(const IID & iid, void ** ppv) = 0; virtual ULONG __stdcall AddRef() = 0; virtual ULONG __stdcall Release() = 0; }; – virtual HRESULT __stdcall QueryInterface(REFIID riid, void ** ppv); – REFIID riid: • #define REFIID const IID & • riid는 매개변수 IID의 참조형으로 클라이언트가 요청하는 인터페이스의 참조형 변수이다.

– ppv : • 클라이언트가 riid매개변수를 통해 요청한 인터페이스에 대한 QueryInterface함수의 실행결과인 인터페이스 포인터가 저장되는 출력 매개변수이다.

5. IUnknown 인터페이스

• AddRef와 Release

– 클라이언트에서 사용중인 COM개체를 소멸시키는 문제를 해결하기 위해서 COM개체는 스스로가 자신의 참조 횟수를 관리한다.

– 이 참조 횟수가 0일 때 스스로를 소멸시키는 방법을 사용한다.

– AddRef함수가 호출되면 레퍼런스 카운터를 하나 증가시키고, – Release함수가 호출되면 레퍼런스 카운터를 하나 감소시킨다.

• 규칙

– 1. 인터페이스를 리턴하는 함수에서는 리턴하기 전에 해당 인터페이 스 포인터에 대하여 항상 AddRef()를 호출해야 한다.

– 2. 인터페이스 사용이 끝나면 해당 인터페이스 포인터에 대하여 항 상 Release()를 호출해야 한다.

– 3. 인터페이스 포인터를 다른 인터페이스 포인터에 대입한 경우에도 해당 인터페이스 포인터에 대하여 AddRef를 호출해야 한다.

6. GUID와 HRESULT

• GUID (Globally Unique Identifier)

– 128비트의 크기를 갖는 구조체이다.

– 전세계적으로 시간과 장소에 관계없이 고유하다고 보장할 수 있는 값을 나타내는 식별자로 사용된다.

– typedef GUID IID; – typedef GUID CLSID; DEFINE_GUID(IID_IFoo, 0xe2eca469, 0x5571, 0x4a5d, 0x85, 0x7d, 0x75, 0xb5, 0xc0, 0x82, 0x6f, 0x8c); – DEFINE_GUID매크로는 GUID구조체 형식에 일치하도록 매크로 확 장을 한다.

extern “ C ” const GUID IID_IFoo = { 0xe2eca469, 0x5571, 0x4a5d, {0x85, 0x7d, 0x75, 0xb5, 0xc0, 0x82, 0x6f, 0x8c}}; – 이 매크로를 사용하기 위해서는 다음과 같이 선언한다.

• #include • #include

Chapter

2.

COM개체의 사용

1. COM클라이언트와 서버 어플리케이션

– COM은 클라이언트/서버 모델을 사용한다.

– COM서버 어플리케이션은 자신의 고유한 서비스를 제공하는 COM 컴포넌트이다.

– COM클라이언트 어플리케이션은 COM컴포넌트가 제공하는 서비스 를 사용하는 어플리케이션이다.

– COM컴포넌트는 어떤 프로세스에서 생성되는가에 따라 인-프로세 스 서버와 아웃-오브-프로세스 서버로 나뉘어진다.

– 인-프로세스 서버는 DLL파일로 구현되며, 자신이 제공하는 서비스 를 사용하는 COM클라이언트 어플리케이션과 같은 프로세스영역 안 에 로드되어 COM개체를 생성한다.

– 아웃-오브-프로세스 서버는 EXE파일로 구현되며, COM클라이언트 어플리케이션의 프로세스 영역 밖에서 독립된 자신의 고유한 프로세 스 영역에 로드되고 COM개체를 생성한다.

2. COM컴포넌트 등록

– COM컴포넌트가 제공하는 서비스를 사용하기 위해서는, COM컴포 넌트는 반드시 레지스트리에 등록되어 있어야 한다.

• regsvr32 ServerName.DLL

• regsvr32 /u ServerName.DLL

• ServerName.exe /RegServer • ServerName.exe /UnregServer

• COM 클라이언트 어플리케이션 생성 과정 개요

– 1. COM라이브러리를 초기화한다.

– 2. COM컴포넌트가 제공하는 COM개체의 CLSID를 구한다.

– 3. COM개체의 인스턴스를 생성한다.

– 4. COM개체에서 제공하는 인터페이스의 포인터를 구하여 해당 인 터페이스가 제공하는 메소드를 호출한다.

– 5. COM컴포넌트의 사용이 끝나면 COM라이브러리의 초기화를 해 제한다.

3. COM라이브러리 초기화 • COM라이브러리의 기능

– COM클라이언트와 서버 어플리케이션의 생성 기능을 제공하는 몇 가지 기본적인 API함수를 제공한다.

– CLSID를 통하여 어떤 서버가 해당 클래스를 구현하고 있고, 그 서버 가 어디에 위치하고 있는지를 레지스트리에서 찾아낸다.

– COM개체가 로컬 또는 리모트 서버에서 실행되고 있을 때 클라이언 트를 대신하여 투명하게 RPC호출을 해준다.

• 1. COM라이브러리의 초기화

HRESULT CoInitialize( LPVOID pvReserved //Reserved; must be NULL ); void CoUninitialize( );

3. COM라이브러리 초기화 • 2. COM개체의 CLSID구하기

– COM개체의 인스턴스를 생성하기 위해서는 먼저 해당 COM개체의 클래스ID를 알아야 한다.

• 1. COM컴포넌트가 소스 코드 형태로 CLSID정의 파일을 제공한다면 해 당 파일을 COM클라이언트 어플리케이션의 프로젝트에 포함시켜 사용 할 수 있다.

• 2. 레지스트리 편집기를 이용하여 COM개체의 CLSID를 복사하여 사용 한다.

• 3. CLSID에 대응되는 ProgID를 사용한다.

HRESULT CLSIDFromProgID( LPCOLESTR lpszProgID , LPCLSID pclsid );

3. COM라이브러리 초기화 • 3. CoCreateInstance함수

– COM개체의 인스턴스를 생성하려면 COM라이브러리의 CoCreateInstance함수를 사용한다.

STDAPI CoCreateInstance( REFCLSID rclsid , //Class identifier (CLSID) of the object LPUNKNOWN DWORD pUnkOuter , dwClsContext , //Context for running executable code REFIID riid , //Reference to the identifier of the interface LPVOID * ppv ); – rclsid : 인스턴스를 생성하기 원하는 COM개체의 CLSID를 지정한다.

– pUnkOuter : 내부 COM개체에서 사용하게 될 IUnknown인터페이스 포인터를 지정한다.

• CLSCTX_INPROC_SERVER : 클라이언트는 같은 프로세스에서 실행하 는 COM컴포넌트를 사용한다. (DLL로 구현) • CLSCTX_INPROC_HANDLER : 인-프로세스 핸들러는 COM개체의 기 능 일부분 만을 구현한 인-프로세스 서버이다. 컴포넌트의 다른 부분은 로컬 또는 리모트 서버에서 실행되는 아웃-오브-프로세스서버로 구현

3. COM라이브러리 초기화

• CLSCTX_LOCAL_SERVER : 클라이언트는 같은 시스템의 다른 프로세 스에서 실행되는 COM컴포넌트를 사용한다.

• CLSCTX_REMOTE_SERVER : 클라이언트는 다른 시스템에서 실행되는 COM컴포넌트를 사용한다.

– riid : 생성된 COM개체에서 사용하고자 하는 인터페이스 식별자(IID) 를 지정한다.

• 자동화컴포넌트인 경우에는 형식 라이브러리에서 COM개체가 제공하는 인터페이스에 대한 정보를 얻어올 수 있다.

• COM컴포넌트가 커스텀 인터페이스만을 포함하는 경우에는 COM컴포 넌트에서 IID와 인터페이스 정의 코드를 소스코드 형태로 제공해야 한다.

– ppv : COM개체가 리턴하는 인터페이스 포인터가 저장된다.

Chapter

3.

COM개체 구현

1. COM인터페이스 정의 • IDL (Interface Definition Language)와 MIDL컴파일러

– MIDL컴파일러는 • 1. IDL파일을 컴파일하여 C와 C++에서 사용할 수 있는 인터페이스를 정의한 코드를 포함하는 헤더 파일을 생성한다.

• 2. 로컬 서버또는 프록시 서버 컴포넌트의 커스텀 인터페이스에 대한 프 록시와 스텁코드를 생성하는 기능을 제공한다.

• 3. 자동화에서 사용되는 형식 라이브러리를 생성하는 기능을 제공한다.

[ object, uuid(987845CD-1737-4470-AF0B-603603B197CC), helpstring("ICFoo Interface"), pointer_default(unique) ] interface ICFoo : IUnknown { [helpstring("method Add")] HRESULT Add(int x,int y,int * pResult); };

1. COM인터페이스 정의

– MIDL컴파일러는 IDL파일을 컴파일 하면 4개의 파일을 생성한다.

• COMTest5.h, COMTest5_i.c, COMTest5_p.c, dlldata.c

• COMTest5.h에는 C와 C++에서 사용할 수 있는 IDL파일에서 지정된 모 든 인터페이스를 정의한 코드가 저장된다.

EXTERN_C const IID IID_ICFoo; MIDL_INTERFACE("987845CD-1737-4470-AF0B-603603B197CC") ICFoo : public IUnknown { public: virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE Add( int x, int y, int __RPC_FAR *pResult) = 0; };

1. COM인터페이스 정의

• COMTest5_i.c에는 IDL파일에서 사용되는 모든 GUID,즉 IID를 정읳는 코드가 포함된다.

#ifndef __IID_DEFINED__ #define __IID_DEFINED__ typedef struct _IID { unsigned long x; unsigned short s1; unsigned short s2; unsigned char c[8]; } IID; #endif // __IID_DEFINED__ #ifndef CLSID_DEFINED #define CLSID_DEFINED typedef IID CLSID; #endif // CLSID_DEFINED const IID IID_ICFoo = {0x987845CD,0x1737,0x4470,{0xAF,0x0B,0x60,0x36,0x03,0xB1,0x97,0xCC}}; const IID LIBID_COMTEST5Lib = {0x76F0E273,0xCA37,0x499A,{0x9A,0x94,0x98,0x1C,0xD7,0x6B,0x23,0x38}}; const CLSID CLSID_CFoo = {0xA9994222,0xC08E,0x45FC,{0xA2,0xD7,0x34,0x16,0x18,0x91,0xA8,0xCD}}; #ifdef __cplusplus } #endif

1. COM인터페이스 정의

• COMTest5_p.c, dlldata.c에는 로컬 서버 또는 리모트 서버에 필요한 커 스텀 인터페이스에 대한 마샬링기능을 제공하는 프록시 스텁코드를 포 함하게 된다.

• IDL 기본 문법

[ object, uuid(987845CD-1737-4470-AF0B-603603B197CC), helpstring("ICFoo Interface"), pointer_default(unique) ] – []안에 인터페이스에 대한 속성 리스트가 놓이게 된다.

– uuid속성에는 해당 인터페이스의 UUID즉 IID가 저장된다.

– object속성은 이 인터페이스가 COM인터페이스라는 것을 나타낸다.

interface ICFoo : IUnknown { [helpstring("method Add")] HRESULT Add(int x,int y,int * pResult); };

1. COM인터페이스 정의

– IDL의 목적 중의 하나는 메서드의 매개변수가 마샬링될 수 있는 충 분한 정보를 제공하여, 필요한 데이터만 프로세스 사이에 넘겨주기 위해 마샬링에 사용되는 프록시/스텁코드를 최적화하는 것이다.

– [in]속성 • 클라이언트에서 컴포넌트로 이동하기만 한다는 것을 알려준다.

• 스텁코드는 이 매개변수를 통해서 어떠한 데이터든 리턴할 필요가 없어 진다.

– [out]속성 • 이 매개변수가 컴포넌트에서 클라이언트로 데이터를 리턴하기만 한다는 것을 알려준다.

• 이 속성은 언제나 포인터여야 한다.

– MILD 컴파일러는 object예약어가 사용된 인터페이스의 모든 메서드 는 HRESULT를 리턴하도록 제한하고 있다.

2. 클래스 팩토리 구현

– COM언어는 언어 독립적이기 때문에 new연산자를 사용할 수 없다.

– COM라이브러리의 CoCreateInstance함수를 사용하여야 한다.

STDAPI CoCreateInstance ( REFCLSID DWORD dwClsContext , REFIID riid rclsid , LPUNKNOWN , LPVOID * ppv ) { *ppv = NULL; IClassFactory * pIFactory = NULL; pUnkOuter , HRESULT hr = CoGetClassObject(rclsid, dwClsContext, NULL, IID_IClassFactory,(void **)pIFactory); if (SUCCEEDED(hr)) { hr = pIFactory->CreateInstance(pUnkOuter,riid,ppv); pIFactory->Release(); } return hr; } – 클라이언트가 CoCreateInstance함수를 사용하여 COM개체의 인스 턴스를 생성하려고 할 때 CoCreateInstance함수 내부에서는 CoGetClassObject함수를 호출하게 된다.

2. 클래스 팩토리 구현

– 1. CoGetClassObject함수 • 시스템 레지스트리의 HKEY_CLASSES_ROOT밑에 있는 CLSID서브 키 에서 rclsid 매개변수에 지정된 CLSID와 같은 서브키를 찾아, 해당 서브 키 밑에 있는 InprocServer32또는 LocalServer32서브 키 값에 지정된 COM개체를 포함하고 있는 COM컴포넌트 서버를 메모리에 로드한다.

– 2. COM 컴포넌트가 DLL로 구현된 In-process서버라면 CoGetClassObject는 DllGetClassObject익스포트 함수를 호출한다.

• DllGetClassObject는 IClassFactory인터페이스를 구현한 클래스 팩토 리 COM개체의 인스턴스를 생성하고, CoGetClassObject함수에 요청한 IClassFactory인터페이스 포인터를 리턴하게 된다.

– 3. CoGetClassObject에서 리턴된 IClassFactory인터페이스 포인터 를 통하여 IClassFactory::CreateInstance메서드를 호출한다.

• 클래스팩토리의 CreateInstance메서드에서는 자신과 관련을 맺고 있는 COM개체의 인스턴스를 생성하고, QueryInterface메서드를 호출하여 CreateInstance메서드의 두 번째 파라미터 riid에 지정된 인터페이스 포 인터를 구한다음, 해당 인터페이스 포인터를 세번쨰 매개변수 ppv에 리 턴하게 된다.

2. 클래스 팩토리 구현

– 클래스 팩토리 COM개체는 반드시 IClassFactory인터페이스를 지원 해야 한다.

interface IClassFactory : public IUnknown { HRESULT __stdcall CreateInstance(IUnknown * pUnkOuter,REFIID riid, void ** ppvObject); HRESULT __stdcall LockServer(BOOL fLock); }; – 클래스 팩토리 COM개체는 반드시 IClassFactory인터페이스를 지원 해야 한다.

– CreateInstance는 CLSID를 매개변수로 받아들이지 않는다. 하나의 클래스 팩토리 COM개체는 하나의 CLSID에 대응되는 COM개체의 인스턴스만 생성할 수 있다.

class CFactory : public IClassFactory { public: CFactory() : m_cRef(0) { cout << "팩토리 생성" << endl; } ~CFactory() { cout << "팩토리 소멸" << endl; } HRESULT __stdcall QueryInterface(const IID & iid, void ** ppv) { if (iid == IID_IUnknown || iid == IID_IClassFactory) { *ppv = static_cast(this); } else { *ppv = NULL; return E_NOINTERFACE; } static_cast(*ppv)->AddRef(); return S_OK; } virtual ULONG __stdcall AddRef() { m_cRef++; return m_cRef; }

private: }; virtual ULONG __stdcall Release() { if (--m_cRef == 0) { delete this; return 0; } return m_cRef; } virtual HRESULT __stdcall CreateInstance(IUnknown * pUnknownOuter, const IID & iid, void ** ppv) { if (pUnknownOuter != NULL) { return CLASS_E_NOAGGREGATION; } CA * pA = new CA; if (pA == NULL) { return E_OUTOFMEMORY; } HRESULT hr = pA->QueryInterface(iid,ppv); return hr; } HRESULT __stdcall LockServer(BOOL bLock) { if (bLock) g_cServerLocks++; else g_cServerLocks--; return S_OK; } long m_cRef;

3. 인-프로세스 서버 구현

– 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개체를 생성 하고 클라이언트에서 요청한 인터페이스 포인터를 리턴한다.

3. 인-프로세스 서버 구현

– 8. CoGetClassObject함수는 IClassFactory::CreateInstance함수에 서 리턴한 인터페이스 포인터를 다시 CoCreateInstance함수에 리턴 한다.

– 9. CoCreateInstance함수는 CoGetClassObject함수가 리턴한 인 터페이스 포인터를 다시 클라이언트에 리턴한다.

– 10. 클라이언트에서는 리턴된 인터페이스 포인터를 통하여 해당 인 터페이스에 구현된 메서드를 사용한다.

– COM개체를 사용할 때 호출되는 다음과 같은 4개의 익스포트 함수 가 포함되어 있어야 한다.

• 1. DllGetClassObject • 2. DllRegisterServer • 3. DllUnregisterServer • 4. DllCanUnloadNow

3. 인-프로세스 서버 구현 • DllGetClassObject 구현

extern "C" STDAPI DllGetClassObject(const CLSID & clsid, const IID & iid, void ** ppv) { if (clsid != CLSID_Component1) { return CLASS_E_CLASSNOTAVAILABLE; } CFactory * pFactory = new CFactory; if (pFactory == NULL) { return E_OUTOFMEMORY; } HRESULT hr = pFactory->QueryInterface(iid,ppv); if (FAILED(hr)) delete pFactory; return hr; }

3. 인-프로세스 서버 구현 • DllCanUnloadNow 함수 구현

– COM라이브러리는 사용되지 않는 DLL을 메모리에서 언로드하여 해 제시키는 역할을 하는 CoFreeUnusedLibraries라고 하는 함수를 제 공한다.

– CoFreeUnusedLibraries함수는 DLL에 구현된 DllCanUnloadNow함 수를 호출하여 DLL을 해제시켜도 좋은지 여부를 묻는다.

extern "C" STDAPI DllCanUnloadNow() { if ((g_cComponents == 0) && (g_cServerLocks == 0)) { return S_OK; } else { return S_FALSE; } }

3. 인-프로세스 서버 구현

– 클라이언트에서 IClassFactory인터페이스 포인터를 사용하는 동안 DLL이 언로드되지 못하게 할 방법이 필요하다.

– IClassFactory::LockServer멤버가 이것을 위한 기능을 제공한다.

HRESULT __stdcall LockServer(BOOL bLock) { if (bLock) g_cServerLocks++; else g_cServerLocks--; return S_OK; }

4. 로컬 서버 구현

– 인-프로세스 서버는 DLL로 구현되기 때문에 자신을 로드한 클라이 언트 어플리케이션의 프로세스 영역 안에서 실행된다.

– COM개체가 리턴한 인터페이스 포인터는 클라이언트와 같은 프로세 스 영역 안에 있게 된다.

– 로컬 서버나 리모트 서버에서 리턴한 인터페이스 포인터는 클라이언 트 어플리케이션에게는 아무런 의미가 없다.

– 각 프로세스는 자기 자신의 고유한 주소 영역을 가지며, 따라서 설사 같은 주소를 가르킨다고 해서 서로 다른 프로세스 영역에서는 각기 다른 물리적 메모리를 가르키게 된다.

– COM은 두 프로세스 사이의 커뮤니케이션수단으로 LPC와 RPC를 사용한다.

– LPC는 같은 시스템 상에서 실행되는 서로 다른 프로세스 사이의 커 뮤니커에션 방법이다.

– RPC는 광범위한 네트워크 전송 메커니즘을 사용하여 서로 다른 시 스템 상에서 실행되는 프로세스가 서로 커뮤니커이션하는 방법을 제 공한다.

4. 로컬 서버 구현

– 서로 다른 프로세스 영역 사이에 인터페이스 포인터가 넘겨질 때 Ole32.dll은 인터페이스를 마샬링하는데 필요한 프록시와 스텁코드 를 로드한다.

– 프록시는 클라이언트가 커뮤니케이션 하고자 하는 로컬 또는 리모트 서버에 있는 COM개체와 같은 인터페이스를 노출시킨다.

– 클라이언트에게 리턴되는 인터페이스 포인터는 실제로는 프록시가 노출하는 인터페이스 포인터가 된다.

– 이 인터페이스 포인터를 사용하여 메서드를 호출할 때도 실제로는 프록시안에 있는 코드가 실행된다.

– 프록시는 클라이언트로부터 넘겨 받은 매개변수를 전송할 수 있도록 포장하는 작업을 수행한다.

– 호출측의 데이터를 전송할 수 있는 표준 형식을 변환하는 것을 “마 샬링”이라고 한다. 그 반대 작업을 언마샬링이라고 한다.

– 프록시는 마샬링작업을 한 후 IPC채널을 통하여 실제 COM개체가 로드된 프로세스에게 필요한 데이터를 넘겨준다. 이 요청이 로컬또 는 리모트 서버에 도착하면, 스텁이 클라이언트에서 넘겨준 데이터 를 받아 언마샬링작업을 수행하고 COM개체 내부에 있는 메서드를 호출한다.

4. 로컬 서버 구현

– 표준 COM인터페이스에 대한 마샬링코드는 Ole32.dll과 Oleauto32.dll을 통하여 운영체제 레벨에서 자동적으로 제공된다.

Chapter

4.

Visual C++ 컴파일러

1. 형식 라이브러리 • 형식라이브러리란?

– COM컴포넌트가 노출하는 COM개체에 대한 정보를 포함하고 있 는 .TLB확장자를 가지는 일종의 문서이다.

– COM개체는 자신에 대한 정보를 형식 라이브러리를 제공함으로써 COM클라이언트 어플리케이션에게 COM개체에 대한 정보를 제공해 줄 수 있다.

};

1. 형식 라이브러리

[ uuid(9A735140-A38E-443B-B50F-EB69D60DD8D4), version(1.0), helpstring("COMTest4 1.0 Type Library") ] library { COMTEST4Lib importlib("stdole32.tlb"); importlib("stdole2.tlb"); [ uuid(1EB5954A-35C1-4BAD-9C35-9172DA647B66), helpstring("CFoo Class") ] coclass { CFoo [default] interface ICFoo; }; – coclass문 안에는 자동화 개체가 노출하는 인터페이스 리스트가 기 술된다.

1. 형식 라이브러리 • import 선행 처리기 지시어

– #import 선행 처리기 지시어는 형식 라이브러리에서 정보를 읽어 COM개체와 인터페이스가 기술된 C++헤더 파일로 변환하는 작업을 처리한다.

2. COM지원 클래스

• _com_ptr_t 스마트 포인터 클래스

– 스마트 포인터 기능을 갖고 있는 템플릿 클래스이다.

– COM인터페이스 포인터를 캡슐화하여 COM개체의 새로운 인스턴스 를 생성하는 CoCreateInstance함수나 IUnknown인터페이스의 AddRef(), Release() ,QueryInterface()함수의 기능을 클래스 안에 감춤으로써 개발자들이 편리하게 인터페이스 포인터를 사용할 수 있 게 한다.

– _COM_SMARTPTR_TYPEDEF(IAddEnd, __uuidof(IAddEnd));는 다 음과 같이 확장된다. typedef _com_ptr_t<_com_IID> IAddEndPtr; – IAddEndPtr pIAddEnd(_uuidof(AddBack)); – IAddEndPtr pIAddEnd(CLSID_AddBack); – IAddEndPtr pIAddEnd( “ AddBack.AddBack.2

” ); – _com_ptr_t스마트 포인터 클래스 생성자는 _com_ptr_t::CreateInstance함수를 호출하여인스턴스를 생성한다.

2. COM지원 클래스

– _com_ptr_t::operator = 멤버 함수 • IAddPtr pIAdd = pIAddEnd;는 아래의 • IAddPtr pIAdd; • pIAddEnd.QueryInterface(_uuidof(IAdd),&pIAdd);와 같다.

– _com_ptr_t를 사용하면 Release를 호출하지 않아도 된다.

Chapter

5.

Active Template Library 개요

1. 왜 ATL인가?

• ATL(Active Template Library)란 ?

– C++ 템플릿을 사용하여 작성된 클래스 라이브러리이다.

– ATL의 목적은 개발자들이 작고 빠르고 확장성을 갖는 COM객체를 손쉽게 구현할 수 있게 하는데 있다.

– ATL에는 COM개체를 구현한는데 필수적인 IUnknown, IClassFactory, 등의 인터페이스에 대한 코드가 구현되어 있다.

– 개발자들이 반복적으로 구현하지 않고도 COM개체의 고유한 서비스 를 구현하는 것만으로도 손쉽게 COM컴포넌트를 생성할 수 있게 한 다.

– ATL은 MFC와 달리 프레임워크를 지원하지 않는다. – 실행 라이브러리를 필요로 하지 않으므로 작고 빠른 COM컴포넌트 를 생성하는데 적합하다.

2. ATL 프로젝트 생성

– DLL서버 옵션을 선택하면 ATL COM AppWizard는 DllMain함수 외 에도 인-프로세스 서버 COM 컴포넌트가 제공해야 하는 DllGetClassObject, DllCanUnloadNow, DllRegisterServer,

2. ATL 프로젝트 생성

– DllUnregisterServer등의 익스포트 함수에 대한 구현 코드를 제공한 다.

– EXE서버 옵션을 선택하면 WinMain함수 안에 레지스트리 등록 및 해제 , ROT(Running Object Table)에 클래스 팩토리 COM개체 등 록 및 해제, 메시지 루프 등 아웃-오브-프로세스 서버 COM컴포넌 트가 갖추어야 하는 기본적인 코드를 생성해준다.

3. CComModule 클래스 객체와 객체 맵

– CComModule 클래스의 전역 객체 • CComModule _Module; – 객체 맵 • BEGIN_OBJECT_MAP(ObjectMap) • END_OBJECT_MAP() – CComModule클래스는 COM서버 모듈을 구현하며, 클라이언트 애 플리케이션에서 COM 서버에서 제공하는 COM개체에 접근할 수 있 게 한다.

멤버 함수 Init Term GetClassObject GetLockCount RegisterServer 설명 데이터 멤버를 초기화 한다.

데이터 멤버를 해제 한다.

지정된 CLSID의 COM개체를 생성한다.

COM서버 모듈의 현재 락 카운트를 리턴한다.

레지스트리에 개체 맵에 정의된 COM개체를 등록한다.

3. CComModule 클래스 객체와 객체 맵

멤버 함수 설명 UnregisterServer RegisterClassObjects RevokeClassObjects 레지스트리에 개체 맵에 정의된 COM개체의 등록을 해 제한다.

ROT(Running Object Table)에 클래스 팩토리 COM개 체를등록한다. (EXE전용) ROT(Running Object Table)에 클래스 팩토리 COM개 체를등록 해제한다. (EXE전용) – CComModule 클래스의 전역 객체는 개체 맵을 사용하여 COM서버 모듈에 포함된 COM개체를 관리한다.

– BEGIN_OBJECT_MAP과 END_OBJECT_MAP매크로 사이에 COM개 체에 대한 OBJECT_ENTRY매크로 항목이 온다.

BEGIN_OBJECT_MAP(ObjectMap) OBJECT_ENTRY(CLSID_CMyObj1, CCMyObj1) END_OBJECT_MAP()

3. CComModule 클래스 객체와 객체 맵

– 객체 맵은 _ATL_OBJMAP_ENTRY구조체 배열로 구현된다.

• 시스템 레지스트리에 COM개체 등록 및 해제 방법 • 클래스 팩토리 COM개체 생성 방법 • COM개체 생성 방법 #define BEGIN_OBJECT_MAP(x) static _ATL_OBJMAP_ENTRY x[] = { #define END_OBJECT_MAP() {NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}}; #define OBJECT_ENTRY(clsid, class) { &clsid, class::UpdateRegistry, class::_ClassFactoryCreatorClass::CreateInstance, class::_CreatorClass::CreateInstance, NULL, 0, class::GetObjectDescription, class::GetCategoryMap, class::ObjectMain }

3. CComModule 클래스 객체와 객체 맵

카테고리 Objects COM 개체 SimpleObject 유형설정 이중 인터페이스를 지원하는 자동화 개체 Internet Explorer Object ActiveX Server Component MS Internet Explorer에서 사용되는 COM개체 MS IIS의 Active Server Page에서 사용되는 COM개체

3. CComModule 클래스 객체와 객체 맵

카테고리 Objects Controls COM 개체 Microsoft Transactin Server AddIn Object Component Registrar Object Full Control 유형설정 MS Transaction Server에서 사용되는 COM 개체 MS Developer Studio에서 사용되는 애드인 레지스트리 정보를 포함하는 개체 모든 ActiveX컨트롤 컨테이너에서 사용될 수 있는 완전한 기능을 갖춘 ActiveX컨트롤 MS IE에서 사용되는 ActiveX컨트롤 Miscellane ous Internet Explorer Control Property Page Dialog 속성 페이지 대화상자

#ifndef __CMYOBJ1_H_ #define __CMYOBJ1_H_ #include "resource.h" // main symbols ///////////////////////////////////////////////////////////////////////////// // CCMyObj1 class ATL_NO_VTABLE CCMyObj1 : public CComObjectRootEx, public CComCoClass, public IDispatchImpl { public: CCMyObj1() { } DECLARE_REGISTRY_RESOURCEID(IDR_CMYOBJ1) DECLARE_PROTECT_FINAL_CONSTRUCT() BEGIN_COM_MAP(CCMyObj1) COM_INTERFACE_ENTRY(ICMyObj1) COM_INTERFACE_ENTRY(IDispatch) END_COM_MAP() // ICMyObj1 public: }; #endif //__CMYOBJ1_H_

1. ATL Class 구조

– public CComObjectRootEx • 이 클래스와 이 클래스의 base클래스인 CComObjectRootBase는 IUnknown메소드의 구현 처리를 책임진다.

– public CComCoClass • COM객체가 생성되는 방법을 정의한다. (클래스 팩토리) – 클래스에 노출된 모든 인터페이스는 COM맵에 엔트리를 가진다.

BEGIN_COM_MAP(CCMyObj1) COM_INTERFACE_ENTRY(ICMyObj1) COM_INTERFACE_ENTRY(IDispatch) END_COM_MAP() – COM맵에 정의된 정보를 사용하여 COM개체가 제공하는 인터페 이스를 클라이언트에 노출시키게 된다.

– COM맵에 정의된 항목의 순서는 QueryInterface메서드가 인터페 이스를 찾는 순서가 된다.

– DECLARE_REGISTRY_RESOURCEID(IDR_CMYOBJ1) • .rgs file을 자동으로 Registry에 등록한다.

2. ATL 기초 Class

– COM개체 클래스는 COM개체가 제공하는 고유한 인터페이스 외 에도 다음과 같은 템플릿 클래스에서 파생된다.

class ATL_NO_VTABLE CCFoo : public CComObjectRootEx, public CComCoClass, public IDispatchImpl { – CComObjectRoot또는 CCOmObjectRootEx템플릿 클래스는 ATL COM개체의 IUnknown인터페이스의 내부 구현 코드를 제공 한다.

• InternalAddRef, InternalRelease, InternalQueryInterface메서드 – CComCoClass템플릿 클래스는 COM개체의 디폴트 클래스 팩토 리 COM개체를 정의한다.

– CComObjectRootBase와 CComObejctRootEx에서 구현된 InternalAddRef, InternalRelease, InternalQueryInterface메서드 는

2. ATL 기초 Class

– COM개체의 레퍼런스 카운트와 인터페이스 조회기능을 구현하 며,OuterAddRef, OuterRelease, OuterQeuryInterface메서드는 COM개체가 통합으로 사용될 때 외부 COM개체에 레퍼런스 카운 트와 인터페이스 조회 기능을 위임하는 코드를 구현한다.

– CComCoClass 템플릿 클래스는 COM개체의 디폴트 클래스 팩토 리 COM개체를 정의하며, 사용자 COM개체 클래스는 반드시 CComCoClass템플릿 클래스에서 파생되어야 한다.

class CComCoClass { public: DECLARE_CLASSFACTORY() DECLARE_AGGREGATABLE(T) – DECLARE_CLASSFACTORY()매크로를 사용하여 CComClassFactory클래스가 해당 COM개체의 클래스 팩토리라 는 것을 지정한다.

2. ATL 기초 Class

#define DECLARE_CLASSFACTORY_EX(cf) typedef CComCreator< CComObject< cf > > _ClassFactoryCreatorClass; #define DECLARE_CLASSFACTORY() DECLARE_CLASSFACTORY_EX(CComClassFactory) – COM개체의 클래스 팩토리는 CComCloassFactory클래스이며, 클래스 팩토리 COM개체의 인스턴스를 생성하는 클래스가 CComCreator템플릿 클래스라는 것을 의미한다.

– CComClassFactory클래스는 IClassFactory인터페이스를 구현한 클래스이고, CComCreator템플릿 클래스는 내부적으로 COM개 체를 생성하는 역할을 수행하는 COM개체 생성자 클래스 이다.

– CComObject클래스는 사용자 COM개체 클래스 이름을 템플릿 매 개변수로 받아들여 사용자 COM개체 클래스에서 파생한 새 클래 스를 생성하고 AddRef,Release,QueryInterface메서드를 재정의 한다.

3. 사용자 COM개체 생성 과정

struct _ATL_OBJMAP_ENTRY { const CLSID* pclsid; HRESULT (WINAPI *pfnUpdateRegistry)(BOOL bRegister); _ATL_CREATORFUNC* pfnGetClassObject; _ATL_CREATORFUNC* pfnCreateInstance; IUnknown* pCF; DWORD dwRegister; _ATL_DESCRIPTIONFUNC* pfnGetObjectDescription; – 클라이언트 어플리케이션에서 CoCreateInstance함수를 호출하 면 COM라이브러리의 CoGetClassObject함수는 시스템 레지스트 리에서 CLSID정보를 읽어 인-프로세스 서버를 메모리에 로드한 다.

– 이 과정에서 인-프로세스 서버에 정의된 CComModule클래스의 전역 개체인 _Module가 생성되고, 개체 맵 정의에 사용된 정보로 _ATL_OBJMAP_ENTRY구조체 배열이 생성된다.

3. 사용자 COM개체 생성 과정

#define OBJECT_ENTRY(clsid, class) { &clsid, class::UpdateRegistry, class::_ClassFactoryCreatorClass::CreateInstance, class::_CreatorClass::CreateInstance, NULL, 0, class::GetObjectDescription, class::GetCategoryMap, class::ObjectMain }, static _ATL_OBJMAP_ENTRY ObjectMap[] = { { &CLSID_AddBack, CAddBack::UpdateRegistry, CAddBack ::_ClassFactoryCreatorClass::CreateInstance, CAddBack ::_CreatorClass::CreateInstance, NULL, 0, CAddBack ::GetObjectDescription, CAddBack ::GetCategoryMap, CAddBack ::ObjectMain }, {0,0,0,0,0,0,0} };

3. 사용자 COM개체 생성 과정

– COM라이브러리의 CoGetClassObject함수가 해당COM개체의 클 래스 팩토리를 생성하기 위해 인-프로세스 서버의 DllGetClassObject익스포트 함수를 호출한다.

– DllGetClassObject함수에서 CComModule::GetClassObject메서 드를 호출하여 ATL_OBJMAP_ENTRY구조체의 pfnGetClassObject멤버에 저장된 함수 즉,CComCreator::CreateInstance메서드를 호출한다.

– CComCreator::CreateInstance메서드는 이 클래스의 템플릿 매 개변수로 넘어온 CComObject클래스의 인 스턴스를 동적으로 생성하고 IUnknow인터페이스 포인터를 ATL_OBJMAP_ENTRY구조체의 pCF멤버에 저장한 후,CoGetClassObject함수에 IClassFactory인터페이스 포인터를 넘겨주게 된다.

– COM라이브러리의 CoGetClassObject함수가 사용자 COM개체를 생성하기 위해 리턴된 클래스 팩토리 COM개체의 IClassFactory 인터페이스 포인터를 통하여 IClassFactory::CreateInstance메서 드를 호출한다.

3. 사용자 COM개체 생성 과정

– CComClassFactory클래스의 CreateInstance메서드는 ATL_OBJMAP_ENTRY구조체의 pfnCreateInstance포인터 멤버에 저장된 함수 즉, CComCreator2::CreateInstance메서드를 호출한 다.

– CCOmCreator2::CreateInstance메서드는 매개변수로 넘어온 외 부 COM개체의 IUnknown인터페이스 포인터가 NULL인지 여부를 검사하여 단독으로 COM개체가 생성되는 경우에는 CComObject클래스를 템플릿 매개변수로, 외부 COM 개체에 의해 통합되어 COM개체가 생성되는 경우에는 CComAggObject클래스를 템플릿 매개변수로 CComCreator::CreateInstance메서드를 호출한다.

– COMCreator::CreateInstance메서드는 이 클래스의 템플릿 매개 변수로 넘어온 클래스의 인스턴스를 동적으로 생성하고, 요청된 인터페이스 포인터를 클라이언트에 넘겨준다.

4. ATL_NO_VTABLE매크로

– #define ATL_NO_VTABLE __decpspec(novtable) – __declspec(novtable)가 클래스 선언에 사용되면 클래스 생성자 와 소멸자에서 가상 함수 테이블 포인터가 초기화되는 것을 막아 준다.

– 우리가 정의한 사용자 COM클래스는 추상 클래스이다.

– 추상 클래스의 생성자에서 초기화된 가상 함수 테이블 포인터는 파생클래스의 생성자에 의해 다시 한번 덮어 씌여지게 되므로 필 요 없게 된다.

CComObjectRootEx CComCoClass CAddBack CComObejct

5. ATL 스마트 포인터 클래스

template < class T > class CComPtr template < class T, const IID * piid > class CComQIPtr – 두 클래스는 모두 인터페이스에 대하여 자동적으로 AddRef와 Release를 호출하여 레퍼런스 카운트를 수행한다.

– 연산자 멤버 함수를 제공하여 포인터를 조작할 수 있게 하므로 인 터페이스 포인터 변수와 이들 클래스의 인스턴스를 구별없이 사 용할 수 있게 한다.

– CComQIPtr은 추가로 QueryInterface를 통하여 자동적으로 인터 페이스를 질의 하는 기능을 제공한다.

5. ATL 스마트 포인터 클래스

#include #import "COMTest4.tlb" no_namespace void CCOMUse4Dlg::OnButton1() { // TODO: Add your control notification handler code here CComPtr pFI; HRESULT hr = pFI.CoCreateInstance(__uuidof(CFoo)); int i; if (SUCCEEDED(hr)) { pFI->Add(10,20,&i); } CComQIPtr QIptr; hr = QIptr.CoCreateInstance(__uuidof(CFoo)); if (SUCCEEDED(hr)) { QIptr->Add(10,20,&i); } }

Chapter

6.

자동화와 이중 인터페이스

1. 자동화란 무엇인가?

– 애플리케이션은 자신의 기능을 다른 애플리케이션에 노출해야 한 다. (다른 애플리케이션이 프로그래밍하는 일이 가능해야 한다.) • 메서드, Property, 이벤트 – 자동화는 이처럼 다른 애플리케이션이 프로그래밍할 수 있게 하 는 서비스이다.

– 자동화 서비스를 제공하는 측의 애플리케이션을 자동화 컴포넌트 – 자동화 서비스를 사용하는 측의 애플리케이션을 자동화 컨트롤러 라 한다.

– 자동화 컴포넌트는 세가지 형태로 자동화 컨트롤러에게 기능을 제공한다.

• 1. 메서드 : 자동화 개체가 수행할 수 있는 어떤 행위이다.

• 2. 속성 : 자동화 개체의 상태에 대한 정보에 접근할 수 있는 기능이 다.

• 3. 이벤트 : 자동화 개체에 어떤 사건이 발생했다는 사실을 클라이언 트에게 알려주는 방법을 제공한다.

2. 자동화의 이점과 단점

• 자동화를 사용할 때의 이점

– 1. 스크립트 언어를 사용하는 클라이언트 어플리케이션에서도 자 동화 개체를 사용할 수 있게 된다.

– 2. 마샬링 코드를 운영체제에서 제공해주기 때문에 프록시/스텁 DLL을 생성할 필요가 없다.

• 자동화를 사용할 때의 단점

– C++과 같은 COM인터페이스에 직접 접근할 수 있는 언어를 사용 하는 클라이언트 애플리케이션에서는 실행 속도에 불이익을 받게 된다.

– 자동화 개체는 디스패치 인터페이스를 제공해야 한다.

– 직접 COM개체의 메서드를 호출하는 대신에 자동화 개체가 제공 하는 IDispatch인터페이스의 Invoke메서드를 통하여 간접적으로 자동화 개체가 지원하는 메서드에 접근하게 된다.

– 마이크로 소프트는 이러한 자동화의 문제점을 해결하기 위해 자 동화 개체가 이중 인터페이스를 지원할 것을 권하고 있다.

2. 자동화의 이점과 단점

– 이것은 자동화 개체가 디스패치 인터페이스 외에도 IDispatch인 터페이스에서 파생되는 커스텀 인터페이스를 노출시킴으로써, C++같이 COM인터페이스에 직접 접근할 수 있는 언어에서는 커 스텀 인터페이스를 통하여 메서드를 호출할 수 있게 한다.

– 자동화 개체가 사용할 수 있는 데이터형이 제한되어 있다.

• 자동화 기술은 대부분의 언어에서 제공하는 표준 데이터형과 문자열, 배열등으로 사용할 수 있는 데이터형을 제한한다.

• 구조체와 같은 사용자 정의 데이터형을 직접 지원하지 않는다.

};

3. IDispatch인터페이스

IDispatch : public IUnknown { public: virtual HRESULT STDMETHODCALLTYPE GetTypeInfoCount( /* [out] */ UINT __RPC_FAR *pctinfo) = 0; virtual HRESULT STDMETHODCALLTYPE GetTypeInfo( /* [in] */ UINT iTInfo, /* [in] */ LCID lcid, /* [out] */ ITypeInfo __RPC_FAR *__RPC_FAR *ppTInfo) = 0; virtual HRESULT STDMETHODCALLTYPE GetIDsOfNames( /* [in] */ REFIID riid, /* [size_is][in] */ LPOLESTR __RPC_FAR *rgszNames, /* [in] */ UINT cNames, /* [in] */ LCID lcid, /* [size_is][out] */ DISPID __RPC_FAR *rgDispId) = 0; virtual /* [local] */ HRESULT STDMETHODCALLTYPE Invoke( /* [in] */ DISPID dispIdMember, /* [in] */ REFIID riid, /* [in] */ LCID lcid, /* [in] */ WORD wFlags, /* [out][in] */ DISPPARAMS __RPC_FAR *pDispParams, /* [out] */ VARIANT __RPC_FAR *pVarResult, /* [out] */ EXCEPINFO __RPC_FAR *pExcepInfo, /* [out] */ UINT __RPC_FAR *puArgErr) = 0;

3. IDispatch인터페이스

– IDispatch인터페이스는 IUnknown인터페이스 멤버 외에도 GetTypeInfoCount, GetTypeInfo, GetIDsOfNames, Invoke등 4 개의 멤버 함수를 포함한다.

• dim obj As Object • set obj = CreateObject( “ AddBack.AddBack.1

” ) • obj.Prop = propValue • obj.Method

– VisualBasic함수인 CreateObject가 호출될 때 • 1. VisualBasic은 COM라이브러리인 CLSIDFromProgID함수를 호출 하여 “AddBack.AddBack.1

” 에 해당하는 CLSID를 얻어온다.

• 2. CoCreateInstance함수를 호출하여 자동화 개체 인터페이스를 생 성하고, IDispatch 인터페이스 포인터를 VisualBasic의 obj라고 하는 개체 변수에 저장한다.

– 이 개체 변수를 통하여 자동화 개체가 제공하는 메서드나 속성이 참조될 때 • 1.Visual Basic은 IDispatch인터페이스의 GetIDsOfNames멤버함수

3. IDispatch인터페이스

• 를 호출하여 해당 메소드나 속성이름에 대응되는 DISPID값을 구한다.

– Prop이란 속성 이름과 Method라는 메서드 이름에 각각 대응되는 DISPID값을 구한다.

• DISPID를 매개변수로 IDispatch인터페이스의 Invoke함수를 호출한 다.

• 이때 자동화 개체의 IDispatch::Invoke멤버 함수 구현 코드에서는 매 개변수에 넘어온 DISPID에 대응되는 적절할 행위를 수행한다.

– Invoke함수는 COM인터페이스와 유사한 함수 포인터 배열을 생 성하고, 매개변수로 넘어온 DISPID를 이 함수 포인터 배열의 인덱 스로 사용하여 DISPID에 대응되는 함수를 호출하는 방식을 사용 한다.

– IDispatch::Invoke함수에 의하여 구현되는 함수 포인터 배열을 디 스패티 인터페이스(dispatch interface)또는 줄여서 dispinterface 라고 한다.

3. IDispatch인터페이스

pVtbl QueryInterface AddRef Release GetTypeInfoCount GetTypeInfo GetIDsOfNames Invoke 1 2 3 1 2 3

Prop1

” “

Prop2

” “

Method

get_Prop1 put_Prop2 Method

3. IDispatch인터페이스

virtual HRESULT STDMETHODCALLTYPE GetIDsOfNames( /* [in] */ REFIID riid, /* [size_is][in] */ LPOLESTR __RPC_FAR *rgszNames, /* [in] */ UINT cNames, /* [in] */ LCID lcid, /* [size_is][out] */ DISPID __RPC_FAR *rgDispId) = 0; – riid : 항상 ID_NULL로 예약되어 있다.

– rgszNames : DISPID를 구하고자 하는 속성 또는 메서드명이 배 열형태로 지정된다.

– cNames : rgszNames의 매개변수에 사용된 배열의 수가 지정된 다.

• GetIDsOfNames멤버 함수는 한 번에 하나의 속성또는 메서드에 대 한 DISPID를 구하기 때문에 항상 1이다.

– lcid : 로케일 정보가 저장된다.

– rgDispId : rgszNames매개변수에 지정된 속성 또는 메서드에 대 한 DISPID값이 배열 형태로 저장된다.

{ virtual HRESULT STDMETHODCALLTYPE GetIDsOfNames( REFIID riid, LPOLESTR __RPC_FAR *rgszNames, UINT cNames, LCID lcid, DISPID __RPC_FAR *rgDispId) HRSULT hr = S_OK; char name[50]; wcstombs(name, rgszNames[0], 49); if (cNames != 1 ) { hr = E_INVALIDARG; return hr; } if (lstrcmp(name, "AddEnd") == 0) rgDispId[0] = 1; else if(lstrcmp(name, "Sum") == 0) rgDispId[0] = 2; else if(lstrcmp(name, "Add") == 0) rgDispId[0] = 3; else if(lstrcmp(name, "AddTen") == 0) rgDispId[0] = 4; else if(lstrcmp(name, "Clear") == 0) rgDispId[0] = 5; else { rgDispId[0] = DISPID_UNKNOWN; hr = DISP_E_UNKNOWNNAME; } return hr; }

3. IDispatch인터페이스

virtual /* [local] */ HRESULT STDMETHODCALLTYPE Invoke( /* [in] */ DISPID dispIdMember, /* [in] */ REFIID riid, /* [in] */ LCID lcid, /* [in] */ WORD wFlags, /* [out][in] */ DISPPARAMS __RPC_FAR *pDispParams, /* [out] */ VARIANT __RPC_FAR *pVarResult, /* [out] */ EXCEPINFO __RPC_FAR *pExcepInfo, /* [out] */ UINT __RPC_FAR *puArgErr) = 0; – dispIdMember : 자동화 컨트롤러가 호출하기를 원하는 함수의 DISPID값이 지정된다.

– riid : IID_NULL – lcid : GetUserDefaultLCID()의 리턴 값 – wFalgs : 어떤 함수를 호출해야 할지에 대한 값 • DISPATCH_METHOD : 일반 함수 즉 메서드 • DISPATCH_PROPERTYGET : 속성을 읽는 함수 • DISPATCH_PROPERTYPUT : 속성을 저장하는 함수 • DISPATCH_PROPERTYPUTREF : 레퍼런스로 속성을 저장하는 함수

3. IDispatch인터페이스

typedef struct tagDISPPARAMS { /* [size_is] */ VARIANTARG __RPC_FAR *rgvarg; /* [size_is] */ DISPID __RPC_FAR *rgdispidNamedArgs; UINT cArgs; UINT cNamedArgs; } DISPPARAMS; – pDispParams : 내부적으로 호출되는 함수에 전달한 매개변수가 저장된다.

– pValResult : Invoke멤버 함수에 의해 실행된 메서드나 propget속 성의 결과를 저장하는 VAIANT포인터이다.

– 결과 값을 리턴하지 않는 메서드나 propput또는 propputref속성 에 대해서는 NULL이된다.

– 자동화 개체의 메서드나 속성이 매개변수와 리턴되는 결과값의 정보는 VARIANT데이터형이다.

4. 이중인터페이스

– 디스패치 인터페이스는 IDispatch::Invoke함수를 통하여 모든 기능 을 제공하게 된다.

– 자동화 컨트롤러에서는 IDispatch::Invoke함수를 호출하기 전에 많 은 매개변수를 스택에 저장해야 한다.

• 먼저 적절한 DISPPARAM와 VARIANTARG구조체를 생성한 다음, 이들 매개변수 포인터를 스택에 저장한 후에라야 Invoke를 호출할 수 있다.

– C++과 같은 일반적인 COM인터페이스에 접근할 수 있는 언어로 작 성된 클라이언트 어플리케이션에서는 직접 COM인터페이스에 접근 하는 것이 효율적이다.

– 자동화 개체는 IDispatch인터페이스 외에도 이와 동일한 역할을 하 는 커스텀 인터페이스를 함께 제공함으로써, 일반적인 COM인터페이 스에 접근할 수 있는 언어에서도 효율적으로 서비스를 사용할 수 있 게 한다.

4. 이중인터페이스

Private Sub Command1_Click() Dim obj As Object Set obj = CreateObject("COMTest6.MyAdd.1") Dim iResult As Integer iResult = obj.Add(30, 40) End Sub Private Sub Command1_Click() Dim obj As Object Set obj = CreateObject("COMTest6.MyAdd.1") Dim iResult As Integer obj.XValue = 20 obj.YValue = 40 iResult = obj.InternalAdd() End Sub

import "oaidl.idl"; import "ocidl.idl"; [ object, uuid(88680A7E-98C2-4AA4-A022-F7DF1B18133B), dual, helpstring("IMyAdd Interface"), pointer_default(unique) ] interface IMyAdd : IDispatch { [id(1), helpstring("method Add")] HRESULT Add([in] int x,[in] int y,[out, retval] int * iResult); [propget, id(2), helpstring("property XValue")] HRESULT XValue([out, retval] long *pVal); [propput, id(2), helpstring("property XValue")] HRESULT XValue([in] long newVal); [propget, id(3), helpstring("property YValue")] HRESULT YValue([out, retval] long *pVal); [propput, id(3), helpstring("property YValue")] HRESULT YValue([in] long newVal); [id(4), helpstring("method InternalAdd")] HRESULT InternalAdd([out, retval]int * pResult); }; [ uuid(AE0B144D-6900-4546-A326-78E6689DF728), version(1.0), helpstring("COMTest6 1.0 Type Library") ]

library COMTEST6Lib { importlib("stdole32.tlb"); importlib("stdole2.tlb"); [ uuid(A5F48B79-DC63-49A5-BE29-E96859BD6F7B), helpstring("MyAdd Class") ] coclass MyAdd { [default] interface IMyAdd; }; };

class ATL_NO_VTABLE CMyAdd : public CComObjectRootEx, public CComCoClass, public IDispatchImpl { public: CMyAdd() { XValue = 10; YValue = 20; } DECLARE_REGISTRY_RESOURCEID(IDR_MYADD) DECLARE_PROTECT_FINAL_CONSTRUCT() BEGIN_COM_MAP(CMyAdd) COM_INTERFACE_ENTRY(IMyAdd) COM_INTERFACE_ENTRY(IDispatch) END_COM_MAP() // IMyAdd public: STDMETHOD(InternalAdd)(int * pResult); STDMETHOD(get_YValue)(/*[out, retval]*/ long *pVal); STDMETHOD(put_YValue)(/*[in]*/ long newVal); long YValue; long XValue; STDMETHOD(get_XValue)(/*[out, retval]*/ long *pVal); STDMETHOD(put_XValue)(/*[in]*/ long newVal); STDMETHOD(Add)(int x,int y,int * iResult); };

// MyAdd.cpp : Implementation of CMyAdd #include "stdafx.h" #include "COMTest6.h" #include "MyAdd.h" ///////////////////////////////////////////////////////////////////////////// // CMyAdd STDMETHODIMP CMyAdd::Add(int x, int y, int *iResult) { // TODO: Add your implementation code here *iResult = x + y; return S_OK; } STDMETHODIMP CMyAdd::get_XValue(long *pVal) { // TODO: Add your implementation code here *pVal = XValue; return S_OK; } STDMETHODIMP CMyAdd::put_XValue(long newVal) { // TODO: Add your implementation code here XValue = newVal; return S_OK; }

STDMETHODIMP CMyAdd::get_YValue(long *pVal) { // TODO: Add your implementation code here *pVal = YValue; return S_OK; } STDMETHODIMP CMyAdd::put_YValue(long newVal) { // TODO: Add your implementation code here YValue = newVal; return S_OK; } STDMETHODIMP CMyAdd::InternalAdd(int *pResult) { // TODO: Add your implementation code here *pResult = XValue + YValue; return S_OK; }

Chapter

7.

이벤트 구현

1. 이벤트와 커넥션 포인트

– 자동화 개체는 클라이언트인 자동화 컨트롤러에게 이벤트를 발생 시킬 수 있다.

– 이벤트란 자동화 개체가 자신에게 어떠한 사건이 발생했다는 사 실을 자동화 컨트롤러에게 알려주는 기능이다.

– 자동화 컨트롤러가 자동화 개체에게 어떤 작업을 요청하기 위해 메서드를 호출한다면, 역으로 자동화 개체가 자동화 컨트롤러에 게 어떤 작업을 요청하기 위해서는 이벤트를 발생시켜야 한다.

– 이벤트 커넥션 포인트라고 하는 매커니즘으로 구현된다.

– 커넥션 포인트는 두 개의 부분으로 구성된다.

• 1. 이벤트를 발생시키는 소스 인터페이스 • 2. 이벤트를 받아들이는 싱크 인터페이스이다.

• 소스인터페이스를 구현한 객체를 소스 개체라고 한다. 일반적으로 자 동화 개체는 소스 인터페이스 구현 코드를 포함한다.

• 싱크 개체는 자동화 컨트롤러 안에서 별도로 생성된다.

– 이벤트를 발생시키기 위해 자동화 개체에서 호출하는 메서드는 자동화 컨트롤러에서 제공하는 싱크 인터페이스의 메서드이다.

1. 이벤트와 커넥션 포인트

– 자동화 컨트롤러에서는 자동화 개체가 발생시키는 이벤트에 대한 정보를 자동화 개체의 소스 인터페이스에서 구하여 싱크 인터페 이스를 생성한 후, 싱크 인터페이스의 포인터를 자동화 개체에게 다시 넘겨준다.

– 자동화 개체에서는 싱크 인터페이스의 포인터를 사용하여 자동화 컨트롤러에 구현된 싱크 인터페이스의 메서드를 호출할 수 있게 된다.

– 자동화 컨트롤러는 소스 인터페이스에 대한 정보를 얻기 위하여 자동화 개체에게 IProvideClassInfo2인터페이스를 요청한여 IProvideClassInfo2::GetClassInfo메서드를 호출한다.

– 이 메서드는 자동화 개체의 형식 라이브러리에서 소스 인터페이 스에 대한 정보를 넘겨주게 된다.

– 자동화 컨트롤러가 이벤트에 대한 소스 인터페이스를 구했다면 이 정보를 사용하여 싱크 인터페이스를 생성할 수 있게 된다.

– 싱크 인터페이스는 디스패치 인터페이스 또는 이중 인터페이스로 구현된다.

1. 이벤트와 커넥션 포인트

– 위와 같은 과정은 “Import ” 지시어를 사용하여 형식 라이브러리에 대한 정보를 얻는다면 C++ 컴파일러의 도움으로 자동화 개체의 소스 인터페이스를 구하여 이 정보를 기반으로 싱크 인터페이스 를 생성할 수 있게 된다.

– 자동화 컨트롤러에서 생성된 싱크인터페이스는 자동화 개체가 IConnectionPointContainer와 IConnectionPopint인터페이스를 지원하여 자동화 개체에게 넘겨준다.

– 자동화 컨트롤러가 자동화 개체에게 IConnectionPointContainer 인터페이스를 요청한 후 – IConnectionPointContainer::FindConnectionPoint메서드를 호 출하여 소스 인터페이스에 대한 IConnectionPoint인터페이스 포 인터를 구한다.

– IConnectionPoint::Advise메서드를 호출하여 앞에서 생성한 싱크 인터페이스 포인터를 매개변수로 넘겨주게 된다.

// ADD.cpp : Implementation of CADD #include "stdafx.h" #include "COMTest8.h" #include "ADD.h" ///////////////////////////////////////////////////////////////////////////// // CADD STDMETHODIMP CADD::ADD(long x, long y, long *pResult) { // TODO: Add your implementation code here *pResult = x + y; return S_OK; } STDMETHODIMP CADD::get_XValue(long *pVal) { // TODO: Add your implementation code here *pVal = XValue; return S_OK; }

STDMETHODIMP CADD::put_XValue(long newVal) { // TODO: Add your implementation code here XValue = newVal; CProxy_IADDEvents::Fire_XValueChanged(); return S_OK; } STDMETHODIMP CADD::get_YValue(long *pVal) { // TODO: Add your implementation code here *pVal = YValue; return S_OK; } STDMETHODIMP CADD::put_YValue(long newVal) { // TODO: Add your implementation code here YValue = newVal; CProxy_IADDEvents::Fire_YValueChanged(); return S_OK; }

1. 이벤트와 커넥션 포인트

Private WithEvents Obj As Add Private Sub Command1_Click() Set Obj = New Add Dim lResult As Long Obj.XValue = 10 Obj.YValue = 20 lResult = Obj.Add(10, 20) MsgBox lResult End Sub Private Sub Obj_XValueChanged() MsgBox "X값 변경" End Sub Private Sub Obj_YValueChanged() MsgBox "Y값 변경" End Sub

Chapter

8.

AxtiveX 컨트롤 개요

1. ActiveX 컨트롤이란?

– 인터넷이 확산되면서 OLE컨트롤은 ActiveX컨트롤이란 이름으로 진화되었다.

– OLE컨트롤은 표준으로 정의된 모든 인터페이스를 반드시 구현해 주어야 했다. 이것은 이진 파일의 크기를 크게 만드는 요인이 되 었다.

– 인터넷 환경에 효율적으로 사용될 수 있도록 확장된 OLE컨트롤이 필요했다. -> AcviteX – OLE컨트롤을 사용할 수 있는 컨테이너라면 ActiveX컨트롤을 사 용할 수 있다.

– OLE컨트롤과 ActiveX컨트롤의 큰 차이점은 컨트롤이 지원하는 인터페이스 표준이다.

• ActiveX컨트롤은 IUnknown 인터페이스와 자기 등록 기능만 구현하 면 된다.

2. ActiveX 컨트롤의 특징

– ActiveX컨트롤은 컨트롤 컨테이너에 포함되어 사용자 그래픽 인 터페이스를 제공한다.

– 컨트롤이 사용자 그래픽 인터페이스를 지원하지 않는다면, 컨트 롤은 인-프로세스 서버로 구현된 자동화 개체와 다를 바가 없다.

– ActiveX 컨트롤 사양은 컨트롤이 선택할 수 있는 표준 속성과 표 준 메서드, 표준 이벤트를 정의한다.

– 환경속성이란 컨테이너가 컨트롤에게 제공하는 속성이다.

– 컨트롤은 컨테이너가 제공하는 배경 색상과 같은 환경 속성을 사 용하는 컨테이너와 동일한 배경 색상을 사용함으로써, 컨트롤이 컨테이너의 일부분인 것과 같은 효과를 낼 수 있다.

– 확장 속성이란 겉으로 보기에는 컨트롤에 구현되어 있는 것처럼 보이지만 실제로는 컨테이너에 의해 구현된 속성을 말한다.

– 확장 컨트롤이 속성 또는 메서드를 인식하면 속성이나 메서드가 요구하는 행위를 수행하게 되고, 인식하지 못한다면 컨트롤에게 넘겨주게 된다.

3. ActiveX 컨트롤 표준 인터페이스

– 대부분의 컨트롤은 컨테이너에 포함되어 사용자 그래픽 인터페이 스를 제공하며, 메서드와 이벤트, 그리고 속성 등 많은 기능을 지 원한다.

– 따라서 컨테이너가 컨트롤의 이러한 기능을 사용하게 하기 위해 서는 서로 대화를 할 수 있어야 한다.

• 컨트롤은 자신이 위치할 장소를 컨테이너와 협의해야 한다.

• 컨트롤이 제공하는 속성을 컨테이너가 어떻게 저장할 것이며 • 컨트롤이 비활성화 상태에 있을 때 컨테이너는 마우스 이벤트를 어떻 게 컨트롤에 전달할 것인가 … – 컨트롤이 컨테이너와 커뮤니케이션하기 위해서는 컨테이너가 요 구하는 인터페이스를 구현해야만 한다.

– 컨트롤이 구현해야 하는 인터페이스에 대한 결정은 전적으로 컨 테이너가 어떤 인터페이스를 요구하느냐에 달려 있다.

4. 포함 개체로서의 ActiveX컨트롤 인터페이스

– 컨트롤에는 컨테이너가 OLE 다큐먼트 개체를 포함 또는 연결하는 것과 관련된 기술이 포함된다.

– 컨테이너는 IOleClientSite인터페이스를 구현한 ‘클라이언트 사이 트’를 제공함으로써 컨트롤이 컨테이너와 커뮤니케이션을 할 수 있게 한다.

• 클라이언트 사이트는 컨트롤이 컨테이너에 놓여지는 위치와 같은 컨 텍스트 정보를 비롯하여 다른 컨테이너에 관련된 정보를 컨트롤에게 제공하며, 이를 위해 컨트롤은 IOleObject인터페이스를 구현 또는 제 공해야 한다.

– 컨트롤이 컨테이너 안에서 활성화 (In-Place Activation)가 될 때 컨테이너와 커뮤니케이션하기 위해 필요한 IOleInPlaceObject, IOleInPlaceActiveObject, IDataObject, IViewObject2, IRunnableObject컨테이너에서는 IOleInPlaceUIWindow, IOleInPlaceFrame, IOleInPlaceSize, IAdviseSink인터페이스를 제공해야 한다.

– 컨트롤은 인사이드-아웃 활성화(Inside-Out Activation)을 지원 해야 한다.

4. 포함 개체로서의 ActiveX컨트롤 인터페이스

– 일반적으로 OLE다큐먼트 포함 개체는 더블 클릭될 때 컨테이너 안에서 활성화 상태가 된다.

– OLE다큐먼트 포함 개체가 내부적으로 다른 개체를 포함하고 있을 때, 내부 포함 개체는 외부 포함 개체가 활성화될 때까지 활성화 되지 않는다. 이와 같은 OLE다큐먼트 포함 개체의 활성화 방법을 아웃사이드-인 활성화라고 한다.

– 컨트롤은 컨트롤을 포함하는 컨테이너 어플리케이션의 일부분이 되기 때문에, 사용자가 컨트롤의 사용자 그래픽 인터페이스를 한 번만 클릭하여도 해당 컨트롤은 활성화 상태가 되어 반응할 수 있 어야 한다. 이것을 인사이드 – 아웃 활성화라고 한다.

5. 자동화 개체로서의 ActiveX컨트롤 인터페이 스

– 컨테이너는 복합 문서파일을 관리하여, 컨테이너의 복합 문서 파 일에 각각의 컨트롤에 대하여 하나의 스토리지 또는 스트림을 할 당하고, 해당 컨트롤의 속성을 저장하기 위하여 이 스토리지나 스 트림을 사용할 수 있다.

• IPersistStreamInit인터페이스 • 컨테이너는 이 인터페이스의 메서드를 사용하여 컨트롤에게 스트림 포인터를 넘겨주고 컨트롤은 이 스트림 포인터를 통하여 속성값을 저 장하거나 로드할 수 있다.

• IPersistStreamInit인터페이스의 InitNew메서드는 컨트롤이 제공하는 속성의 초기값을 지정하는데 사용된다.

– 컨테이너는 IPropertyBag인터페이스를 구현함으로써 속성 백을 제공한다.

– 이 속성 백은 해당 컨트롤의 속성 값이 저장된다. 컨트롤은 IPersistPropertyBag인터페이스 구현을 통하여 컨테이너의 속성 백에게 속성값을 받아들이거나 리턴하도록 요청하게 된다.

6. ActiveX컨트롤 고유의 인터페이스 • 키보드 입력 정보 전달

– 컨트롤은 엑셀러레이터 테이블에 정의된 키 또는 액세스 키에 대 한 정보를 컨테이너에게 자신이 어떤 키에 관심을 갖고 있는 지에 대한 정보를 넘겨줄 수 있다.

– 컨테이너는 자신에게 포함된 모든 컨트롤에 대하여 IOleControl::GetCrolInfo메서드를 호출하여 이정보를 구성할 수 있다.

– 컨트롤은 IOleControlSite::OnControlInfoChanged메서드를 호추 하여 이 정보를 동적으로 변경할 수 있으며, 이때 컨테이너는 해 당 컨트롤에 대하여 IOleControl::GetControlInfo메서드를 호출해 야 한다.

– 이제 컨트롤이 관심을 갖고 있는 키가 눌려질 때 컨테이너는 IOleControl::OnMnemonic메서드를 호출함으로써 컨트롤이 원하 는 행위를 할 수 있도록 할 수 있다.

• 환경 및 확장 속성 전달

– 컨테이너는 IOleControl::OnAmbientPropertyChage메서드를 호 출하여 자신의 환경속성이 변경되었다는 정보를 컨트롤에게 넘겨

6. ActiveX컨트롤 고유의 인터페이스

– 줄 수 있으며, 컨트롤은 IOleControlSite::GetExtendedControl메 서드를 호출하여 컨테이너에 포함된 확장 컨트롤이 IDispathc인 터페이스포인터를 구하여 컨테이너의 확장 속성의 현재 값을 결 정할 수 있다.

• 이벤트 발생 제어

– 컨테이너는 IOleControl::FreezeEvents 메서드를 호출하여 컨트 롤의 이벤트를 발생을 제어할 수 있다.

– 컨테이너가 TRUE를 매개변수로 FreezeEvents 메서드를 호출할 때마다 컨트롤은 이벤트 발생 억제 카운터를 증가시키고, FALSE 를 매개변수로 FreezeEvent메서드를 호출할 때 감소시킨다.

– 컨트롤은 이 카운터가 0이 될때까지 컨테이너가 이벤트를 받아들 일 수 없다고 인식하고 이벤트를 발생시키지 않아야 한다.

7. OLE 컨트롤 96사양 • 활성화 지연

– 컨테이너가 많은 컨트롤을 포함하고 있을 때 컨트롤의 활성화를 지연시킴으로써 더욱 빠르게 컨테이너의 인스턴스를 생성하고 사 용자와 대화할 수 있게 한다.

– 새로운 IPointerInactive인터페이스는 컨트롤이 비활성화 상태로 있지만 마우스 이벤트나 끌어놓기 등의 최소 기능에는 반응할 수 있게 한다.

• 윈도우 없는 컨트롤

– 컨트롤이 자신의 윈도우를 갖는 대신에 컨테이너의 윈도우를 사 용할 수 있게 함으로써, 컨트롤이 적은 리소스를 사용하게 하며 빠르게 활성화 또는 비활성화할 수 있는 수행 능력의 이점을 제공 한다.

– IOleInPlaceObject와 IOleInPlaceSite인터페이스에서 각각 파생 된느 IOleInPlaceObjectWindowless와 IOleInPlaceSiteWindwoless라고 하는 새로운 인터페이스가 추가 되었다.

7. OLE 컨트롤 96사양

– 컨트롤은 자신을 rfudi 할 필요가 있을 때 컨테이너에 구현된 IOleInPlaceSiteWindowsless::GetDC메서드를 호출하여 컨테이 너에게 디바이스 컨텍스틀ㄹ 요청하고, 그리기를 마친 후에는 IOleInPlaceSiteWindowsless::ReleaseDC메서드를 호출하여 컨 테이너에 디바이스 컨텍스트를 되돌려 준다.

– 윈도우가 없는 컨트롤은 직접 윈도우 메시지를 받을 수 없기 때문 에 컨테이너는 컨트롤에 구현된 IOleInPlaceObjectWindowless::OnWindowMessage메서드를 호 출하여 컨트롤에게 윈도우 메시지를 전달하게 된다.

• 그리기 최적화

– 윈도우 없는 컨트롤의 시각적인 효과를 극대화하기 위해 기존의 IViewObject인터페이스에서 파생되는 IViewObjectEx인터페이스 가 추가되었다.

– 컨트롤이 활성화 또는 비활성화될 때마다 컨트롤은 자기 자신을 다시 그리게 되므로, 이 과정에서 컨트롤에는 깜박거리는 현상이 발생하게 된다. IViewObjectEx인터페이스는 깜박거림이 없는 그 리기 기능과 함께, 비사각형 영역에 대한 히트 테스트정보를 처리 하는 기능을 제공한다.

8. ATL 컨트롤 클래스

– ATL Object Wizard는 자동화 개체와 마찬가지로, 해당 컨트롤의 메서드와 속성을 노출하는 IDispatchImpl 클래스와 CComObjectRootEx템플릿 클래스와 CComCoClass템플릿 클래 스에서 파생되는 컨트롤 개체 클래스를 생성한다.

– 이외에도 모든 컨트롤 개체 클래스는 해당 컨트롤이 지원하는 인 터페이스 클래스와 함께 CComControl템플릿 클래스에서 파생된 다.

CComObjectRoot CComCoClass IDispatchImpl CComControl ISupportErrorInfo CAddBack CComObject

8. ATL 컨트롤 클래스

– CComControl 템플릿 클래스는 CComControlBase클래스와 CWindowImpl템플릿 클래스에서 파생된다.

– CComControlBase클래스는 ATL컨트롤에 유용한 FireViewChange, SetDirty등의 멤버 함수와 데이터 멤버 및 내장 속성을 저아하는 데이터 멤버, 그리고 컨테이너의 환경 속성에 접 근할 수 있는 GetAmbientProperty 멤버 함수를 제공한다.

– CWindowImpl템플릿 클래스는 새로운 윈도우를 생성하거나 기존 의 윈도우를 수퍼클래싱할 수 있는 구현 코드를 제공한다.

8. ATL 인터페이스 클래스

– 컨트롤이 구현 또는 제공해야 하는 인터페이스는 컨테이너가 어 떤 인터페이스를 요구하느냐에 달려 있다.

– ATL은 이들 인터페이스에 대하여 디폴트 구현 코드를 제공하는 클래스를 제공함으로써, 굳이 이들 인터페이스를 구현하지 않아 도 된다.

– Full Control 유형은 모든 컨테이너에서 사용될 수 있는 완전한 인 터페이스가 구현된 컨트롤을 제공한다.

9. ATL 윈도우 클래스

– ATL은 BEGIN_MSG_MAP매크로와 END_MSG_MAP매크로로 정 의되는 메시지 맵 매커니즘을 통하여 윈도우 메시지를 처리한다.

– CWindowImpl과 CDialogImpl클래스의 윈도우 프로시저는 윈도 우 메시지를 메시지 맵에 전달하여 윈도우 메시지와 대응되는 메 시지 핸들러 함수를 호출하게 된다.

– 클래스에 메시지 맵을 선언하기 위해서는 CMessageMap클래스 에서 파생해야 하며, CWindowImpl과 CDialogImpl클래스는 기본 적으로 CMessageMap클래스에서 파생된다.

– 메시지 핸들러는 MESSAGE_HANDLER매크로를 사용하여 정의한 다.

– LRESULT FunctionName(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL & bHandled); – bHandled가 FALSE로 지정되면 함수가 처리되지 않았다는 것을 나타내며, 이때 ATL은 메시지 맵에서 다른 메시지 핸들러 함수를 찾는 과정을 진행하게 된다.

– CHAIN_MSG_MAP매크로를 사용하여 다른 클래스에 정의된 메시 지 맵에서 메시지를 처리할 수 있도록 메시지 맵을 연결시킨다.

9. ATL 윈도우 클래스

– ATL에서 윈도우를 구현하려면 해당 클래스는 CWindowImpl클래 스에서 파생되어야 한다.

– CWindowImpl파생 클래스에는 DECLARE_WND_CLASS매크로를 지정하여 윈도우 클래스 정보를 선언한다.

– CWindowImpl::Create함수가 호출될때 이 윈도우 클래스가 등록 되며 새로운 윈도우가 생성된다.

10. ATL ActiveX 컨트롤 구현

– Opaque : 컨트롤을 불투명하게 하여 컨트롤 뒤에 있는 컨테이너 부분이 나타나지 않게 하려면 Check한다.

– Solid Background : 컨트롤의 배경 색상을 단색으로 지정하려면 체크한다.

– Invisible at runtime : 실행 시에 컨트롤이 화면에 나타나지 않게 한다.

– Acts link button : 컨트롤이 버튼과 같은 행위를 할 수 있게 한다.

• 컨테이너에서 이 옵션이 선택된 컨트롤을 디폴트 버튼으로 지정하면 컨트롤은 좀 더 두꺼운 프레임으로 자신을 표시하게 된다.

10. ATL ActiveX 컨트롤 구현

– 컨트롤이 표준 컨트롤을 수퍼클래싱한다면 [Add control based on]콤보 상자에서 원하는 표준 컨트롤을 선택한다.

– [NormalizeDC] 컨트롤이 자신을 그릴 때 표준화된 디바이스 컨 텍스트를 생성하게 된다. – [Insertable] 개체 삽입 대화상자에 컨트롤이 나타나게 된다.

– [Windowed Only] 컨테이너가 윈도우 없는 컨트롤을 지원할 때도 컨트롤이 윈도우를 가질 수 있게 한다.

11. 속성 맵

– ATL컨트롤 클래스에 속성과 메서드, 그리고 이벤트를 구현하는 것은 자동화 개체를 구현할 때와 전혀 다른 것이 없다.

– 컨트롤의 속성이 지속성을 갖게 하기 위해 ATL은 속성 맵 매커니 즘을 사용한다.

– BEGIN_PROPERTY_MAP – PROP_ENTRY( “ ShapeType ” ,1,CLSID_NULL) – END_PROPERTY_MAP – 속성 맵 안에는 PROP_ENTRY매크로나 PROP_ENTRY_EX매크로 항목이 정의된다.

– IPersistStreamInit 인터페이스의 ATL구현 클래스인 IPersistStreamInitImpl클래스 또는 IPersistPropertyBag인터페이 스의 ATL 구현 클래스인 IPersistPropertyBagImpl클래스의 Load 및 Save메서드에서는 각각 컨테이너의 IStream와 IPropertyBag 인터페이스 포인터에서 속성값을 읽고 저장하는 코드를 구현하고 있다.

12. OnDraw코드 구현

– 컨테이너가 컨트롤을 그릴 필요가 있을 때마다 컨트롤의 IViewObject::Draw메서드를 호출한다.

– IViewObejct인터페이스의 ATL구현 클래스인 IViewObjectExImpl 클래스의 Draw메서드가 호출되고, 이 메서드 구현 코드는 ATL_DRAWINFO 구조체 정보를 구성한 후, 이 구조체 정보를 매 개변수로 CComControl::OnDrawAdvanced멤버 함수를 호출한 다.

– 이 멤버 함수에서는 표준화된 디바이스 컨텍스트를 준비하고, 다 시 ATL_DRAWINFO구조체 정보를 매개변수로 사용자 컨트롤 클 래스의 OnDraw멤버 함수를 호출한다.

12. OnDraw코드 구현

struct ATL_DRAWINFO { UINT cbSize; DWORD dwDrawAspect; LONG lindex; DVTARGETDEVICE* ptd; HDC hicTargetDev; HDC hdcDraw; LPCRECTL prcBounds; //Rectangle in which to draw LPCRECTL prcWBounds; //WindowOrg and Ext if metafile BOOL bOptimize; BOOL bZoomed; BOOL bRectInHimetric; SIZEL ZoomNum; //ZoomX = ZoomNum.cx/ZoomNum.cy

SIZEL ZoomDen; };

12. OnDraw코드 구현

– IViewObject 인터페이스의 ATL구현 클래스인 IViewObjectExImpl 클래스의 Draw메서드 구현 코드에서는 IViewObject::Draw메서드 매개변수에 전달되는 정보를 사용하여 ATL_DRAWINFO구조체 정 보를 구축한다.

– 컨트롤 클래스 내부에서 데이터가 변경되어 컨트롤을 다시 그려 야 할 필요가 있다면 CComControl::FireViewChange멤버 함수를 호출해야한다.

– CComControl::FireViewChange멤버 함수는 컨트롤이 활성화 상 태이고 윈도우를 갖는다면 InvalidateRect API함수를 호출하여 직 접 다시 그리게 된다.

– 윈도우가 없는 컨트롤이라면, 컨테이너의 IOleInPlaceSiteWindowless::InvalidateRect메서드를 호출하여 컨테이너에게 컨트롤을 다시 그려야 할 필요가 있다는 것을 알려 주게 도니다.

1. ActiveX 컨트롤과 인터넷

– ActiveX컨트롤이란 인터넷 환경에 효율적으로 사용될 수 있도록 확장된 OLE컨트롤의 상위 집합이다.

– 웹페이지에서 ActiveX컨트롤을 사용하기 위해서는 웹페이지에 ActiveX컨트롤을 포함시켜야 한다.

– 각 ActiveX컨트롤은 태그 안에 포함된다.

– 각 ActiveX컨트롤은 CLSID로 식별되며, 각 웹페이지에서 스크립 트 코드가 참조할 수 있는 ID값을 갖는다.

2. 컨트롤 속성 사용

– IE가 ActiveX컨트롤을 로드할 때 새로운 인스턴스를 생성하고 각 속성에 디폴트 값을 부여한다.

– 이 디폴트 값 대신에 새로운 초기값을 지정하려면 태 그의 속성을 사용할 수 있다.

속성으로 초기값을 지정하기 위해서는 ActiveX컨트롤 은 IPersistPropertyBag인터페이스를 지원해야 한다.

– IE는 ActiveX컨트롤에 IPersistPropertyBag인터페이스 포인터를 요청하고, 이 인터페이스를 통하여 속성을 읽어 한 번 에 하나씩 속성값을 컨트롤에 넘겨주게 된다.

Chapter

10.

MFC COM을 위한 기초 클래스

1. MFC COM

– MFC에서 지원되는 클래스를 이용하면, COM관련 소스를 직접 제 작할 필요가 없게 된다.

– 클래스 팩토리를 자동으로 생성하기 위해서는 OLECREATE메크 로를 이용한다.

– MFC의 클래스 팩토리의 구현 코드는 DYNCREATE매크로에 의해 추가된 멤버 데이터와 메소드에 의존하기 때문에 두 매크로를 동 시에 사용하여야 한다.

DECLARE_DYNCREATE(CMFCCom2Ctrl) DECLARE_OLECREATE_EX(CMFCCom2Ctrl) IMPLEMENT_DYNCREATE(CMFCCom2Ctrl, COleControl) IMPLEMENT_OLECREATE_EX(CMFCCom2Ctrl, "MFCCOM2.MFCCom2Ctrl.1", 0xec5d15d0, 0x767d, 0x414f, 0x9d, 0xc7, 0xb3, 0xf0, 0x9b, 0xca, 0xe2, 0xc3)

1. MFC COM

#define DECLARE_OLECREATE_EX(class_name) \ BEGIN_OLEFACTORY(class_name) \ END_OLEFACTORY(class_name) #define BEGIN_OLEFACTORY(class_name) \ protected: \ class class_name##Factory : public COleObjectFactoryEx \ { \ public: \ class_name##Factory(REFCLSID clsid, CRuntimeClass* pRuntimeClass, \ BOOL bMultiInstance, LPCTSTR lpszProgID) : \ COleObjectFactoryEx(clsid, pRuntimeClass, bMultiInstance, lpszProgID) {} \ virtual BOOL UpdateRegistry(BOOL); #define END_OLEFACTORY(class_name) \ }; \ friend class class_name##Factory; \ static AFX_DATA class_name##Factory factory; \ public: \ static AFX_DATA const GUID guid; \ virtual HRESULT GetClassID(LPCLSID pclsid);

1. MFC COM

#define IMPLEMENT_OLECREATE_EX(class_name, external_name, \ l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \ const TCHAR _szProgID_##class_name[] = _T(external_name); \ AFX_DATADEF class_name::class_name##Factory class_name::factory( \ class_name::guid, RUNTIME_CLASS(class_name), FALSE, \ _szProgID_##class_name); \ const AFX_DATADEF GUID class_name::guid = \ { l, w1, w2, { b1, b2, b3, b4, b5, b6, b7, b8 } }; \ HRESULT class_name::GetClassID(LPCLSID pclsid) \ { *pclsid = guid; return NOERROR; }

2. 기본클래스

class CMFCTest6App : public COleControlModule { public: BOOL InitInstance(); int ExitInstance(); }; class CMFCTest6Ctrl : public COleControl { DECLARE_DYNCREATE(CMFCTest6Ctrl);

• COleControlModule

– COleControlModule은 CWinApp로부터 파생되었다.

class COleControlModule : public CWinApp { DECLARE_DYNAMIC(COleControlModule) public: virtual BOOL InitInstance(); virtual int ExitInstance(); };

2. 기본클래스

BOOL COleControlModule::InitInstance() { #ifdef _AFXDLL // wire up resources from OLE DLL AfxOleInitModule(); #endif COleObjectFactory::RegisterAll(); return TRUE; } – AfxOleInitModule() : • MFC DLL에서 COM이 지원되도록 한다.

• 클래스 팩토리를 이용하여, 각각의 객체를 레지스트리에 등록하는 역 할을 한다.

2. 기본클래스

class COleControl : public CWnd

• OnDraw()함수

– void OnDraw(CDC* pdc, const CRect& rcBounds, const CRect& rcInvalid) – pdc : 컨트롤의 DC – rcBounds : 그리기가 수행될 사각 영역 – WM_PAINT메시지를 받았을 때 발생 – IViewObejctEx::Draw()를 호출하여 컨테이너의 창에 컨트롤 자신 을 그리도록 요청할 경우 – 장치를 사용할 때는 초기 속성을 고려하지 않고 항상 새롭게 지정 하여 사용하는 것이 효과적이다.

2. 기본클래스 • 컨트롤의 속성 설정

– 컨트롤의 속성은 컨테이너에 의해 구현되는 자동화 속성이기 때 문에 컨트롤에서 IDispatch::Invoke를 호출하여 그 값을 얻을 수 있다.

– COleContol은 Invoke()함수를 직접 호출할 필요 없이, COleControl클래스에서 제공되는 래퍼를 이용하면 쉽게 환경 속 성을 검색, 설정할 수 있다.

• AmbientBackColor() – TranslateColor()는 OLE_COLOR를 COLORREF값으로 변환해 준 다.

OLE_COLOR COleControl::AmbientBackColor() { OLE_COLOR clrBackColor; if (!GetAmbientProperty(DISPID_AMBIENT_BACKCOLOR, VT_I4, &clrBackColor)) clrBackColor = GetSysColor(COLOR_WINDOW); return clrBackColor; }

2. 기본클래스

– COleControl멤버 함수가 지원되지 않는 속성 이름일 경우, GetAmbientProperty()함수를 이용하여 속성 값을 얻어올 수 있다.

– 컨트롤의 속성이 변화되었을 때 • OnAmbientPropertyChange함수가 호출된다.

• InvalidateControl을 호출하여 컨트롤을 다시 그릴 수 있다.

2. 기본클래스

– COleControl멤버 함수가 지원되지 않는 속성 이름일 경우, GetAmbientProperty()함수를 이용하여 속성 값을 얻어올 수 있다.

속성 이름 COleControl검색 함수 Appearance short AmbientAppearance(); BackColor DisplayName Font ForeColor LocaleID MessageReflect ScaleUnits TextAlign UserMode UIDead ShowGrabHandle OLE_COLOR AmbientBackColor(); CString AmbientDisplayName(); LPFONTDISP AmbientFont(); OLE_COLOR AmbientForeColor(); LCID AmbientLocaleID(); dispid = DISPID_AMBIENT_MESSAGEREFLECT BOOL GetAmbientProperty(dispid, vtProp, pvProp); CString AmbientScaleUnits(); short AmbientTextAlign(); BOOL AmbientUserMode(); BOOL AmbientUIDead(); BOOL AmbientShowGrabHandles();

2. 기본클래스

속성 이름 ShowHatching DisplayAsDefaultBut ton COleControl검색 함수 BOOL AmbientShowHatching() dispid = DISPID_AMBIENT_DISPLAYASDEFAULT BOOL GetAmbientProperty(dispid, vtProp, pvProp); SupportsMnemonics dispid = DISPID_AMBIENT_SUPPORTSMNEMONICS BOOL GetAmbientProperty(dispid, vtProp, pvProp); AutoClip dispid = DISPID_AMBIENT_AUTOCLIP BOOL GetAmbientProperty(dispid, vtProp, pvProp); Palette TransferPriority dispid = DISPID_AMBIENT_PALETTE BOOL GetAmbientProperty(dispid, vtProp, pvProp); dispid = DISPID_AMBIENT_TRANSFERPRIORITY BOOL GetAmbientProperty(dispid, vtProp, pvProp);

2. 기본클래스

– MFC를 이용하여 ActiveX컨트롤을 만들 때 자주 사용되는 비가상 함수들 함수 설명 AmbientXXX FireXXX GetXXX InitializeIIDs InvalidateControl SetXXX SetModifiedFlag TrhowError 컨테이너로부터 환경 속성을 얻는다. (AmbientBackColor) 스톡 이벤트를 발생시킨다.(FireClick) 스톡 속성의 값을 얻는다. (GetBackColor) IDisplatch인터페이스와 컨트롤의 이벤트 인터페이스의 ID를 MFC에서 인식할 수 있도록 한다.

컨트롤을 다시 그린다.

스톡 속성 값을 설정한다. (SetBackColor) 컨트롤의 변화가 생겼음을 설정한다. 즉, 컨트롤의 내용 이 변화는 되었지만, 저장되지 않은 경우 이 함수를 호 출한다.

오류가 발생했다는 신호를 보내다.

TranslateColor OLE_COLOR값을 COLORREF값으로 바꾼다.

2. 기본클래스

– MFC를 이용하여 ActiveX컨트롤을 만들 때 자주 사용되는 비가상 함수들 함수 설명 AmbientXXX FireXXX GetXXX InitializeIIDs InvalidateControl SetXXX SetModifiedFlag TrhowError 컨테이너로부터 환경 속성을 얻는다. (AmbientBackColor) 스톡 이벤트를 발생시킨다.(FireClick) 스톡 속성의 값을 얻는다. (GetBackColor) IDisplatch인터페이스와 컨트롤의 이벤트 인터페이스의 ID를 MFC에서 인식할 수 있도록 한다.

컨트롤을 다시 그린다.

스톡 속성 값을 설정한다. (SetBackColor) 컨트롤의 변화가 생겼음을 설정한다. 즉, 컨트롤의 내용 이 변화는 되었지만, 저장되지 않은 경우 이 함수를 호 출한다.

오류가 발생했다는 신호를 보내다.

TranslateColor OLE_COLOR값을 COLORREF값으로 바꾼다.