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;
}
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