*이 포스팅은 개인 학습을 위해 내용을 정리한 것이 목적입니다.
클라이언트에서 객체를 직접 생성하고, delete를 해야한다 -> DLL개체에 대해 의존적이다.
->클래스 팩토리를 이용하여 해결한다.
1. 레지스트리
- 클라이언트에서 직접적으로 라이브러리 로딩 -> 이름충돌의 문제
1) 윈도우 레지스트리
- 컴포넌트가 윈도우 레지스트리에 CLSID를 인덱스로 하여 DLL파일 이름을 기록해 두었음.
- COM관련 레지스트리
-> HKEY_CLASSES_ROOT\\CLSID 내에 있음.
- 레지스트리에서 사용하는 다른 서버키
서버 키 이름 |
전체 이름 |
의 미 |
ProgID |
Program Identifier |
컴포넌트 구분자 |
AppID |
Application ID |
원격 서버를 가르키는 APPID |
CATID |
Component Categories ID |
개별 컴포넌트 카테고리 |
Interfaces |
|
인터페이스의 ID |
Licenses |
|
COM을 사용할 수 있게 승인된 라이센스 |
TypeLib |
|
인터페이스 멤버함수에서 사용되는 변수 |
ProgID은 스트링으로 CLSID와 매핑. VB에서는 ProgID를 사용하여 COM과 연결. ProgID를 이용한 검색
은 시간이 많이 소요된다. 그래서 CLSID 바로아래 ProgID키값을 두고, 또한, ProgID의 서브키로 CLSID
를 둔다.
* CLSIDFromProgID("ProgID", &CLSID) : PRogID에서 CLSID 얻어오는 함수
2. 레지스트리 등록
DLL에서 레지스트리 등록을 원할 경우 두 함수가 포함되어 있어야 한다.
STDAPI DllRegisterServer() STDAPI DllUnregisterServer() |
사용자가 직접 레지스트리를 편집해야 한다.
1) 레지스트리 등록 방법
HKEY_CLASSES_ROOT\CLSID 키의 서브 키에 포함될 3가지
i) InProcServer32 : DLL의 경로에 관한 정보를 기록
ii) ProgID : CLSID대신 프로그램의 이름을 나타내는 별명이 들어감
iii) VersionIndependentProgID : 간단한 별명이 들어감
3. 클라이언트에서 DLL로 접속하기
클라이언트에서 DLL로딩시, LoadLibrary보다 더 유용한 방법이 있다. (단! 레지스트리에 등록되야만함)
CoGetClassObject()함수.
1) CoGetClassObject함수 사용하기
STDAPI CoGetClassObject( REFCLSID rclsid, //만들려는 Class의 ID DWORD dwClsContext, //COM서버 형태 COSERVERINFO *pServerInfo, //원격서버인경우 서버정보 REFIID riid, //얻고자 하는 인터페이스 LPVOID *ppv //리턴되는 인터페이스 포인터 ); |
레지스트리에서 rclsid에 해당하는 값을 찾고, 그 값에 포함된 DLL경로를 가지고 로딩함.
그 후에, DllGetClassObject()함수를 호출한다. (rclsid, riid, ppResult)가 인자. 단, 이경우 DLL에서
DllGetClassObject()함수를 익스포트 해주어야 함.
*클라이언트에서 CoGetClassOjbect를 호출하면 HRESULT가 0x800401f0이 발생하는 경우가 있다. 이 경우, DLL을 호출하기 전에 CoInitialize(NULL);을 호출하였는지 확인하자. 안하면 저 오류 발생과 함께, NULL을 받아올 것이다.
물론, CoInitialize(NULL);이 호출되었으면 마지막에 CoUninitialize();는 호출해주어야 한다.
2) DLL서버 만들기 (DLL모듈)
- 이전에 했던 CreateInstance()에서 구현한내용을 DllGetClassObject()에 구현해 주어야 한다. 클라이언
트에서 CoGetClassObject()를 이용하기 때문이다. (저 함수 호출되면 알아서 DllGetClassObject호출됨)
4. 클래스팩토리
위에서 구현한 DllGetClassObject()
STDAPI DllGetClassObject(const CLSID& clsid, const IID& iid, void** ppv) { *ppv = static_cast<IFunc1*>( new CMyClass); reinterpret_cast<IUnknown*>(*ppv)->AddRef(); //IUnknown으로 캐스팅 가능함을 알기에 reinterpret_cast를 사용하였다. return S_OK; } |
위 코드에서 보면, 항상 IUnknown만을 반환하도록 만들었다. 여러 인터페이스에 대해 반환하기 위해 QueryInterface()함수를 이용해야 하기에, 이를 구현해야 한다. 예상되는 다음 코드는 아래와 같다.
STDAPI DllGetClassObject(const CLSID& clsid, const IID& iid, void** ppv){ ... pSth->QueryInterface(iid, ppv); pSth->Release(); return S_OK; } |
위 pSth클래스의 역할은 CMyClass를 새롭게 생성하여야 하고, 컴포넌트에 포함된 인터페이스를 질의받으면 해당 인터페이스를 반환해야한다. -> 클래스팩토리
*클래스 팩토리 : 특정한 클래스의 인스턴스를 만들 수 있는 방법을 알고 있는 객체. MS에서는 IClassFactory라는 인터페이스를 제공하고 있다.
클라이언트는 클래스팩토리에 객체생성을 요청하고 그를 이용하면 된다. 객체생성/삭제와는 무관해짐.
대신 클래스 팩토리가 그 관리를 담당한다.
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; } |
- QueryInterface, AddRef, Release는 앞에서 본 내용과 동일하다. CreateInstance에서 CMyClass를 생
성해 주어야 한다.
- DllGetClassObject에서는 클래스팩토리를 생성한다. 그리고 클라이언트에서는 IClassFactory를 구해서
거기서 인터페이스를 가져온다.
1) CoCreateInstance()
클라이언트 프로그램 내에서 CoGetClassObject()와 pIFactory->CreateInstance()를 구현해야한다. 그러나 MFC에서는 이를 지원하기 위한 API가 CoCreateInstance이다.
STDAPI CoCreateInstance(REFCLSID rclsid, LPUNKNOWN pUnkOuter, DWORD dwClsContext, REFIID riid, LPVOID* ppv); |
- rclsid : 생성하고자 하는 컴포넌트의 클래스ID. 레지스트리 등록되어 있어야 함
- pUnkOuter : IUnknown의 포인터
- dwClsContext : 생성하고자 하는 컴포넌트의 동작 환경
환경변수 |
설명 |
CLSCTX_INPROC_SERVER |
클라이언트와 같은 프로세스 내에서 COM이 실행됨. COM은 DLL형태 |
CLSCTX_INPROC_HANDLER |
클라이언트는 인프로세스 핸들러를 사용한다. *인프로세스핸들러 : 컴포넌트의 한 부분을 인프로세스 컴포넌트로 구현한 것, 다른부분은 지역 또는 원격으로 동작함 |
CLSCTX_LOCAL_SERVER |
컴포넌트와 클라이언트가 한 PC, 다른 프로세스에서 구현됨. 컴포넌트는 EXE여야 한다. |
CLSCTX_REMOTE_SERVER |
컴포넌트와 클라이언트가 다른 PC에서 구현되어 있음. |
CLSCTX_INPROC |
CLSCTX_INPROC_SERVER | CLSCTX_INPROC_HANDLER |
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 : 얻고자 하는 컴포넌트의 인터페이스
- ppv : 객체 인터페이스를 받을 수 있는 포인터
- CoCreateInstance()함수는 CoGetClassObject()함수 호출과 클래스팩토리객체를 이용하여 호출한 CreateInstance()함수로 구성되어있다. 호출 과정은 아래와 같다.
i) 클라이언트가 CoCreateInstance()를 호출해 COM객체 생성을 시도하면, 함수 내부에서는 CoGetClassObject()라는 COM라이브러리 함수를 호출함. ii) CoGetClassObject()함수는 클라이언트가 HKEY_CLASSES_ROOT\CLSID서브키에 rclsid가 등록되어 있는지를 조사하여 있을 경우에는 COM컴포넌트를 메모리에 로드한다. COM의 경우 InProcess서버는 InProcServer32에 등록되어 있고, 로컬서버는 LocalServer32서브키에 등록되어 있음. iii) CoGetClassObject()는 컴포넌트를 메모리에 로드한 후, 클래스팩토리객체를 생성하려고 함 iv) CoGetClassObject()는 클래스 팩토리 객체 생성을 위해 Dll서버에 구현된 DllGetClassObject() 호출 v) 서버에 구현된 DllGetClassObject()는 클래스팩토리객체를 생성하고, 객체에 연결된 IClassFactory의 인터페이스 주소를 반환 vi) 반환된 주소는 CoGetClassObject()를 거쳐 CoCreateInstance()함수로 반환됨 vii) CoCreateInstance()함수에서는 IClassFactory인터페이스 주소를 사용해 클래스팩토리 객체가 제공하는 CreateInstance()를 호출 viii) 서비스 요청을 받은 클래스 객체는 자신이 관리하는 COM클래스로부터 객체를 생성 ix) COM객체 생성과 함꼐 클래스팩토리객체의 CreateInstance()는 QueryInterface()를 사용해 생성 된 COM객체에 연결된 클라이언트가 요구한 COM객체의 인터페이스 주소를 반환 |
a) LockServer
InProcessServer의 경우 DLL의 참조 개수를 클래스 팩토리의 수로서 증가/감소가 가능하지만 로컬서버
의 경우 이러한 클래스 팩토리를 이용할 수 없다. -> 클래스 팩토리의 개수가 메모리에 올라와 있는 DLL
의 개수를 보장하지 못함.
이러한 문제를 해결하기 위해 LockServer를 사용한다.
HRESULT __stdcall CFactory::LockServer(BOOL bLock){ if( bLock == TRUE) { InterlockedIncrement(&g_cServerLocks); } else { InterlockedDecrement(&g_cServerLocks); } } |
b) 클라이언트에서 호출 방법
클라이언트는 CoCreateInstance()를 통해 DLL내에서 클래스 팩토리를 생성하고, 생성된 클래스 팩토리
가 COM컴포넌트를 생성하고, 그 후 클라이언트가 COM컴포넌트를 사용할 수 있음. (위의
CoCreateInstance()호출과정 참조)
c) 클래스팩토리에 대해서
- 클래스팩토리는 식별자가 없다. 클래스 팩토리는 같은 종류의 COM객체만 생성하므로, COM클래스의
식별자로 구분한다. 하나의 클래스 팩토리는 단 하나의 COM클래스에만 대응함
- 클래스 팩토리도 COM객체. IUnknown인터페이스를 가지고 있다. 클래스팩토리가 제공하는 서비스들
은 IClassFactory라는 표준 인터페이스에서 제공됨
- 클래스 팩토리는 IClassFactory와 IClassFactory2의 인터페이스를 가진다. IClassFactory는 COM컴포
넌트를 생성하는 함수와 서버의 생명주기를 관리하는 함수를 가지고 있다. IClassFactory2는
IClassFactory의 기능에 라이센스에 대한 기능이 추가된 것임
- 어떤 COM객체의 클래스팩토리를 얻는 API는 CoGetClassObject()임.
2) COM서버 만들기
i) 프로젝트 만들기
ii) DllMain()함수 구현
- DLL_PROCESS_ATTACH에서 로드된 모듈의 핸들이 있다. 이를 기억하자
iii) DllRegisterServer() 호출하기
- 레지스터에 기입할 내용 작성
iv) DllUnRegisterServer() 호출하기
- 레지스터에서 삭제할 내용 작성
v) DllGetClassObject() 호출하기
- 클래스팩토리 생성후, 해당 인터페이스 포인터 반환
vi) QueryInterface() 구현
- Interface ID를 통해 인터페이스 포인터 전달하는 함수 구현
vii) CreateInstance() 구현
- 클라이언트에서 클래스팩토리에 대한 포인터를 획득하면 CreateInstance()를 호출한다. 이때 컴포넌트
의 클래스를 생성하여주고, 이 interface ID를 가지고 QueryInterface()를 수행한다.
viii) 클래스팩토리 정의파일 구현
ix) 모듈정의파일 구현
- 클라이언트에서 접근할 함수들을 export해준다.
3) 클라이언트 만들기
i) 프로젝트 생성
ii) MyCreateInstance() 구현
- CoGetClassObject()를 이용해 클래스 팩토리를 얻어오고, 획득한 pIFactory를 이용하여 실제적인 클
래스 객체를 생성하는 CreateInstance()를 호출.
iii) MyCreateInstance() 호출
iv) Main() 구현
v) 프로젝트 빌드
- COM컴포넌트를 레지스트리에 등록하고 실행하자.
내용 출처 : COM Bible
댓글