COM Component Object Model

Download Report

Transcript COM Component Object Model

COM
COM
Component Object Model
제작 : 장문성
([email protected])
비트 교육 센터
COM
0. 배경
COM
객체 지향 (Object Orientation)
• 현재의 소프트웨어 기술
– Component-based Distributed Computing
• 객체
– Component or Application
– Code+Data
• 상용화 된 분산 객체 지원 미들 웨어
– Microsoft - COM / OMG - CORBA
/ Sun - EJB
COM
COM 의 역사 - 1
• OLE1
– compound document 지원
– DDE (Dynamic Data Exchange)
• OLE2
– COM으로 완전히 재 작성됨
– OLE Automation 과 OLE Control
COM
COM 의 역사 - 2
• Active-X
– OLE Control → Active-X Control
– OLE Automation → Automation
– Active-X Document
– Active-X Scripting
COM
왜 COM 인가
• 요구 조건
– Component Interoperability
– Robust evolution (versioning)
– Language independence
– Location transparency
– Scalability
• COM
COM
1. 인터페이스(Interface)
COM
객체(Object)와 인터페이스(Interface)
• 실생활에서의 인터페이스
– 자동차 핸들, 조이스틱, GUI, 자판기 버튼…
• 객체와 인터페이스
– 어떤 객체가 다른 객체와 상호 작용 하기 위
해서 인터페이스가 필요
– 객체는 자신의 인터페이스를 외부에 노출시
키기도 하고, 다른 객체의 인터페이스에 접
근하기도 함
– 흔히 하나의 객체가 두 개 이상의 인터페이
스를 제공
COM
a COM Object
?
COM
인터페이스(Interface)의 예 1
• 자동차 운전 인터페이스
struct ICarControl {
virtual void RotateHandle( int nAngle ) = 0;
virtual void SetGear( int nLevel ) = 0;
virtual void Accelerate( int nRpm ) = 0;
virtual void Break() = 0;
virtual int GetSpeed() = 0;
};
COM
인터페이스(Interface)의 예 2
• 라디오 인터페이스
struct IRadio {
virtual void SetVolumn( int nLoudness ) = 0;
virtual void Tune( double dMHz ) = 0;
};
COM
인터페이스의 상속
struct IStereoRadio : public IRadio {
virtual void SetMode( bool bStereo ) = 0;
};
struct IStereoRadio2 {
virtual void SetVolumn( int nLoudness ) = 0;
virtual void Tune( double dMHz ) = 0;
virtual void SetMode( bool bStereo ) = 0;
};
struct IAudio : public IRadio, public ICD {
};
COM
인터페이스의 사용 (상상도)
CTico* pMyCar = new CTico( “경기”, 2, “로”, 2047 );
ICarControl* pControl = pMyCar->GetControlInterface();
IRadio* pRadio = pMyCar->GetRadioInterface();
pRadio->Tune( 91.9 );
pRadio->SetVolumn( 10 );
pControl->SetGear( 1 );
pControl->Accelerate( 3000 );
pControl->SetGear( 2 );
pControl->RotateHandle( 10 );
:
CTico
COM
GetControlInterface ?
• 앞 페이지 예제의 문제점
– 미리 CTico의 클래스 정의를 알아야 함
– 인터페이스를 얻는 범용적인 방법이 없음
– 객체의 생성과 소멸에 대한 인터페이스 지
원이 없음 (new/delete: 언어 특정적)
– 에러에 대한 정보가 부족함 (NULL을 리턴
하는 정도)
COM
IUnknown
• IUnknown
– 앞에서 제시된 문제를 해결
– 모든 COM 인터페이스가 계승하는 최상위
인터페이스
– 모든 COM 객체가 반드시 지원하고 구현해
야함
– 3개의 함수
COM
IUnknown::QueryInterface
• 역할
– 그 객체에게 특정한 인터페이스를 달라고
‘요청’함
• 생김새
– <에러코드> QueryInterface(
<인터페이스 이름>,
<인터페이스 포인터의 주소> );
COM
IUnknown::Release
IUnknown::AddRef
• 객체의 유지와 소멸을 관리
– Reference Counting
– AddRef : Counter 증가
– Release : Counter 감소
• QueryInterface
– 내부적으로 AddRef()를 호출함
COM
Reference Counting
Release
QueryInterface
1
1
2
0
Release
QueryInterface
???
COM
Reference Counting (cont’d)
ULONG CTico::AddRef()
{
return ++m_nRef;
}
ULONG CTico::Release()
{
m_nRef--;
if ( m_nRef > 0 ) return m_nRef;
delete this; // suicide
return 0;
// do not ‘return m_nRef;’
}
COM
Calling Convention
• __stdcall, __cdecl
– 모두 함수 전달 인자를 스택에 오른쪽에서
왼쪽으로 push
– 스택을 책임지는 자
• __stdcall : callee
• __cdecl : caller
COM
Calling Convention (cont’d)
• 예
int __stdcall do_it( int a, int b )
{ return 0; }
int __cdecl do_that( int a, int b )
{ return do_it( a, b ); }
:
do_that( 1, 2 );
COM
HRESULT type
• HRESULT 구조
– typedef LONG HRESULT; // -_-;
31 30 29 28 27 26 ~ 16
S R C N r Facility
15 ~ 0
Code
– S : severity bit
– Facility : FACILITY_NULL, FACILITY_ITF,
FACILITY_DISPATCH, FACILITY_RPC,
FACILITY_STORAGE
– Code : Error Code
COM
HRESULT type (cont’d)
• 몇 몇 유명한 HRESULT 값들
•
•
•
•
•
•
•
S_OK(==0), NOERROR(==S_OK) : 성공(true)
S_FALSE(==1) : 성공 (false)
E_UNEXPECTED : 예상치 못한 실패
E_NOTIMPL : 구현되지 않았음
E_NOINTERFACE : 그런 인터페이스가 없음.
E_OUTOFMEMORY : 메모리 부족
E_FAIL : 실패
COM
HRESULT type (cont’d)
• 특정한 부분을 얻는 법
– HRESULT_FACILITY, HRESULT_CODE
• 성공, 실패 여부를 판단할 때
if ( hRes == S_OK ) {}
// 나쁜 방법
– 여러 가지의 성공 값이 존재하므로 범용적
인 방법을 사용해야 함
if ( SUCCEEDED(hRes) ) {}
if ( FAILED(hRes) ) {}
– 가장 확실한 방법은 함수 설명을 읽는 것임
COM
GUID/UUID
• 인터페이스와 클래스에 이름 붙이기
– 사용자가 “맘대로” 정의한 스트링
• “CTico”, “내 인터페이스”
• QueryInterface( “내 인터페이스”, &pMyInterf );
– “중앙 서버”가 정해 준 일련번호
• #define MYINTERFSEQ 254356
• QueryInterface( MYINTERFSEQ, &pMyInterf );
• 요구사항
– 유일하면서도 모든 이가 자유롭게 생성할
수 있어야 함
COM
GUID/UUID (cont’d)
• GUID/UUID
– Globally Unique IDentifier
– Universal Unique IDentifier (==GUID)
• 128 비트 구조체
• UuidCreate() 로 생성됨
• 함수를 실행할 때 마다 고유한 UUID를
생성하는 것을 보장
COM
GUID/UUID (cont’d)
• 실제 모습
typedef struct _GUID {
DWORD Data1;
WORD
Data2;
WORD
Data3;
BYTE
Data4[8];
} GUID;
COM
GUID/UUID (cont’d)
• 표기 방법
– 레지스트리 형식
• {2B836510-FCBE-11d2-98A8-00AA006dEEA4}
• {00000000-0000-0000-C000-000000000046}
– 구조체 형식
• extern “C” const GUID IID_MyInterface =
{ 0x2b836510, 0xfcbe, 0x11d2,
{ 0x98, 0xa8, 0x00, 0xaa, 0x00, 0x6d, 0xee,
0xa4 } };
• GUIDGEN.EXE / UUIDGEN.EXE
COM
GUID/UUID (cont’d)
• 동의어들
typedef GUID UUID;
#define uuid_t UUID
typedef GUID IID;
// interface id
typedef GUID CLSID;
// class id
typedef const GUID& REFGUID;
typedef const CLSID& REFCLSID;
typedef const IID& REFIID;
• 비교 함수
– IsEqualGUID, IsEqualIID, IsEqualCLSID
COM
IUnknown 인터페이스
struct IUnknown {
HRESULT __stdcall QueryInterface(
REFIID iid, void** ppvObject ) = 0;
ULONG __stdcall AddRef(void) = 0;
ULONG __stdcall Release(void) = 0;
};
COM
ICarControl COM버전
#define STDMETHOD(FUNC) virtual HRESULT __stdcall FUNC
#define STDMETHOD_(RET,FUNC) virtual RET __stdcall FUNC
struct ICarControl : public IUnknown
{
STDMETHOD(RotateHandle)( long nAngle ) = 0;
STDMETHOD(SetGear)( long nLevel ) = 0;
STDMETHOD(Accelerate)( long nRpm ) = 0;
STDMETHOD(Break)() = 0;
STDMETHOD_(long,GetSpeed)() = 0;
};
※ STDMETHOD 는 objbase.h 에 진짜로 정의되어 있음
COM
Driving the Tico
IUnknown* pUnk = ???;
HRESULT hRes;
IRadio* pRadio = 0;
ICarControl* pControl = 0;
// 어떤 방법을 써서
// pUnk가 Tico객체를 참조
// 하게 되었다고 가정함
// (그 방법은 나중에 나옴)
hRes = pUnk->QueryInterface(
IID_IRadio, (void**)&pRadio );
if ( !SUCCEEDED( hRes ) ) return hRes;
hRes = pUnk->QueryInterface(
IID_ICarControl, (void**)&pControl );
if ( !SUCCEEDED( hRes ) ) return hRes;
COM
Driving the Tico (cont’d)
pRadio->Tune( 91.9 );
pRadio->SetVolumn( 10 );
pControl->SetGear( 1 );
pControl->Accelerate( 3000 );
pControl->SetGear( 2 );
pControl->RotateHandle( 10 );
:
pControl->Release();
pRadio->Release();
:
pUnk->Release();
// 이것이 Tico를 참조하던 마지막
// 참조였다면 Tico는 delete됨
COM
규칙
• QueryInterface
– IID_IUnknown으로 얻는 IUnknown* 값은 항상 같
음
– 한 번 얻었던 인터페이스는 다시 얻을 수 있음 (현
재 가지고 있는 인터페이스 포함)
• AddRef/Release (참조 계수 관리)
– 참조가 하나 증가할 때 AddRef
– 참조가 하나 감소할 때 Release
COM
규칙 (cont’d)
• AddRef/Release (cont’d)
– 새로운 인터페이스 포인터를 출력하는 함수는 내부
적으로 AddRef를 해 주어야 함
(예 : QueryInterface와 같은 함수)
– 다른 인터페이스 포인터를 입력으로 받는 경우에는
AddRef/Release를 해 줄 필요가 없음
– 인터페이스 포인터를 다른 변수에 복사하는 경우
원칙적으로 AddRef를 해야 함
– 인터페이스 포인터를 보관하던 변수(AddRef 를 야
기했던 변수)가 소멸되는 경우 Release 해야 함
– 잠깐 사용될 지역변수에 인터페이스 포인터를 보관
하는 경우 AddRef/Release를 조심스럽게 생략함
COM
2. COM 객체의 생성과 사용
COM
COM 객체가 있는 곳
Marshalling
Application
EXE
Remote
Server
Marshalling
DLL
EXE
In-process
Server
Local
Server
Process A
Host A
Process B
Host B
COM
프로세스(Process)
• Process
– Program이 실행되어 활성화 된 상태
– 쓰레드들과 주소공간, 환경변수등으로 구성
• 주소 공간
– Win32에서는 각 프로세스가 2Gbyte의 가상 주소
공간을 가짐 (커널이 실제 주소로 매핑)
– 프로세스 간에는 원칙적으로 상대방의 메모리 영역
을 접근할 수 없음
– IPC(Inter-Process Communication) 수단을 커널이
제공해야 함
• Pipe, Socket, Message Queue, Shared Memory 등
COM
In-Process Server
• DLL (Dynamic Link Library)
– 실행 시 응용 프로그램의 주소 공간에 동적
으로 링크됨
– 응용 프로그램의 일부로서 실행되므로 IPC
가 불필요
• In-Process Server
– COM 객체를 DLL 이 제공
– 효율이 좋고 작성이 용이함
– 원칙적으로 객체를 공유할 수 없음
COM
Local Server
• Local Server
– COM 객체를 EXE(실행파일)이 제공
– RPC (Remote Procedure Call) 을 통해서
통신해야 함
– 속도가 현저히 떨어짐
– 독립적으로 실행할 수 있다는 장점
COM
Local Server (cont’d)
• RPC (Remote Procedure Call)
– Marshalling / Unmarshalling
• RPC를 하기 위해 전달 인자와 리턴 값을 한 덩
이의 바이트 스트림으로 묶거나 푸는 것
– IDL (Interface Definition Language)
• 인터페이스를 정의하는 또 다른 언어
• IDL 컴파일러가 컴파일 하여 C++ 인터페이스 헤
더 파일과 마셜링 코드 등을 만들어 줌
• MIDL.EXE
COM
Local Server (cont’d)
• IDL 파일 예제
import "oaidl.idl";
import "ocidl.idl";
[
object,
uuid(B128513B-FD1C-11D2-98A9-0000212035B8),
helpstring("ICarControl 인터페이스"),
pointer_default(unique)
]
COM
Local Server (cont’d)
interface ICarControl : IUnknown
{
[helpstring("RotateHandle 메쏘드")]
HRESULT RotateHandle([in] long nAngle);
[helpstring("SetGear 메쏘드")]
HRESULT SetGear([in] long nLevel);
[helpstring("Accelerate 메쏘드")]
HRESULT Accelerate([in] long nRpm);
};
COM
Remote Server
• Remote Server
– COM 객체를 제공하는 서버가 리모트 호스
트에 존재
– 그 외에는 Local Server와 같음
• RPC
– Network 을 통해서 이루어 짐
• DCOM (Distributed COM)
– Remote Server를 통한 분산 COM 을 뜻함
COM
객체를 생성하는 객체
• IClassFactory 인터페이스
– 이 인터페이스를 제공하는 객체는 새로운
객체를 생성할 수 있는 능력을 가짐
– 이름과 달리 Class가 아닌 Object를 생성
HRESULT CreateInstance(
IUnknown* pUnkOuter, REFIID riid,
void** ppvObject );
– 생각해 볼 문제
• 우리가 정말로 제공해야 할 것은 객체 그 자체인
가, 클래스 팩토리인가 ?
COM
COM 객체 생성
hRes = CoInitialize( NULL );
hRes = CoCreateInstance( clsid, NULL, CLSCTX_ALL,
IID_IUnknown,
(void**)pUnk );
pUnk->Release();
hRes = CoUninitialize();
• CLSCTX : CLSCTX_INPROC_SERVER,
CLSCTX_INPROC_HANDLER,
CLSCTX_LOCAL_SERVER,
CLSCTX_REMOTE_SERVER
COM
COM 객체 생성 (cont’d)
COM
COM 객체 생성 (cont’d)
COM
COM 객체 생성 (cont’d)
• 객체 생성 개요
– COM 은 CLSID에 해당하는 클래스 객체에
서 QueryInterface로 IClassFactory 인터페
이스를 얻고,IClassFactory::CreateInstance
함수로 객체를 생성
– 이후에 IClassFactory를 Release 함
– IClassFactory 자체를 얻고 싶으면
CoGetClassObject 함수 사용
COM
COM 객체 생성 (cont’d)
• In-Process Server
– COM 은 DLL의 exported 함수인
DllGetClassObject 함수를 호출하여 해당
객체의 IClassFactory 인터페이스를 얻음
• Local Server
– 해당 객체를 서비스하는 실행파일은 실행시
에 자신이 제공하는 객체 리스트와
IClassFactory 인터페이스를 COM 에 등록
COM
ProgID
COM
ProgID (cont’d)
• 어려운 CLSID
– 외우기도 다루기도 어렵기 때문에 친근한 스
트링 포맷의 이름을 제공
– ProgID 구조
• <컴포넌트명>.<개체명>.<버전>
• <컴포넌트명>.<개체명>
• 변환 함수
CLSIDFromProgID( L”MSCAL.Calendar”, &clsid );
COM
참고 : COM 에서 사용되는 스트링
• UNICODE
– 대부분의 언어에서 쓰이는 문자를 2바이트로
인코딩
– wchar_t, WCHAR, OLECHAR
– L”MSCAL.Calendar”
– MultiByteToWideChar, WideCharToMultiByte
– OLE2A, OLE2T … (string conversion macros)
– LPCTSTR
COM
- 실습 COM 서버/클라이언트 구현
COM
3. COM 인터페이스 구현
( in C++ )
COM
인터페이스 구조 설계
• 포함 클래스로 구현 예 (MFC)
class CTico {
struct XCarControl : public XUnknown {
STDMETHOD(RotateHandle)( long nAngle );
} m_xCarControl;
struct XRadio : public XUnknown {
STDMETHOD(Tune)( double dMHz );
} m_xRadio;
- 중략 };
※ 여기 구현된 것은 개략적인 흐름을 보일 뿐 실제 구현과는 거리가 있다
COM
인터페이스 구조 설계 (cont’d)
… QueryInterface( REFIID riid, void** ppvObject )
{
*ppvObject = 0;
if ( riid == IID_IUnknown
|| riid == IID_ICarControl )
*ppvObject = &m_xCarControl;
else if ( riid == IID_IRadio )
*ppvObject = &m_xRadio;
if ( *ppvObject ) { AddRef(); return S_OK; }
else return E_NOINTERFACE;
}
COM
인터페이스 구조 설계 (cont’d)
HRESULT CTico::XCarControl::RotateHandle( long nAngle )
{
METHOD_PROLOGUE( CTico, CarControl );
pThis->DoSomething();
return S_OK;
}
COM
인터페이스 구조 설계 (cont’d)
• 가상 함수로 구현 예 (ATL)
class CTico : public ICarControl, public IRadio
{ - 중략 - };
…QueryInterface( REFIID riid, void** ppvObject )
{ - 중략 else if ( riid == IID_ICarControl )
*ppvObject = (ICarControl*)this;
-중략}
HRESULT CTico::Tune( long nAngle ) { … }
COM
QueryInterface 구현
• 수공예 방법
if ( riid == IID_ICarControl ) …
else if ( …
• 인터페이스 테이블을 정의
• ATL
BEGIN_COM_MAP(CTico)
COM_INTERFACE_ENTRY(ICarControl)
END_COM_MAP()
• MFC
BEGIN_INTERFACE_MAP(CTico,CCmdTarget)
INTERFACE_PART( CTico, IID_ICarControl, CarControl )
END_INTERFACE_MAP()
COM
QueryInterface 구현 (cont’d)
• 주의할 점
– AddRef() 꼭 해줄것 !!
– 인터페이스를 찾을 수 없으면
E_NOINTERFACE 를 리턴
– 에러 시 *ppvObject 를 NULL 로 세트 권장
COM
AddRef/Release 구현
• 단일 쓰레드 모델
ULONG AddRef() { return ++m_nRef; }
ULONG Release() {
if ( !--m_nRef ) { delete this; return 0; }
else return m_nRef;
}
COM
AddRef/Release 구현 (cont’d)
• 다중 쓰레드 모델
volatile long m_nRef;
ULONG AddRef() {
InterlockedIncrement( &m_nRef ); }
ULONG Release() {
InterlockedDecrement( &m_nRef );
if ( !m_nRef ) { delete this; return 0; }
else return m_nRef; }
COM
AddRef/Release 구현 (cont’d)
• 경쟁 조건 해결을 위해 재 작성된
Release의 예 (편의상 MFC를 사용)
ULONG Release() {
static CCriticalSection s_cs;
s_cs.Lock();
long nRef = - - m_nRef;
s_cs.Unlock();
if ( !nRef ) delete this;
return nRef;
}
COM
CreateInstance 구현
HRESULT CreateInstance(
IUnknown* pUnkOuter, REFIID, void** );
• 그 객체를 new 한 후 QueryInterface로
해당 인터페이스를 리턴한다
• pUnkOuter
– Aggregation 을 지원하지 않는 경우
pUnkOuter 는 NULL임
– NULL이 아니면
CLASS_E_NOAGGREGATION 을 리턴
COM
LockServer 의 구현
HRESULT LockServer( BOOL fLock )
• IClassFactory의 두 함수중 하나로 서버
자체의 Reference Counter 를 변경
• 이 함수의 필요성
– 서버의 종료 시기를 판단하는 근거임
– IClassFactory 인터페이스를 유지하기 위해
• 서버 전역적인 참조 계수를 유지하여
AddRef/Release 와 마찬가지로 구현함
COM
LockServer 의 구현 (cont’d)
• 서버의 소멸 시점
– 서비스 하고 있는 객체의 수가 0이고,
LockServer 계수가 0 일 때
– DLL 은 Unload, EXE 는 실행 종료
– 객체의 개수를 유지하도록 만들어 져야 함
(예: 생성자/소멸자, 또는
CreateInstance/Release 에 포함)
– Factory의 참조 계수로 LockServer계수를
대신하면 안됨
COM
4. In-Proc-Server 구현
COM
4 개의 exported 함수
• export / import
– DLL은 자신이 제공하는 함수들을 export 해
야 다른 모듈들이 그 함수를 사용할 수 있음
– 다른 모듈들은 그 함수들을 import함
– __declspec(export), __declspec(import)
– 모듈 정의(DEF) 파일
COM
4개의 exported 함수 (cont’d)
• COM In-Process Server DLL은 4개의
exported 함수를 제공해야 함
• DllRegisterServer
• DllUnregisterServer
• DllCanUnloadNow
• DllGetClassObject
COM
4개의 exported 함수 (cont’d)
• HRESULT DllRegisterServer(void) /
HRESULT DllUnregisterServer(void)
– 레지스트리에 자신이 제공하는 CLSID 와
ProgID를 등록/해제함
– regsvr32.exe 가 실행하는 함수
– RegOpenKeyEx, RegCreateKeyEx,
RegSetValueEx, RegDeleteKey,
RegCloseKey 등등 → 스스로 찾아 볼것
– 직접 레지스트리에 정해진 포맷으로 작성
COM
4개의 exported 함수 (cont’d)
• HRESULT DllCanUnloadNow(void)
– COM 은 DLL이 더 이상 필요 없다고 판단되
면 DLL을 Unload 함
– 서버는 자신이 더 이상 필요 없는 경우 이 함
수에서 S_OK 를, 계속 실행되어야 하는 경
우 S_FALSE 를 리턴
– 객체를 하나라도 서비스 하고 있거나
LockServer 계수가 0 보다 큰 경우
S_FALSE를 리턴한다
COM
4개의 exported 함수 (cont’d)
• HRESULT DllGetClassObject(
REFCLSID, REFIID, void** ppv )
– 클래스 팩토리를 생성해서 요청하는 인터페
이스를 넘겨줌 (보통 IClassFactory)
– 사실은 클래스 팩토리가 아니어도 됨
– 해당 객체가 없으면
CLASS_E_CLASSNOTAVAILABLE 리턴
COM
4개의 exported 함수 (cont’d)
– 수공예로 구현
• if (rclsid == … ) else if (...
– 생성자가 연결 리스트에 등록 (MFC)
IMPLEMENT_OLECREATE( … )
– 객체 테이블을 정의 (ATL)
BEGIN_OBJECT_MAP()
OBJECT_ENTRY(CLSID_Tico, CTico)
OBJECT_ENTRY(CLSID_Atoz, CAtoz )
END_OBJECT_MAP()
COM
In-Proc Server 객체 생성 과정
• CoInitialize
– COM 객체 초기화
• CoGetClassObject
– COM은 레지스트리에서 해당 CLSID를 찾
아 서버 DLL 경로를 얻음
– LoadLibrary로 서버 DLL 을 로드하고
GetProcAddress로 4개 함수의 주소를 얻음
– DllGetClassObject로 클래스 팩토리 객체를
DLL로 부터 얻음
COM
객체 생성 과정 (cont’d)
• IClassFactory::CreateInstance
– 객체를 생성하고 인터페이스를 얻음
• IClassFactory::Release
– 클래스 팩토리 인터페이스 포인터를
Release 함
• 마지막 세 과정은 CoCreateInstance 로
간단히 할 수 있음
COM
객체 소멸 과정
• IUnknown::Release
– 사용한 객체의 Release 함수를 호출
– 객체의 레퍼런스 카운터가 0이 되었다면 객
체를 삭제
– 그 후에 DllCanUnloadNow 함수로 더 이상
DLL이 존재할 필요가 없다면 DLL 을
Unload
• CoUninitialize
– COM 사용을 종료한다
COM
ATL (Active Template Library)
• MFC&T
– MFC와 ATL을 함께 일컬음
• MFC
– Class Bodies, Application Framework
• ATL
– Templates, Some Global Function
COM
ATL vs. MFC
• MFC COM 지원
– 완전한 랩핑 - COM 지식이 거의 없어도 됨
– MFC의 모든 기능을 사용할 수 있음
– 프로그램의 크기가 매우 큼
– Automation과 Active-X 를 위한 지원만 존재
(Simple COM Object, 또는 범용적인 COM
Object를 만들기에 부적합)
COM
ATL vs. MFC (cont’d)
• ATL COM 지원
– ATL은 오직 COM만을 위해 만들어 졌음
– 거의 모든 구현이 템플릿으로, 프로그램의
크기가 매우 작고 가벼움
– 범용적인 구현 부분만 제공하기 때문에 어
떤 COM 객체라도 만들 수 있음
– MFC 를 결합할 수도 있음
– 상당한 COM 지식이 필요함
– 인터페이스가 부실함
COM
_Module ( in ATL )
• CComModule 객체의 전역 인스턴스
• COM 서비스 제공 모듈이 해야 하는 기
본적인 작업을 총괄
• LockServer 계수와 객체 개수를 관리
• 객체 테이블을 관리
• 레지스트리에 서버를 등록/해제함
COM
ATL COM Class 선언
class ATL_NO_VTABLE CTico :
public ICarControl,
public CComObjectRoot<CComSingleTreadModel>,
public CComCoClass<CTico,&CLSID_Tico>
{…}
COM
ATL COM 객체의 계층도
CComObjectRootBase
CComCoClass
CComObjectRootEx
CTico
CComObject
COM
ATL 에서의 객체 생성 과정
• DllGetClassObject
– _Module.GetClassObject 함수로 객체 맵을
조사함
• BEGIN_OBJECT_MAP()
OBJECT_ENTRY( CLSID_Tico, CTico )
END_OBJECT_MAP()
• _ATL_OBJMAP_ENTRY
COM
객체 생성 과정 (cont’d)
• _ATL_OBJMAP_ENTRY 내용 예
{
&CLSID_Tico, &CTico::UpdateRegistery,
&CTico::_ClassFactoryCreatorClass::CreateInstance,
&CTico::_CreatorClass::CreateInstance,
NULL/*pCF*/, 0/*dwRegister*/,
&CTico::GetObjectDescription
}
COM
객체 생성 과정 (cont’d)
• pfnGetClassObject으로 클래스 팩토리 객체를
생성
• 이 객체는 자신의 _COM_MAP에서
IClassFactory 를 찾은 후 이를 리턴
• 이 객체는 다시 pfnCreateInstance 함수를 호출
하여 COM 객체를 생성
• 새롭게 만들어 진 COM 객체는 마찬가지로
_COM_MAP에서 원하는 인터페이스를 찾아서
이를 리턴
COM
ATL 이 생성한 매크로
• ATL_NO_VTABLE
– 가상 함수 테이블을 안 만들어서 크기를 줄이
는 선택사항
• DECLARE_REGISTRY_RESOURCEID()
– 레지스트리에 저장될 텍스트 포맷 파일 리소
스를 선언함
• DECLARE_PROTECT_FINAL_CONSTR
UCT()
– FinalConstruct 함수를 보호함
COM
5. Local Server 구현
COM
RPC (Remote Procedure Call)
• RPC
– 프로세스 경계(또는 호ㅅ트 경계)를 넘기 위
해서 함수 호출을 IPC 또는 네트워크를 통
해서 하는 것
– Marshaling : 함수 호출을 네트워크 패킷으
로 인코딩 하는 것 (UnMarshaling:그 반대)
COM
RPC 예
long Double( long a )
1743 | DoIt | 10
Host A
Host B
1743Ack | 20
※ 포인터 전달 방법은?
COM
Proxy / Stub
• 원거리에 있는 객체를 조작하기 위한 개
념
• IDL 컴파일러가 코드를 제작해 줌
• MIDL
– 인터페이스 정의 파일 생성
– uuid 상수 등 정의 파일 생성
– Marshaling 코드 생성
– Proxy/Stub Code 생성
COM
Proxy/Stub 예
Application
COM 객체
COM
Proxy/Stub 예 (cont’d)
Application
COM Proxy
HOST B
HOST A
Com Stub
COM 객체
COM
포인터의 전달
• [in], [out], [in,out]
– RPC 패킷에 싣는 데이터를 선택함
• [ref], [unique], [ptr]
– 포인터의 용도별 속성
• [uuid_is(00A8…-00..-..046)]
– IUnknown 전달 → Proxy/Stub 생성
• [length_is()], [size_is()], [first_is()],
[last_is()]
– 배열 전달
COM
포인터의 전달 (예)
[
object,
uuid(742810E2-FFD2-11D2-98B3-000000000000),
helpstring("ICarControl Interface"),
pointer_default(unique)
]
interface ICarControl : IUnknown
{
[helpstring("method RotateHandle")]
HRESULT GetSpeed( [out] long* pnAngle);
COM
포인터의 전달 (예) (cont’d)
HRESULT MyQueryInterface(
[in] REFIID riid, [out,uuid_is(riid)] void** ppvObject );
HRESULT Sort(
[in] long nElements,
[in,out,size_is(nElements)] long anElements[] );
• MIDL
– Tico.IDL 을 MIDL로 컴파일 하면
Tico_p.c, Tico_i.c, Tico.h 의 세 파일이 생성
– local 인터페이스인 경우 Tico.h만 생성
COM
Local Server 구현
• In-Proc-Server에 있던 4 함수
– DllRegisterServer/DllUnregisterServer
– DllCanUnloadNow
– DllGetClassObject
• 이들과 같은 역할을 하도록 치환
• EXE 인 만큼 능동적인 처신이 기대됨
COM
DllRegisterServer
/DllUnregisterServer 의 치환
• 커맨드 라인
– /RegServer, -RegServer
• DllRegisterServer
– /UnregServer, -UnregServer
• DllUnregisterServer
– 대소문자 구별 안함
– 슬래쉬(/)와 하이픈(-) 모두 지원
– regsvr32.exe 의 도움 없이 실행
COM
DllRegisterServer
/DllUnregisterServer 의 치환
• ATL 구현 예
if (lstrcmpi(lpszToken, _T("UnregServer"))==0)
{
_Module.UpdateRegistryFromResource(
IDR_ComTestSvr, FALSE);
nRet = _Module.UnregisterServer(TRUE);
bRun = FALSE;
break;
}
COM
DllCanUnloadNow의 치환
• 서비스 하는 객체의 수가 0 이고
LockServer Counter가 0 일 때
• 스스로 종료 메시지를 자신에게 보냄
• PostQuitMessage(0),
PostThreadMessage(dwThreadID,
WM_QUIT, 0, 0)
COM
DllGetClassObject의 치환
• ROT (Registered Object Table)
– 시스템이 관리하는 객체 테이블
– 현재 활성화 되어 있는 Local-Server들이 제
공할 수 있는 클래스들의 IClassFactory(와
기타 정보들)의 리스트
– CoRegisterClassObject
/CoRevokeClassObject
COM
DllGetClassObject의 치환 (c’d)
• 커맨드 라인
– /Embedded, -Embedded
– 요즘은 무시함 (ATL 최근버전)
• 실행 시
– 제공하는 모든 객체에 대해서 IClassFactory
를 CoRegisterClassObject로 ROT에 등록
• 종료 시
– 모든 객체를 CoRevokeClassObject 함
※ 위의 두 COM API 에 대해서는 도큐먼트를 반드시 읽을 것
COM
6. 포함과 통합
(Containment & Aggregation)
COM
Inheritance
• 객체가 다른 객체나 인터페이스의 특성
을 물려 받는 것
• interface-inheritance (인터페이스 상속)
• implementation-inheritance(구현 상속)
• COM 은 외형상 interface-inheritance만
제공한다고 함
COM
COM Inheritance 지원
• Containment (포함)
– 다른 객체를 완전히 내부에 포함하는 것
• Aggregation (통합)
– 다른 객체와 합체 하는 것 -_C++ 에서의 구현 상속
class X { void DoX(); }
class Y : public X
{ void DoY(); }
DoX()
DoY()
COM
Containment
IUnknown
IX
IY
IUnknown
IY
Y
COM
Containment 구현
HRESULT CTico::FinalConstruct()
{ return CoCreateInstance( CLSID_Grandeur, …
(void**)&m_pGrandeur );
}
STDMETHODIMP CTico::RotateHandle( long nAngle )
{
m_pGrandeur->RotateHandle( nAngle );
}
COM
Aggregation
IUnknown
IX
IUnknown
IY
Y
COM
Aggregation (cont’d)
• IY::QueryInterface( IID_IX ) 문제
– 내부의 Y 객체는 IX 에 대해서 알 수 없음
• IY::QueryInterface( IID_IUnknown ) 문제
– 동일한 IUnknown* 을 리턴하지 않음
• 내부의 객체는 자신이 Aggregation 되었
는지 알고 있어야 함
COM
Aggregation (cont’d)
– Aggregation 되었다면 IX의 IUnknown을 보
관해 두었다가 IX의 QueryInterface 를 호출
– AddRef, Release도 마찬가지임
• CoCreateInstance( REFCLSID,
LPUNKNOWN *pUnkOuter,
DWORD clsContext,
REFIID riid, LPVOID* ppvObject);
– pUnkOuter, riid, ppvObject
COM
Aggregation 구현
struct IInnerUnknown {
…InnerQueryInterface(...) = 0;
…InnerAddRes(...) = 0;
…InnerRelease(...) = 0;
};
COM
Aggregation 구현 (cont’d)
class CY :
public IY,
public IInnerUnknown, ...
{
...QueryInterface(...)
{ m_pOuter->QueryInterface(); }
…AddRef(…)
{ m_pOuter->AddRef(); }
…Release(…)
{ m_pOuter->Release(); }
COM
Aggregation 구현 (cont’d)
…InnerAddRef(…) { InterlockedIncrease( &m_nRef ); }
…InnerRelease(…) { … }
…InnerQueryInterface(…)
{
…
if ( riid == IID_IUnknown )
*ppvObject = reinterpret_cast<IUnknown*>
( static_cast<IInnerUnknown*>(this));
else if ( riid == IID_IY ) *ppvObject = (IY*)this;
}
};
COM
Aggregation 구현 (cont’d)
IY*
IUnknown*
QueryInterface
AddRef
Release
ppvObject
DoY
IUnknown*
InnerQueryInterface
InnerAddRef
InnerRelease
COM
Aggregation 구현 (cont’d)
• IClassFactory::CreateInstance 구현
HRESULT CreateInstance( … ) {
…
CTico* o = new CTico;
...
if ( pUnkOuter != NULL && riid != IID_IUnknown )
return CLASS_E_NOAGGREGATION;
if ( pUnkOuter ) o->m_pOuter = pUnkOuter;
else o->m_pOuter = reinterpret_cast<IUnknown*>(
static_cast<IInnerUnknown*>(this));
...
COM
ATL 지원
• IInnerUnknown를 다루는 내용은 자동으
로 지원됨
• DECLARE_GET_CONTROLLING_UNK
NOWN()
• FinalConstruct()
• DECLARE_PROTECT_FINAL_CONST
RUCT()
COM
ATL 지원 (cont’d)
class ATL_NO_VTABLE CAggrExam :
… public IAggrExam
{ ...
virtual HRESULT FinalConstruct();
virtual void FinalRelease();
IUnknown* m_lpAggrInner;
DECLARE_PROTECT_FINAL_CONSTRUCT()
DECLARE_GET_CONTROLLING_UNKNOWN()
BEGIN_COM_MAP(CAggrExam)
COM_INTERFACE_ENTRY(IAggrExam)
COM_INTERFACE_ENTRY_AGGREGATE(IID_IInner,m_lpAggrInner)
END_COM_MAP()
… };
COM
ATL 지원 (cont’d)
HRESULT CAggrExam::FinalConstruct()
{
HRESULT hRes = CoCreateInstance(CLSID_Example,
GetControllingUnknown(), CLSCTX_ALL,
IID_IUnknown, (LPVOID*)&m_lpAggrInner);
if ( FAILED(hRes) || m_lpAggrInner == NULL)
return E_FAIL;
else return S_OK;
}
void CAggrExam::FinalRelase()
{ if ( m_lpAggrInner ) m_lpAggrInner->Release(); }
COM
MFC 지원
class CAggrExample : public CCmdTarget
{
public:
CAggrExample() { m_lpAggrInner = NULL; }
protected:
LPUNKNOWN m_lpAggrInner;
virtual BOOL OnCreateAggregates();
virtual void OnFinalRelease();
DECLARE_INTERFACE_MAP()
};
COM
MFC 지원 (cont’d)
BOOL CAggrExample::OnCreateAggregates()
{
// wire up aggregate with correct controlling unknown
HRESULT hRes = CoCreateInstance(CLSID_Example,
GetControllingUnknown(), CLSCTX_ALL,
IID_IUnknown, (LPVOID*)&m_lpAggrInner);
if ( FAILED(hRes) || m_lpAggrInner == NULL)
return FALSE;
// optionally, create other aggregate objects here
return TRUE;
}
COM
MFC 지원 (cont’d)
void CAggrExample::OnFinalRelease()
{
if ( m_lpAggrInner ) m_lpAggrInner->Release();
}
BEGIN_INTERFACE_MAP(CAggrExample, CCmdTarget)
INTERFACE_AGGREGATE(CAggrExample, m_lpAggrInner)
END_INTERFACE_MAP()
COM
7. COM 쓰레딩 모델
COM
쓰레딩 모델
• 쓰레딩 모델
– 단일 쓰레딩 모델, 멀티 쓰레딩 모델
• COM 쓰레드 독립성
– 클라이언트와 서버는 상대방이 사용하는 쓰
레딩 모델에 종속되지 않음
– 이들에 대한 중개는 COM 이 알아서 처리
COM
COM 쓰레딩 모델
• Apartment
– 공통된 쓰레딩 모델을 가지고 있는 COM 객
체/쓰레드 그룹
– 쓰레드들과 COM 객체들이 거주함
– STA or MTA
COM
COM 쓰레딩 모델 (cont’d)
STA
Thread 1
MTA
STA
Thread 2, 3
Process A
Process B
COM
COM 쓰레딩 모델 (cont’d)
• 규칙
– 모든 COM 객체는 특정한 하나의
Apartment 내에 거주
– 각 Apartment 내에는 복수개의 COM 객체
가 존재 가능
– 하나의 Apartment는 특정한 하나의 프로세
스에만 포함됨
– 쓰레드는 한번에 하나의 Apartment에만 들
어가서 그 Apartment내의 객체를 접근 가능
COM
STA
• STA (Single Threaded Apartment)
– 하나의 쓰레드만 들어갈 수 있는 Apartment
– 이 안의 COM 객체는 특정한 하나의 쓰레드
에 의해서만 접근 가능
– 다중 쓰레드에 대한 동기화가 필요 없음
– 한 프로세스에 다수의 STA 존재
– 동기화 & 함수 호출 방식
• Hidden Window 에 전달되는 메시지 큐 이용
• Pump/DispatchMessage 로 함수가 호출됨
COM
MTA
• MTA (Multi Threaded Apartment)
– 여러 쓰레드가 동시에 들어갈 수 있음
– 이 안의 COM 객체는 같은 Apartment내의
모든 쓰레드에 의해 동시 접근 가능
– 다중 쓰레드에 대한 대비가 필요
– 한 프로세스에 하나의 MTA로 충분
– 동기화 & 함수 호출 방식
• 동기화는 COM 객체가 스스로 지원
• 함수 호출은 직접 쓰레드가 함수 내로 진입함
COM
마셜링
• 다시, 마셜링이란?
– 어떤 쓰레드가 자신이 거주하는 Apartment
와 다른 Apartment 에 있는 객체를 접근할
때 필요한 수단
– 비록 같은 프로세스 내(같은 주소 공간 내)
에 있는 쓰레드와 객체 지간이라도
Apartment 가 다르면 마셜링이 필요
• Free-Threaded Marshaler
– 같은 프로세스 내에서 마셜링 코드를 최적
화 하는 커스텀 마셜링 객체(Aggregation됨)
COM
쓰레드의 Apartment 선택
• 현재 쓰레드가 STA로 들어가려면
– CoInitializeEx(NULL,
COINIT_APARTMENTTHREADED);
– CoInitialize(NULL);
– 쓰레드 별로 새로운 STA를 생성함
• 현재 쓰레드가 MTA로 들어가려면
– CoInitializeEx(NULL,
COINIT_MULTITHREADED);
– 공통의 MTA영역으로 들어감(없으면 생성)
COM
쓰레드의 Apartment 선택 (c’d)
• Apartment에서 나오기
– CoUnitialize();
• 도중에 다른 Apartment로 들어가기
– 일단 CoUnitialize()로 나와야 함
– 다시 CoInitializeEx()로 다른 곳으로 들어감
COM
객체의 Apartment 선택
COM
객체의 Apartment 선택 (c’d)
• ThreadingModel
– 없는 경우 : 오직 그 프로세스의 주 STA 에서만
– “Apartment” : STA
– “Free” : MTA
– “Both” : STA & MTA
COM
ATL 지원
• CComObjectRootEx
<CComSingleThreadModel>
• CComObjectRootEx
<CComMultiThreadModel>
• CComObjectRoot
– CComObjectRootEx
<CComObjectThreadModel>
• rgs 파일의 ThreadingModel 부분
COM
8. 스마트 포인터
COM
스마트 포인터
• “인터페이스 포인터” 객체
– 포인터처럼 사용되는 C++ 객체
– 특정 인터페이스를 가리키는 포인터로서 사
용함
• 지원 기능
– 자동 참조 계수 관리 (AddRef/Release)
– 인터페이스 질의, 객체 생성 자동화
• ATL(CComPtr) vs compiler(_com_ptr_t)
COM
간이 스마트 포인터 구현 예
template <class T>
struct CSmartPtr {
CSmartPtr() { p = 0; }
~CSmartPtr() { if ( p ) p->Release(); }
CSmartPtr& operator=( T* pT ) {
if ( p ) p->Release(); p=pT; if ( p ) p->AddRef(); return *this; }
CSmartPtr& operator=( const CSmartPtr& pT ) {
if ( p ) p->Release(); p=pT.p; if ( p ) p->AddRef(); return *this; }
T** operator&() { return &p; }
T* operator->() { return p; }
operator T*() { return p; }
T* p;
};
COM
스마트 포인터 사용 예 (cont’d)
CSmartPtr<ICarControl> pControl;
CSmartPtr<IUnknown> pUnk;
CoCreateInstance( CLSID_Tico, …, IID_IUnknown,
&pUnk );
pUnk->QueryInterface( … IID_ICarControl, &pControl );
pControl->RotateHandle( 45 );
...
pControl = 0;
// need not if we are in a block
pUnk = 0;
//
COM
ATL Smart Pointer
• CComPtr<ICarControl> pControl;
– 앞에 나온 간이 포인터의 기능과 기타 여러가
지 기능을 제공함 (Attach/Detach,
CreateInstance 등)
• CComQIPtr<ICarControl,IID_ICarControl>
– 포인터 자체가 자신의 타입을 알고 있음
– 다른 타입의 인터페이스 포인터를 대입하는 경
우 스스로 QueryInterface 를 호출함!
– IUnknown에 대한 CComQIPtr은 만들수 없음
(그 이유는 ?)
COM
ATL Smart Pointer 사용 예
CComPtr<IUnknown> pUnk;
hRes = pUnk->CoCreateInstance( L”ComSvr.Tico” );
CComQIPtr<ICarControl,IID_ICarControl>
pControl = pUnk;
// 내부적인 Q.I. 발생
if ( !pUnk ) hRes = pControl->RotateHandle( 45 );
// pUnk->Release(); pControl->Release(); : 죽음임
COM
Complier Smart Pointer
• 컴파일러가 제공하는 COM 기능
– __declspec(uuid), __declspec(property)
– __uuidof()
– #import
– _com_ptr_t, _com_error
– _bstr_t, _variant_t
COM
uuid
struct __declspec(uuid("401dd04f-035c-11d3-98c20000212035b8")) Tico;
struct __declspec(uuid("401dd04e-035c-11d3-98c20000212035b8")) ICarControl : IUnknown { … }
CoCreateInstance( __uuidof( Tico ), … );
p->QueryInterface( __uuidof( ICarControl ), … );
COM
TYPELIB
• Type Library
– 그 객체에 대한 타입 정보 데이터베이스
– VC, VB 에서 프로그램이 사용
– EXE나 DLL에 숨겨진 리소스로 포함됨(리
소스 타입 : “TYPELIB”
– 확장자 tlb, dll, exe …
– MIDL 또는 MkTypLib 컴파일러가 idl, odl 파
일에서 생성
COM
#import
#import “ComSvr.tlb” no_namespace named_guids
// ComSvr.tlh 와 ComSvr.tli 가 생성됨
// 이후부터는 ComSvr.tlh가 인클루드 된 것처럼 처리
ICarControlPtr pControl;
pControl->RotateHandle( 45 );
...
COM
_com_ptr_t<…>
• COM 스마트 포인터 타입
– _com_ptr_t<…>에서 계승됨
– ComQIPtr와 유사함
– 스스로 객체를 생성할 수 있는 등 더 많은 기
능을 지원함
– 포인터 이름 : 인터페이스이름 + “Ptr”
• ICarControlPtr pControl;
– 이름이 충돌하는 경우 : namespace 사용
COM
_com_error
• _com_error
– HRESULT가 실패값이면 _com_ptr_t 객체
가 이 객체를 throw
– Error() : HRESULT 리턴
– FormatMessage() : 스트링으로 변환
– 기타 등등...
COM
사용 예
#import “Tico.tlb” no_namespace
try {
ICarControlPtr pControl( L(“ComSvr.Tico”) );
IRadioPtr pRadio = pControl;
pControl->RotateHandle( 45 ); pRadio->Tune( 91.9 );
}
COM
사용 예 (cont’d)
catch ( _com_error e ) {
HRESULT hRes = e.Error();
_tprintf( e.ErrorMessage() );
}
COM
Future Work
• DCOM
• Active-X, ASP
• Automation, Active-Document
COM
- 실습 종합적인 COM 객체 구현