COM, ATL

com을 위한 기초

디버그정 2008. 8. 14. 20:44
1. COM을 위한 기초
COM : 이진표준(binary standard)을 따르며, 구현은 블랙박스로 숨겨져 있고, 외부에선 인터페이스를 통해서만 접근 가능 이진표준 : 서로 다른 언어와 플랫폼에서 개발된 COM 객체 사이의 접근을 가능하게 함 즉, 이진표준에 따른다는 것은, 메모리 맵 상의 데이터 정렬방식이 같다는 것을 의미하게 되고, 이 규칙을 따르는 모든 플랫폼, 언어들이 이 블랙박스(COM 객체)에 접근이 가능하다는 것을 의미 COM에서 interface는 외부에서 접근할 수 있도록 open되어 있는 whitebox 구조이므로, struct(public을 기본)로 구현 COM 객체 자체는 blackbox구조이므로, class(private를 기본)로 구현 가상 상속 struct Life { int years; void IsAlive(); }; struct M : virtual Life { int mammal; void prog(); }; struct B : virtual Life { int wing; void fly(); }; struct Bat : M, B { int eat; void how(); }; Pure Virtual Method (순수 가상 함수) 기본 클래스에서 가상함수의 속성이 정의되어 있지 않은 함수 - 0으로 항상 초기화 Abstract Base Class (추상 기본 클래스) 클래스 선언문 내에 하나 이상의 순수가상함수를 가지고 있는 클래스 추상 기본 클래스로부터 상속 받은 파생 클래스는 반드시 추상 기본 클래스에서 선언된 순수 가상함수를 모두 오버라이딩(재정의)해야 한다. class book { public: virtual void title() = 0; } class COM_book : public book { public: void title() { cout << “ Inside COM” << endl; } } class Cpp_book : public book { public: void title() { cout << “ C++” << endl; } }
2. COM : Component Object Model
GUID(Globally Unique IDentifier) : 모든 interface에 할당되는 unique ID Interface GUID : IID #define IID GUID \Common\Tools\Guidgen.exe IDL : interface를 정의하는 standard tool 모든 COM interface는 반드시 IUnknown 으로부터 상속되어 생성 -> IUnknown의 정의 : UNKNWN.H
3. QueryInterface
interface는 struct이다. #define interface struct -> Objbase.h 에 정의 interface IUnknown { virtual HRESULT __stdcall QueryInterface(const IID& iid, void** ppv) = 0; virtual ULONG __stdcall AddRef() = 0; virtual ULONG __stdcall Release() = 0; }; IUnknown* CreateInstance(); QueryInterface() : IID를 이용하여 IID에 대응되는 interface의 pointer를 return iid : ptr를 얻으려는 interface의 id ppv : 얻은 ptr를 저장할 변수 HRESULT : 32-bit value, not handle S_OK, E_NOINTERFACE, … SUCCEEDED, FAILED macro와 함께 사용 void foo(IUnknown* pI) { IX* pIX = NULL; HRESULT hr = pI->QueryInterface(IID_IX, (void**)&pIX); if (SUCCEEDED(hr)) { pIX->Fx(); } } 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) { *ppv = static_cast(this); } else { *ppv = NULL; return E_NOINTERFACE; } static_cast(*ppv)->AddRef(); return S_OK; }
4. Reference Counting
- rules - interface를 return하기 전에 AddRef를 호출 : QueryInterface(), CreateInstance() assignment 후엔 AddRef 호출 interface 사용 후엔 Release 호출 ULONG __stdcall AddRef() { return InterlockedIncrement(&m_cRef); } ULONG __stdcall Release() { if (InterlockedDecrement(&m_cRef) == 0) { delete this; return 0; } return m_cRef; }
5. HRESULTs, GUIDs, Registry, etc...
1. HRESULTs QueryInterface를 비롯한 대부분의 COM interface function들은 HRESULT를 리턴 32-bit(4 fields) -> WINERROR.H 에 정의 status codes : multiple success, failure codes를 리턴 SUCCEEDED, FAILED macro를 사용 2. GUIDs IID : interface를 identify하는 constant 128-bit의 GUID structure로 정의 How to get? -> GUIDGEN.exe / UUIDGEN.exe DEFINE_GUID(OBJBASE.h) : declaraing, defining을 한 번에 -> GUIDGEN.exe의 두번째 format 사용 3. Windows Registry COM은 HKEY_CLASSES_ROOT만 사용 InprocServer32 : 각 CLSID key에 대해 관심 있는 subkey이며, DLL의 이름을 나타냄 Other Registry Details : ProgID format : .. 4. CLSID clsid; CLSIDFromProgID(L"Helicopter.TailRotor", &clsid); ProgIDFromCLSID(REFCLSID clsid, LPOLESTR* lplpszProgID); 5. regsvr32, STDAPI regsvr32.exe : DLL의 등록과 해제 STDAPI DllRegisterServer(); STDAPI DllUnregisterServer(); #define STDAPI EXTERN_C HRESULT STDAPICALLTYPE -> objbase.h 에 정의 -> extern “C” HRESULT __stdcall 6. Component Categories CATID라는 GUID를 할당 받은 interface 집합 그 category에 소속되는 component들은 집합 내의 모든 interface를 구현해야 함 Component Category Manager ICatRegister : 새로운 category의 등록, 해제 ICatInformation : category에 대한 정보를 제공 7. COM 라이브러리 함수 OLE32.DLL : COM client, server가 사용하는 공용 함수들 HRESULT CoInitialize(void* reserved); COM library를 사용하기 위해 호출 process 단위로 한 번 호출해야 CoInitializeEx() : for DCOM void CoUninitialize(); OleInitialize() : OLE의 추가 기능을 사용하고자 할 때 Memory Management HRESULT CoGetMalloc(DWORD, LPMALLOC*); IMalloc interface pointer를 return IMalloc::Alloc, IMalloc::Free IMalloc pointer를 관리하기가 불편 void* CoTaskMemAlloc(ULONG cb); void CoTaskMemFree(void* pv); Converting from Strings to GUIDs StringFromCLSID // Task Memory allocator를 사용해야 StringFromIID StringFromGUID2 // 내부적으로 Unicode 사용 -> wcstombs()로 변환 필요하기도 CLSIDFromString IIDFromString /************************************ wchar_t szCLSID[39]; int r = ::StringFromGUID2(CLSID_Component1, szCLSID, 39); #ifndef _UNICODE char szCLSID_single[39]; wcstombs(szCLSID_single, szCLSID, 39); #endif ************************************/ /************************************ wchar_t* string; // get string from CLSID ::StringFromCLSID(CLSID_Component1, &string); // use String ... // free string ::CoTaskMemFree(string); ************************************/ In process Server (DLL) 1) Window registry에 등록 : regsvr32 server.dll 2) IUnknown 획득 3) CA(COM object) 생성 4) IUnknown 반환 5) Window registry에서 해제 : regsvr32 /u server.dll
6. Class Factory
CoGetClassObject() 1) COM 객체 생성 2) COM 객체 이용 CoCreateInstance() 1) Class Factory 생성 2) COM 객체 생성 3) COM 객체 이용 STDAPI CoCreateInstance( REFCLSID rclsid, // Class identifier (CLSID) of the object LPUNKNOWN pUnkOuter, // Pointer to whether object is or isn't part of an aggregate DWORD dwClsContext, // Context for running executable code REFIID riid, // Reference to the identifier of the interface LPVOID * ppv // Address of output variable that receives the interface pointer requested in riid ); /************************************ IX* pIX = NULL; HRESULT hr = ::CoCreateInstance(CLSID_Component1, NULL, CLSCTX_INPROC_SERVER, IID_IX, (void**)&pIX); if (SUCCEEDED(hr)) { pIX->Fx(); pIX->Release(); } ************************************/ 문제점 : Inflexible(비융통성) client의 component 생성 control 불가능 해결 : component 생성 전용 component 사용 CoCreateInstance() -> class factory 생성 -> 요구한 component 생성 class factory : 다른 component를 생성하는 component IClassFactory : component를 생성하는 표준 interface STDAPI CoGetClassObject( REFCLSID rclsid, // CLSID associated with the class object DWORD dwClsContext, // Context for running executable code COSERVERINFO * pServerInfo, // Pointer to machine on which the object is to be instantiated // reserved for DCOM REFIID riid, // Reference to the identifier of the interface LPVOID * ppv // Address of output variable that receives the interface pointer requested in riid ); -> 리턴된 class factory pointer로 component 생성 IClassFactory::CreateInstance Creates an uninitialized object. HRESULT CreateInstance( [in] IUnknown * pUnkOuter, // Pointer to whether object is or isn't part of an aggregate [in] REFIID riid, // Reference to the identifier of the interface [out] void ** ppvObject // Address of output variable that receives the interface pointer requested in riid ); -> CLSID 인자가 없음 : CoGetClassObject에 이미 CLSID 사용 CoCreateInstance()는 CoGetClassObject()를 사용하여 구현 /************************************ HRESULT CoCreateInstance(const CLSID& clsid, IUnknown* pUnknownOuter, DWORD dwClsContext, const IID& iid, void** ppv) { *ppv = NULL; IClassFactory* pIFactory = NULL; HRESULT hr = CoGetClassObject(clsid, dwClsContext, NULL, IID_IClassFactory, (void**)&pIFactory); if (SUCCEEDED(hr)) { hr = pIFactory->CreateInstance(pUnknownOuter, iid, ppv); pIFactory->Release(); } return hr; } ************************************/ 대부분의 경우 CoCreateInstance로 충분 CoGetClassObject를 써야만 하는 경우 ① IClassFactory가 아닌, 다른 interface로 생성하려는 경우 - IClassFactory2 ② 한번에 많은 component를 만드는 경우 DllGetClassObject() : component의 class factory를 생성 STDAPI DllGetClassObject(const CLSID& clsid, const IID& iid, void** ppv); Procedure ① Client : CoCreateInstance -> CoGetClassObject() ② DLL(Server) : DllGetClassObject() : creates Class Factory(CFactory) ③ return IClassFactory ④ calls IClassFactory::CreateInstance ⑤ component 생성(CA) 즉, Control Flow은 다음과 같다. ① Client가 CoCreateInstance 호출 CoCreateInstance는 CoGetClassObject로 구현 ② CoGetClassObject가 registry 검색 찾으면 DLL을 load하고 DllGetClassObject 호출 ③ DllGetClassObject가 class factory 생성 생성 후 class factory에게 IClassFactory interface를 요청하고 결과를 CoCreateInstance에게 전달 ④ CoCreateInstance가 IClassFactory::CreateInstance 호출 IX interface query도 함께 전달
7. Component Reuse : Containment and Aggregation
Implementation inheritance : COM에서는 지원하지 않음 Component Containment(포함) : COM의 implementation inheritance simulation 포함(Containment) 외부 컴포넌트가 내부 컴포넌트를 생성과 소멸까지 모두 관리 클라이언트에서 내부 컴포넌트로 접근하고자 하는 경우에도 직접 접근하지 못하고 외부 컴포넌트를 통해서 접근하여야 함 이미 만들어진 컴포넌트를 다시 개발할 필요 없이 재활용을 통하여 개발 주기를 단축시킬 수 있음 통합(Aggregation)
8. Servers in EXEs
1. LPC (Local Procedure Call) 동일 기계내에서 process들간의 통신 방법 : Remote Procedure Call(RPC)에 기반 2. Marshaling 다른 프로세스에 존재하는 COM 클라이언트와 서버가 데이터를 공유할 수 있는 메커니즘 3. Proxy Client와 동일 주소 공간을 가지며, Client측을 위한 데이터 송수신과 마샬링을 담당 4. Stub Server와 동일 주소 공간을 가지며, server측을 위한 데이터 송수신과 마샬링을 담당 5. IDL(Interface Description Language) /***************************** import! "unknwn.idl"; // Interface IX [ object, uuid(32bb8323-b41b-11cf-a6bb-0080c7b2d682), helpstring("IX Interface"), pointer_default(unique) ] interface IX : IUnknown { HRESULT FxStringIn([in, string] wchar_t* szIn); HRESULT FxStringOut([out, string] wchar_t** szOut); }; *****************************/ 1) In/Out parameter in : client가 제공 out : server가 제공 : 반드시 pointer 이어야 함 2) Strings data를 복사하려면 크기를 알아야 함 표준 convention은 UNICODE : wchar_t 3) import! Include와 유사 기능, but redefinition 문제 없음 4) size_is marshaling에서 data size를 요구 proxy code에 size 알려주는 역할을 함 in, in-out parameter에만 사용가능 5) Structures MIDL은 pointer가 가리키는 data를 marshaling 하기 위해 정확하게 알아야 함 void*는 parameter로 사용하면 안됨 generic interface pointer로는 IUnknown*을 사용 6) CoClass 새로운 COM 객체 타입의 선언을 정의 6. MIDL (Microsoft IDL) Compiler "midl obo.idl" 이 생성하는 파일들 OBO.H - IDL 내 모든 interface의 선언을 포함 /h switch로 변경 가능 OBO_I.C - IDL 에서 사용한 모든 GUID를 정의 /iid switch로 변경 가능 OBO_P.C - IDL 내 interface에 대한 proxy, stub을 implement /proxy switch로 변경 가능 DLLDATA.C - proxy, stub code를 갖는 DLL implementation /dlldata switch로 변경 가능 사용자가 작성할 파일 : obo.idl, obo.def /***************************** LIBRARY Proxy.dll DESCRIPTION ‘Proxy/Stub DLL’ EXPORTS DllGetClassObject @1 PRIVATE DllCanUnloadNow @2 PRIVATE GetProxyDllInfo @3 PRIVATE DllRegisterServer @4 PRIVATE DllUnregisterServer @5 PRIVATE *****************************/ DllCanUnloadNow : DLL이 해제될 수 있는지 GetProxyDllInfo : 해당 proxy/stub dll이 지원하는 class와 interface의 정보를 알아낼 수 있는 메커니즘 제공 Build options 1) RPC Run-Time library 추가: -> Link : Object/library modules: rpcrt4.lib 2) Self-Register/Unregister Proxy-Stub code를 위한 옵션 추가: "dlldata.c"의 C/C++ : Preprocessor definitions : REGISTER_PROXY_DLL HKEY_CLASSES_ROOT -> Interface -> ProxyStubClsid32
9. Dispatch Interfaces and Automation
1. Automation Automation Server IDispatch interface를 구현한 COM component Automation Controller IDispatch interface를 통해 automation server와 통신하는 COM client automation server가 제공하는 함수를 직접 호출 불가 : IDispatch의 member 함수를 통해 호출 2. IDispatch interface macro에서 함수 이름으로 함수 실행을 가능하게 함 GetIDsOfNames : 함수의 이름을 받아 dispatch ID (DISPID)를 return Invoke : DISPID값을 받아 해당 함수를 실행 : window procedure와 비슷 /***************************** interface IDispatch : IUnknown { HRESULT GetTypeInfoCount([out]UINT* pctinfo); HRESULT GetTypeInfo([in]UINT iTInfo, [in]LCID lcid, [out]ITypeInfo** ppTInfo); HRESULT GetIDsOfNames([in]const IID& riid, [in, size_is(cNames)]LPOLESTR* rgszNames, [in]UINT cNames, [in]LCID lcid, [out, size_is(cNames)]DISPID* rgDispId); HRESULT Invoke([in]DISPID dispIdMember, [in]const IID& riid, [in]LCID lcid, [in]WORD wFlags, [in, out]DISPPARAMS* pDispParams, [out]VARIANT* pVarResult, [out]EXCEPINFO* pExcepInfo, [out]UINT* puArgErr); }; *****************************/ 3. Dual Interfaces IDL /***************************** [ uuid(FB08FF23-1333-48C0-8B00-82342AA32803), oleautomation, dual ] Interface ISample : IDispatch { }; *****************************/ 4. Type Libraries language independent C++의 header file에 해당 component, interface, method, property, argument, structure에 대한 type 정보 제공 programmatic하게 access할 수 있는, IDL file의 compiled version 출처:http://obori.new21.net/vc/com/com_base.html