인-프로세스서버 COM컴포넌트
- 같은 클라이언트 프로그램 안에서 COM컴포넌트가 생성되고 실행되는 것.
4.1 Win32 DLL의 이해
- 클라이언트 안에서 인-프로세스 서버 COM 컴포넌트가 실행되기 위해서는 Win32DLL이어야함
4.1.1 프로세스와 DLL
- 윈도우 운영체제의 프로세스는 4GB의 고유의 주소영역을 가짐. OS의 메모리관리자는 페이징을 이용
해 프로세스가 소유한 물리적인 메모리에 대해 4GB크기의 연속적인 리니어 주소 어딘가에 매핑되게 하
여 다른 프로세스의 메모리를 알 수 없게 한다.
> 하나의 프로세스에 문제가 생겨도 다른 프로세스는 영향받지 않음.
> DLL의 물리적 메모리 영역은 DLL을 로드한 어플리케이션의 프로세스 주소에 매핑된다.
4.1.2 DLLMain함수
- DLL이 메모리에 처음 로드되면 DllMain()이라는 진입점 함수가 호출된다. 모든 DLL이 제공해야 함
BOOL APIENTRY DllMain(HINSTANCE hModule, DWORD dwReason, LPVOID lpReserved){ switch(dwReason){ case DLL_PROCESS_ATTACH: break; //DLL이 프로세스 주소 영역에 매핑됨 case DLL_PROCESS_DETACH: break; //DLL이 프로세스 주소 영역에서 매핑 해제됨 case DLL_THREAD_ATTACH: break; //스레드 생성됨 case DLL_THREAD_DETACH: break; //스레드 종료됨 } } |
> 스레드 생성/종료, DLL로드/언로드에서 호출된다.
(참조 : http://msdn.microsoft.com/ko-kr/library/vstudio/ms682583(v=vs.110).aspx )
4.1.3 DLL익스포트 함수
- DLL을 사용하는 목적은 다른 어플리케이션이나 DLL에서 해당 DLL의 함수를 호출할 수 있게 제공하는
것이다. 이를 위해 DLL은 자신의 함수를 export해야 한다.
- DLL에서 함수 export하는 두 가지 방법
1) __declspec(dllexport)키워드의 사용
__declspec: 주어진 형식의 인스턴스가 MS전용 저장소 클래스 특성과 함꼐 저장하도록 지정하는 키워드 (참조: http://msdn.microsoft.com/ko-kr/library/dabb5z75.aspx) |
dllimport / dllexport키워드 : 함수, 데이터 및 개체를 DLL에 내보내거나 DLL에서 가져올 수 있음 (참조 : http://msdn.microsoft.com/ko-kr/library/3y1sfaz2.aspx ) |
2) 모듈정의파일( .def파일)에 export함수에 대한 정보를 기입
> 인-프로세스 서버 COM컴포넌트를 구현할 때에는 모듈정의 파일을 사용하는것이 좋음
예)
DLL프로젝트를 생성 후, 새항목 추가를 통해 def파일을 추가한다.
그리고 다음과 같이 내용을 채운다.
LIBRARY "inproccom.dll" EXPORTS DllCanUnloadNow PRIVATE DllGetClassObject PRIVATE DllRegisterServer PRIVATE DllUnregisterServer PRIVATE |
위에서 파란색으로 표기한 부분이 추가한 내용이다.
4.2 인-프로세스 서버 COM 컴포넌트 익스포트 함수
- 다시한번 짚어보는 COM컴포넌트의 생성과정
1) CoCreateInstance()호출 2) CoCreateInstance()내부에서 CoGetClassObject()를 호출하여 COM객체 정보 검색 3) CoGetClassObject()에서 CoLoadLibrary()로 메모리에 로드 4) CoGetClassObject()는 DLL의 DllGetClassObject()를 호출한다. 5) DllGetClassObject()에서는 인수로 넘어온 CLSID에 대응하는 클래스팩토리 COM객 체를 생성한 후 IClassFactory인터페이스를 구해 CoGetClassObjet()로 반환 6) CoGetClassObject()는 5)에서 받아온 IClassFactory를 CoCreateInstance()로 반 환함 7) CoCreateInstance()에서는 받아온 IClassFactory의 CreateInstance()를 호출하여 COM객체 생성 |
4.2.1 DllGetClassObject함수
- COM라이브러리에서 DLL을 메모리에 로드한 후, DLL에 DllGetClassObject함수를 호출한다. 원형은
STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID* ppv); |
> rclsid : CLSID레퍼런스타입. CoCreateInstance()의 첫 번째 인수에 지정된 CLSID가 전달됨.
> riid : rclsid로 지정된 CLSID로 식별되는 COM객체를 생성할 IClassFactory의 IID
> ppv : IClassFactory포인터를 저장할 포인터변수의 주소값
- DllGetClassObject()익스포트 함수에서는 클라이언트가 생성하고자 하는 COM객체, rclsid인수에 지정
된 COM객체에 대응되는 클래스팩토리 COM객체의 인스턴스를 생성하고, 해당 객체로부터 riid에 지정
된 IClassFactory 인터페이스 포인터를 구해 ppv인수에 넘겨주면 된다. 코드는 다음과 같다.
4.2.2 DllCanUnloadNow함수
- CoFreeUnusedLibraries() : COM라이브러리가 호출하는 함수. 사용되지 않는 COM컴포넌트 DLL을
메모리에서 언로드하여 해제하기 위함. DLL은 스스로 메모리에서 해제될 수 없다.
- DllCanUnloadNow() : COM라이브러리에서 DLL참조여부를 확인하기 위해 사용. S_OK리턴시 언로드
STDAPI DllCanUnloadNow(); |
- 이 export함수에서 자신의 COM컴포넌트가 클라이언트에 서비스중인지 알기위해 COM객체 카운터를
둔다. (전역으로)
LONG g_cObjects= 0; //COM객체 카운터 extern "C" STDAPI DllCanUnloadNow(void){ if(g_cObjects == 0){ return S_OK; } return FALSE; } |
> 이와 같이 COM객체 카운터를 둘 경우 이 카운터를 관리하는 코드를 추가해야 한다.
HRESULT STDMETHODCALLTYPE CHelloFactory::CreateInstance( LPUNKNOWN lpOuter, REFIID riid, LPVOID* ppv){ ... pHello = new pHello; if( pHello != NULL){ ++g_cObjects; hr = pHello->QueryInterface(riid, ppv); if( FAILED(hr)){ --g_cObjects; delete pHello; } } ... }
ULONG STDMETHODCALLTYPE CHello::Release(void){ if( --m_cRef == 0){ --g_cObject; delete this; } return m_ref; } |
4.2.3 DllRegisterServer 함수
- 클라이언트에서 COM컴포넌트 사용을 위해서 해당 컴포넌트는 레지스트리에 등록되어 있어야 함.
- DllRegisterServer()에서는 COM컴포넌트에 구현되어 있는 COM객체에 대한 정보를 레지스트리에 기
록하면 됨
> 기록할 정보는 다음과 같다 (위치는 HKCR\CLSID)
1) InProcServer32 : DLL의 경로에 관한 정보
2) ProgID : CLSID대신 프로그램이름을 나타낼 별칭
3) VersionIndependentProgID : 간단한 별칭이 들어감
(참조 : http://gomnezip.tistory.com/328)
- 이 과정 지원을 위해 3개의 Win32API함수를 사용해야 함.
1) RegCreateKeyEx() : 레지스트리 키를 생성 (존재할 경우 해당 키 open)
2) RegSetValueEx() : 레지스트리에 값 설정
3) RegCloseKey() : 레지스트리에 값 기록 후 키를 닫는 함수
위 DllRegisterServer를 구현한 예제
4.2.4 DllUnregisterServer함수
- regsvr32 /u 옵션으로 COM컴포넌트가 레지스트리에서 제거될 때 호출되는 함수. DllRegister에서 레지스트리에 키 값을 기록하는 과정이었다면, 이 함수에서 수행할 역할은 그 반대임. 키를 제거하면 됨.
* COM객체를 구현한다.
- CLSID는 GUIDGEN 으로 생성하여준다. idl파일도 만들어서 midl로 컴파일도 수행하여주고, 클래스 팩토리도 구현하여 둔다.
- 만들어진 DLL에 추가로 작업해 줄 함수는 다음 4개의 함수를 export하는것.
DllRegisterServer : COM객체의 정보를 DLL등록시 레지스트리에 기록하는 함수
DllUnregisterServer : COM객체가 등록 해제될 때, 기록했던 레지스트리 값을 지우는 기능 구현
DllGetClassObject : 클래스 팩토리를 반환하여 준다.
DllCanUnloadNow : 현재 COM컴포넌트가 참조중인지 여부를 판단하여 메모리 해제 여부를 반환
댓글