1.54k likes | 1.97k Views
Visual C++ ATL/COM Programming. 천 정 아 jachun3@kornet.net. 교육일정. COM 프로그래밍 개요. Component Object Model. 자신의 고유한 기능을 제공하는 단위 어플리케이션 ( 즉 , 컴포넌트 ) 의 통합 및 커뮤니케이션 방법에 대한 표준을 정의한 사양 COM 컴포넌트를 정의하는 방법에 대한 표준이면서 COM 컴포넌트를 사용하는 방법에 대한 표준임 핵심적임 MS 의 기반 기술 역할 제공
E N D
Visual C++ ATL/COM Programming 천 정 아 jachun3@kornet.net
Component Object Model • 자신의 고유한 기능을 제공하는 단위 어플리케이션(즉, 컴포넌트)의 통합 및 커뮤니케이션 방법에 대한 표준을 정의한 사양 • COM 컴포넌트를 정의하는 방법에 대한 표준이면서 COM 컴포넌트를 사용하는 방법에 대한 표준임 • 핵심적임 MS의 기반 기술 역할 제공 • OLE, ActiveX, ADO, OLE DB, Active Directory 모두 COM을 기반으로 작성
COM의 목표 • 각 컴포넌트는 서로 다른 언어로 개발될 수 있어야 한다. • 컴포넌트가 설치되고 실행하는 위치에 관계없이 같은 방법으로 컴포넌트를 사용할 수 있어야 한다. • 컴포넌트의 버전 관리가 쉬워야 한다. • 한 업체에 종속적이지 않아야 한다.
COM 컴포넌트의 조건 • 언어 독립적이어야 한다. • 이진 형태로 제공되어야 한다. • 버전 호환성을 제공해야 한다. • 위치 투명성을 제공해야 한다.
COM 인터페이스 (1) • COM 객체가 자신의 기능을 노출시키는 기본적인 방법 • COM 객체와 이를 사용하는 클라이언트 사이의 계약 COM 클라이언트 COM 컴포넌트 COM 인터페이스
COM 인터페이스 (2) • 인터페이스의 의미 • 논리적 의미 • 특정 서비스를 제공하는 일련의 함수(메서드)들 • ~하는 기능(서비스) • 물리적 의미 • 함수 포인터 배열 형태의 메모리 구조 • C++의 가상함수테이블 순수 가상함수로만 구성되는 추상 클래스로 정의 • 각 COM 객체는 반드시 IUnknown 인터페이스와 COM 객체 고유의 기능을 노출하는 하나 이상의 인터페이스를 제공
vptr 멤버 변수 인터페이스 메모리 구조 interface IFoo : IUnknown { virtual HRESULT __stdcall Method1() = 0; virtual HRESULT __stdcall Method2() = 0; virtual HRESULT __stdcall Method3() = 0; }; class CImplIFoo : public IFoo { // QueryInterface, AddRef, Release 구현 // Method1, Method2, Method3 구현 }; 가상함수테이블 인스턴스 인터페이스 포인터 QueryInterface AddRef Release Method1 Method2 Method3
COM 클라이언트/컴포넌트 • COM 컴포넌트 : 자신의 고유한 서비스 제공 • COM 서버 : 물리적인 파일(DLL, EXE)의 실행 인스턴스 • 인-프로세스 서버(In-proc Server) : DLL 파일로 구현 • 아웃-오브-프로세스 서버(Out-of-process Server) : EXE 파일로 구현 • 로컬 서버(Local Server) • 리모트 서버(Remote Server) • COM 객체 : COM 인터페이스를 구현한 클래스의 인스턴스 • COM 클라이언트 : COM 컴포넌트의 서비스를 사용
GUID • Globally Unique Identifier • 128 bit 크기의 정수 값(구조체로 정의) • 전세계적으로 시간과 장소에 관계없이 고유하다고 보장할 수 있는 값 • UUID에서 유래 • GUID의 사용 • IID : 인터페이스 ID (인터페이스 식별자) • CLSID : 클래스 ID (COM 객체 식별자) • GUIDGEN.EXE로 생성
HRESULT • 대부분의 COM 인터페이스 함수는 HRESULT를 리턴 • 32 bit 정수값 (LONG) • SUCCEEDED, FAILED 매크로와 함께 사용 15 bit 16 bit Facility code Return Code 31 30 16 15 0 Severity code
COM 컴포넌트 등록 • 레지스트리에 반드시 등록 후 사용
COM 클라이언트 작성 • COM 라이브러리 초기화 • COM 객체의 CLSID 구함 • COM 객체의 인스턴스 생성 • COM 객체가 제공하는 인터페이스 포인터를 구하여 메서드 호출 (COM 객체의 서비스 사용) • COM 라이브러리의 초기화를 해제
COM 라이브러리 초기화/해제 • COM 라이브러리 • COM을 사용하는 모든 application에서 유용하게 사용될 수 있는 컴포넌트 관리 서비스 제공 • HRESULT CoInitialize(LPVOID); • 프로세스마다 한번만 호출 • HRESULT CoInitializeEx(LPVOID, DWORD); • #define _WIN32_DCOM 매크로 정의 후 사용 • 두번째 인자로 COINIT_APARTMENTTHREADED 지정 • void CoUninitialize(); • CoInitialize와 짝을 이루어 호출
COM 객체의 CLSID 구하기 • CLSID가 정의된 소스 파일 이용 • 레지스트리 편집기나 OLE/COM 개체뷰어를 사용하여 CLSID 구하기 • ProgID 사용 • 읽기 쉬운 문자열 형태의 식별자 • <컴포넌트 명>.<객체 명>.<버전> • HKEY_CLASSES_ROOT의 서브키로 등록 • 사용은 간편하지만 유일성 보장되지 않음 • 형식 라이브러리 이용
… 레지스트리 등록 예 HKEY_CLASSES_ROOT CLSID {22D6F312-B0F6-11D0-94AB-0080C74C7E95} InProcServer32 : C:\WINNT\System32\msdxm.ocx ProgID : MediaPlayer.MediaPlayer.1 VersionIndependentProgID : MediaPlayer.MediaPlayer MediaPlayer.MediaPlayer.1 ProgID CLSID : {22D6F312-B0F6-11D0-94AB-0080C74C7E95} MediaPlayer.MediaPlayer VerIndProgID CurVer : MediaPlayer.MediaPlayer.1 CLSID : {22D6F312-B0F6-11D0-94AB-0080C74C7E95}
CLSID와 ProgID 변환 • HRESULT CLSIDFromProgID(LPCOLESTR, LPCLSID); • HRESULT ProgIDFromCLSID(REFCLSID, LPOLESTR);
COM의 문자열 • 유니코드 문자열 사용 • typedef unsigned short wchar_t; • typedef wchar_t WCHAR; • typedef WCHAR OLECHAR; • typedef OLECHAR* LPOLESTR; • typedef const OLECHAR* LPCOLESTR; • TCHAR • UNICODE 매크로 정의 시 WCHAR로 처리 • UNICODE 매크로 미정의 시 char로 처리
COM 객체의 생성 • STDAPI CoCreateInstance(REFCLSID rclsid, LPUNKNOWN pUnkOuter, DWORD dwClsContext, REFIID iid, LPVOID* ppv); • rclsid : COM 객체의 CLSID • pUnkOuter : 통합에서만 사용 • dwClsContext : 실행 형태 (CLSCTX_INPROC_SERVER, CLSCTX_LOCAL_SERVER 등) • riid : 사용하고자 하는 인터페이스 IID • ppv : COM 객체가 리턴하는 인터페이스 포인터의 주소
IUnknown • 모든 COM 인터페이스는 IUnknown을 상속 • 모든 COM 객체가 갖추어야 할 기본적인 서비스 제공 interface IUnknown { virtual HRESULT __stdcall QueryInterface( REFIID riid, void** ppv)=0; virtual ULONG __stdcall AddRef()=0; virtual ULONG __stdcall Release()=0; } ;
QueryInterface • 클라이언트가 COM 객체의 다른 인터페이스를 요청할 때 해당 인터페이스 포인터를 리턴 • COM 객체가 제공하는 인터페이스를 구하는 유일한 방법임
AddRef/Release • COM 객체가 스스로 자신이 몇 번 참조되어 있는가 하는 횟수를 관리 • 레퍼런스 카운터가 0일 때 스스로를 소멸 • 참조 카운터 규칙 • 인터페이스를 리턴하기 전에 AddRef 한다. • 인터페이스의 사용이 끝나면 Release 한다. • 인터페이스 포인터를 다른 인터페이스 포인터에 대입할 때도 AddRef 한다.
COM 객체의 사용 • 인터페이스 포인터를 통하여 인터페이스가 제공하는 메서드 호출 • QueryInterface를 통하여 COM 객체가 지원하는 다른 인터페이스를 요청 • 인터페이스의 사용이 끝나면 Release 호출 • COM 서버가 할당한 메모리를 클라이언트가 해제해야 하는 경우 CoTaskMemAlloc, CoTaskMemFree 사용
COM 컴포넌트의 구현 • COM 인터페이스의 정의 • COM 객체 클래스 구현 • 클래스 팩토리 클래스 구현 • COM 서버 구현
COM 인터페이스 정의 • IDL로 COM 인터페이스 정의 • MIDL 컴파일러로 컴파일 • IDL(Interface Definition Language) • 인터페이스를 정의하는 표준 개발 도구 • MIDL 컴파일러 제공 • OSF RPC의 IDL을 확장 • C/C++ like language with attribute • 언어 독립성 제공 (형식 라이브러리) • 위치투명성 제공 (프록시/스텁 코드)
IDL의 예 • 인터페이스 헤더 • object : COM 인터페이스 • uuid : 인터페이스 식별자(IID) // Hello.idl [ object, uuid(B98E4691-4C07-4c4b-8E88-2EC7EEF13862), ] interface IHello : IUnknown { import “unknwn.idl”; HRESULT sayHello([in, string] wchar_t* name, [out, string] wchar_t** message); }; • 메서드의 인자 헤더 • in : 클라이언트에서 서버로 이동(마샬링) • out : 서버에서 클라이언트로 이동(마샬링) • string : NULL 종료 문자열
MIDL 컴파일러의 역할 • C/C++에서 사용할 수 있는 인터페이스를 정의한 코드를 포함하는 헤더 파일 생성 • 커스텀 인터페이스에 대한 프록시(proxy)/스텁(stub) 코드 생성 • 자동화에서 사용되는 형식 라이브러리(Type Library) 생성 • library, coclass 문이 사용되는 경우에만 생성
COM 객체 구현 • 인터페이스 포함 • COM 객체 클래스 안에 인터페이스 구현 클래스를 포함 • 구문은 복잡하지만 디버깅이 쉽다. • MFC에서 사용 • 인터페이스 상속 • COM 객체 클래스를 인터페이스에서 상속 • 간단하다. • ATL에서 사용
COM 객체 클래스 정의 • 다중 인터페이스 구현을 위해 다중 상속 이용 • COM 객체 구현 클래스에서는 모든 인터페이스 메서드를 재정의해야 한다. class CHello : public IHello, public IGoodbye { // IUnknown 메서드 구현 // IHello 메서드 구현 // IGoodbye 메서드 구현 };
다중 상속 시 메모리 구조 CHello::this IHello vptr QueryInterface (IHello*)CHello::this IGoodbye vptr AddRef IHello 멤버 변수 Release (IGoodbye*)CHello ::this sayHello QueryInterface AddRef IGoodbye Release sayGoodbye
QueryInterface의 구현 HRESULT _stdcall CHello::QueryInterface(REFIID riid, LPVOID* ppv) { HRESULT hr = E_NOINTERFACE; *ppv = NULL; if(riid == IID_IUnknown || riid == IID_IHello) *ppv = static_cast<IHello*>( this ); else if(riid == IID_IGoodbye) *ppv = static_cast<IGoodbye*>( this ) if(*ppv != NULL) { AddRef(); return S_OK; } return hr; }
AddRef, Release의 구현 • InterlockedIncrement, InterlockedDecrement 함수 • thread-safe하게 증가, 감소 ULONG _stdcall CHello::AddRef() { return InterlockedIncrement(&m_cRef); } ULONG _stdcall CHello::Release() { if(InterlockedDecrement(&m_cRef) == 0) { delete this; } return m_cRef; }
COM 객체 서비스 메서드 구현 • COM 객체 고유한 서비스를 제공하는 인터페이스의 메소드를 구현 • 필요 시 생성자, 소멸자, 멤버 변수 추가 가능 • COM 객체의 CLSID 정의 • GUIDGEN 이용
클래스 팩토리의 구현 • COM 컴포넌트는 COM 객체의 인스턴스를 생성할 수 있는 매커니즘 제공해야 함 • 클래스 팩토리가 COM 객체의 인스턴스를 생성 • 클래스 팩토리도 일종의 COM 객체 • IClassFactory를 반드시 제공해야 함
IClassFactory interface IClassFactory : IUnknown { HRESULT __stdcall CreateInstance( LPUNKNOWN pUnkOuter, REFIID iid, LPVOID* ppv) = 0; HRESULT __stdcall LockServer(BOOL bLock) = 0; };
Client COM Library Server 클래스 팩토리 생성 IClassFactory 리턴 IClassFactory::CreateInstance 호출 CFactory IHello 리턴 CHello pIHello::sayHello호출 컴포넌트 생성 컴포넌트의 생성 과정 CoGetClassObject 호출 CoGetClassObject DllGetClassObject pIClassFactory pIHello pIClassFactory->Release() 호출
컴포넌트 생성 과정 (코드) HRESULT CoCreateInstance(REFCLSID rclsid, LPUNKNOWN pUnkOuter, DWORD dwClsContext, REFIID riid, LPVOID* ppv) { *ppv = NULL; IClassFactory* pIFactory = NULL; HRESULT hr = CoGetClassObject(rclsid, dwClsContext, NULL, IID_IClassFactory, (LPVOID*) &pIFactory); if( SUCCEEDED(hr) ) { hr = pIFactory->CreateInstance(pUnkOuter, riid, ppv); pIFactory->Release(); } return hr; }
인-프로세스 서버의 구현 • 클라이언트에서 COM 객체 사용 시 호출되는 4개의 익스포트 함수 구현 • DllGetClassObject • CoGetClassObject에 의해 호출 • DllRegisterServer, DllUnregisterServer • REGSVR32.EXE에 의해 호출 • DllCanUnloadNow • CoFreeUnusedLibrary에 의해 호출
로컬 서버 구현 • Win32에서는 프로세스마다 주소공간이 다르므로 로컬 서버나 리모트 서버가 리턴한 인터페이스 포인터는 클라이언트 주소공간에서는 의미가 없다. • 위치 투명성을 제공하기 위해서는 프록시와 스텁이 필요
프록시(Proxy)/스텁(Stub) Process Boundary Network Boundary Client EXE Server EXE Proxy DLL Stub DLL LPC RPC 마샬링 언마샬링
마샬링 • 표준 인터페이스의 마샬링 • 운영체제에 의해 제공(ole32.dll) • 자동화 인터페이스의 마샬링 • 운영체제에 의해 제공(oleaut32.dll) • 커스텀 인터페이스의 마샬링 • MIDL 컴파일 결과로 만들어지는 코드 이용 • IDL 작성 시 속성 지정
로컬 서버의 구현 • 인-프로세스 서버와는 달리 CoInitialize, CoUninitialize 호출 • DllRegisterServer, DllUnregisterServer • 명령행 인자 처리로 구현 • /RegServer, /UnregServer 옵션 처리 • DllGetClassObject • CoRegisterClassObject, CoRevokeClassObject 이용 • DllCanUnloadNow • 스스로 능동적으로 종료
CoRegisterClassObject (1) • COM은 내부적으로 등록된 클래스 팩토리 COM 객체를 저장하는 ROT 관리 • 클라이언트가 CoGetClassObject 호출 시 ROT부터 검사함 • 등록이 안된 경우 /Embedding 옵션으로 로컬 서버 실행 • 클래스 팩토리 COM 객체를 ROT에 등록함
CoRegisterClassObject (2) • STDAPI CoRegisterClassObject(REFCLSID rclsid, IUnknown* pUnk, DWORD dwClsContext, DWORD flags, LPDWORD lpdwRegister) • rclsid : COM 객체의 CLSID • pUnk : 클래스 팩토리 객체의 IUnknown* • flags : REGCLS_SINGLEUSE, REGCLS_MULTI_SEPARATE, REGCLS_MULTIPLEUSE • lpdwRegister : 클래스 팩토리에 대한 매직 쿠키 (CoRevokeClassObject에서 사용)
로컬 서버 종료 • 다음 조건 만족 시 능동적으로 종료 • COM 객체 카운터가 0이고 클라이언트가 IClassFactory::LockServer(FALSE)를 호출함으로써 마지막 로크 카운터가 0이 될 때 • 로크 카운터가 현재 0이고, 클라이언트가 IUnknown::Release를 호출하여 마지막 COM 객체 카운터가 0이 될 때 • PostQuitMessage 함수 이용
VC++의 COM 지원 • #import • __declspec 확장 속성 : uuid, property • __uuidof • _com_ptr_t 클래스 • _com_error 클래스 • _bstr_t 클래스 • _variant_t 클래스