친구랑 바닷가 갔었습니다. 놀러 간 것은 아니고, 그냥 바람 쐬러 갔었습니다. 정확히 말하면 바닷가는 아니군요. 바다가 보이는 산(해발
513m)이라고 해야 정확하겠군요. 전망이 정말 좋습니다. 동해가 훤히 보이는 조그마한 절입니다.

차가 올라가기 때문에 올라간다고 힘들진 않습니다. 머리 식힐 때는 그곳 만한 곳이 없죠. 그리고 바닷가를 갈 수
있도록 등산로도 되어있습니다. 대신 내려갔다가 다시 올라와서 차를 타고 다시 내려가야 하는 이상한 코스죠. (2시간 정도)

여러분은 바람 쐬러 갈 때 주로 어디를 가는 지 궁금하군요.

친구랑 이런 얘기 저런 얘기 했습니다. 그 녀석은 전문대 교수자리 라도 하면서 박사 과정을 밟는 중입니다.
부러웠습니다. 그래도 목표가 있으니 말입니다.

프로그래머 과연 밝은 미래인지 다시 한번 생각해 봅니다.

그냥 열심히 해 보는 수 밖에요.

 

자, 그럼 시작하겠습니다.

 

저번 시간에는 실습을 했다. 두 수를 넣고 더하기를 해주는 COM 컴포넌트를 구현해봤다.

어떻게 실제로 해본사람이 있을까? 내가 생각 할 때는 전체의 5% 미만일 거라 생각한다. 대부분 아예 보질
않았거나 샘플을 다운 받아 놓았을 뿐일 거다. 다행이 다운 받은 소스를 한번이라도 훑어 본 사람은 그나마 관심이 조금은 있는 사람일 것이다.

그건 그렇고, 다시 인터페이스를 생각해보자. 우리는 인터페이스를 공부했지만, 아직 이것은 애매한 부분이
없지 않다.

인터페이스의 경우 바이너리로 배포된다고 했다. 우리가 COM을 사용할 때 인터페이스는 COM이 무엇을 제공하는지
알려 줄 뿐 어떻게 그 기능을 제공하는 지는 알 수가 없다. 실제, 이것이 COM의 목적일 수도 있다. 내부 구현은 어떻게 되어 있던 상관이
없다는 말이다.

일반적으로 이것은 What과 How에 비교된다. 클라이언트 입장에서는 What가 중요할 것이고 서버 입장에서는
How가 중요할 것이다. 이것은 각각의 개발자 역시 마찬가지다. COM 서버 개발자로서는 어떻게 클라이언트가 내부에서 어떻게 돌아가던지 상관없이
사용하기 쉽게 해줄까를 고민해야만 한다. 그래야만 COM의 진정한 의미가 있지 않나 생각한다.

실제 COM 개체 구현에 있어서 COM 서버의 경우 실행되는 위치에 따라 크게 3가지로 분류된다. 이것은
이미 CoCreateInstance 에서 조금 다루었다. 세 번째 인자를 보면 알 수 있다. 기억이 나지 않는 사람은 #2를 참조하면 될 것
같다. 여기서 좀더 자세히 알아보자.

COM 서버의 생성과정을 비교해보면 이 차이를 이해하기 쉬울 것 같다. 이것을 이해하면 역시 구현도 쉬워진다.
당연히 돌아가는 순서가 중요할 것이다.

그 첫번째가 in-process 서버였다. 이것은 우리가 실습했던 서버와 같다. 우선 이 in-process
서버의 경우는 DLL로 구성된다. 클라이언트에서 이 COM 서버를 로드하면 클라이언트와 같은 주소공간에 로드 되기 때문에 따로 복잡한 코드가
필요 없어지게 되고 이런 이유로 많은 COM 서버가 in-process 서버로 구현된다.(결국 이것이 개발이 가장 쉽다는 말이다.) 그럼 이제
어떻게 생성되는 지 알아보자.

먼저 레지스트리를 보면 HKEY_CLASSES_ROOT\CLSID를 열어보자. 수없이 많은 클래스 아이디가 등록
되어 있는 것을 볼 수 있을 것이다. (한번쯤은 다 봤을 것이다. 처음 이것을 접했을 때 황당했었다. 이게 모야? 뭐가 이렇게 많아? 알수 없는
숫자들, 나중에 이것들이 GUID인 것을 알게 됐을 때 아~~이거구나 하고 했던 기억이 난다.) 이것으로도 윈도우가 COM으로 완전히 도배가
되어 있다는 것을 알 수 있다. 몇 개인지 세어보고 싶은 충동이 엄청 일어났지만, 참았다. 완전히 노가다 같은 기분이 들 것 같아서 였다.
(남들도 ‘할 짓 디게 없나 보군’ 이럴 것 같아서 ㅜㅜ) 여기서 하나의 클래스 아이디를 다시 열어 보면 Inprocserver32 라는 키가
있다. 거기에 보면 파일 패스가 나오고 그 정보가 바로 COM서버의 위치이다. 말 그대로 in-process 라는 의미가 그대로 담겨져 있다.

전 장에서 COM 라이브러리의 역할에 대해서 알아 봤었다. 레지스트리에서 COM 서버 모듈이 있는 곳을 알아와
메모리에 로드 하는 역할도 포함 되어 있다는 것이 들었을 것이다. 이 역할을 하는 함수가 바로 CoGetClassObject COM 라이브러리
함수이다. CoGetClassObject라는 COM 라이브러리에서 그 위치를 얻어와 DLL을 로드한다.결과적으로 이 함수는 그 COM DLL의
내부에 구현된 DllGetClassObject라는 함수를 호출할 것이다. 여기서 클래스 팩토리를 생성하고 IClassFactory 인터페이스의
포인터를 리턴한다. 여기서 CoGetClassObject의 역할을 끝이 난다. (조금 밑으로 가다보면 그림이 하나 나올 것이다. 이 그림을 보면
대충 이해가 간다. 물론, 내가 그린 것이 아니다. 난 그럴 능력도 못 된다라는 것은 여러분이 더 잘 알 것이다. 나 역시
Copy&Paste 기법을 최고의 기법으로 알고 코딩하는 사람중에 한 명일 뿐이다.)

다음으로 호출되는  CoCreateInstance 함수가 하는 역할은 무엇일까? 이것도 했었다. 바로
IClassFactory인터페이스의 CreateInstance 메서드였다. 이 매서드를 통해 COM 개체의 인터페이스 포인터를 얻어올 수 있다.
그리고 클래스 팩토리는 제 역할이 끝났으니 Release 될 것이다. 그리고 나면 이제부터 클라이언트는 인터페이스 포인터를 가지게 됐으니,
맘대로 COM개체를 사용할 수 있다.

두번째는 로컬 서버이다. 이것은 우리가 아직 다루지 않은 것이다.

사실, 클라이언트 측면에서는 이 세가지가 별 차이가 없다. 이것이 COM이 입이 닳도록 자랑하는 위치 투명성이라는
장점인 것이다. 그냥 쓰면 된다. 문제는 COM 개체를 직접 구현할 때 문제가 된다. 이 3 가지 중 어느 것을 구현하느냐 따라서 구현 방법이
조금 달라지기 때문이다.

로컬 서버의 경우 in-process 에 비하면 복잡하지만, 원격서버에 비하면 또 쉬운 편이다. 이 로컬 서버의
경우는 EXE로 존재하며 in-process와 달리 클라이언트의 주소공간과 다른 곳에서 작동한다. 그럼 과정을 보자. 역시 레지스트리에서 정보를
읽어온다. In-process 서버의 경우 InProcserver32 키가 있는 것을 알 수 있었다. 하지만, 로컬 서버의 경우는
LocalServer32 라는 키가 존재한다. 여기서 파일 패스를 얻어온다. 역시 키 값에서 그 감이 바로 올 것이다. 꼭 레지스트리를 열어서
확인해 보기 바란다. (보면, 그래도 끝까지 확인 하지 않는 사람이 있다.)

In-process의 경우는 DllGetClassObject라는 함수를 통해 클래스팩토리를 제공했다. 하지만
EXE 파일의 경우는 특정 함수를 노출할 수 있는 방법이 없다.

그럼 어떻게 COM은 이 문제를 해결하였을까? 그것의 답은 바로 Class Table이다. 로컬 서버의 경우
자신이 실행되면서 클래스팩토리를 생성하고 이것을 클래스테이블에 등록한다. 이때 쓰이는 함수가 CoRegisterClassObject라는
함수이다. 이렇게 되고 나면 이제 CoGetClassObject를 사용할 수 있게 된다. 그 다음 부터는 in-process 서버와 크게 다르지
않다. 자세한건 나중에 상세히 다뤄보자.

마지막으로 원격 서버이다. 앞에서 잠시 언급한 적이 있는 DCOM(Distributed COM)이
영역이다. 왠지 분산이라고 말이 들어가면 어렵게 보인다. 원격서버의 경우 로컬서버와 비슷한 면이 많다. 왜냐하면 어차피 클라이언트와 다른
주소공간에 로드 되기 때문이다. COM에서 이 프로세스의 경계를 넘기 위해 많은 기술들을 제공한다. 이것은 상당히 어려운 기술에 속하고 이해하는
것도 어렵다. marshaling, Proxy, Stub등등의 기술들이다. 여기에 대해선 다음에 알아보도록 하자.

로컬서버와 가장 큰 차이점은 LPC(Local Procedure Call)을 이용하느냐 아니면 RPC(Remote
Procedure Call)을 사용하느냐의 차이일 것이다.

참고로, 원격서버의 경우 SCM(Service Control Manager)에서 클래스팩토리 개체를 얻을 수
있다.

결국 앞에서 말한 것들의 과정은 모두가 클래스팩토리 개체를 얻는 것이 목표였다. 여기까지만 해결되면 그 다음
부터는 거의 같은 과정이기 때문이다.

슬슬 머리가 아플 것이다. 나도 마찬가지다. 이것은 실제 겪어보지 않고는 결혼 생활이 어떻고 저떻고 말하는
것이 아무 의미가 없는 것과 마찬가지 인 것 같다. 실제로 한번 해보는 것이 가장 빠른 이해의 수단일 것이다.
자 정리해보자. 어렵게 하는 것은 나도 질색이다.

COM 서버의 종류는 그 위치에 따라 크게 3가지로 분류 된다고 하였다. 우리는 여기에 신경 쓸 필요가 없다.
COM이 위치 투명성이라는 것을 제공하기 때문이다. 하지만, 구현할 때 조금한 주의를 요하기 때문에 우리는 각각의 특징에 따라 몇몇 메서드만 더
제공해주면 된다. COM의 어려운 부분은 COM 라이브러리가 모든 뒷처리를 다 해주니 이쪽은 원리만 이해하고 넘어가면 될 것 같다.

잠시, 커피 좀 끓여 먹어야 겠다. 커피에 담배 한 모금 만한 것이 있을까?

 

 

(혹시나 해서 그러는데, 쓸데없는 걸로 지면 채우는 군 이렇게 생각하는 사람이 없지는 않을는지,
쩌비~~~~~~ 어쩌겠나? 내가 그렇게 하고 싶은걸.)
 그럼 앞에서 말한 이해를 돕기 위한 그림 감상 좀 하고 있길 바란다.
참고로 이 그림은 이재규씨의 홈페이지에서 가져온 것이다.

http://www.cfriend.co.kr/~leejaku/ 지금은 들어가지질 않는다.

혹시 바뀐 홈페이지 주소를 아시는 분은 리플을 달아 줬으면 고맙겠다. 내가 본 COM 사이트 중에서 가장 잘
구성되어 있는 홈페이지 중에 하나였던 걸로 기억한다.

1. in-process 서버

 

 

2. 로컬 서버

 

3. 원격 서버

 

그림을 보면 대충 이해가 될 것이다. 정말 잘 만든 것 같다.

이제 #5에서 실습한 내용을 지금까지 한 설명과 그림을 연상하면서 같이 되돌아 보도록 하자.
저 번 장에서 실습한 것은 in-process 서버 구현이었다.

앞에서 소스를 한번 보고 오라고 했다. 결국 그렇게 한 사람은 없을 거라 생각한다. 대충 돌아가는 것만 보고
왔다고 해도 난 만족이다.

먼저 우리는 프로젝트를 생성하고 IDL 파일을 만들어서 MIDL 로 컴파일을 했다. 결국 커다란 목적 중 하나는
타입라이브러리를 만드는 것이었다. 그럼 굳이 .h 헤더파일이 있어 C++에서 사용하는데 문제가 없을 텐데 타입라이브러리를 생성할까? 그 해답은
바로 특정언어에 종속되는 않는 COM의 특성 때문이기도 하다.

처음 IDL 파일을 컴파일 하니 파일이 5개 생겼다. Dlldata.c, iadd.h, iadd.tlb,
iadd_i.c, iadd_p.c 였다. 여기서 tlb 파일을 제외하고는 모두가 Proxy와 Stub DLL을 만들기 위한 것이다. 일단 여기는
신경 쓰지 말도록 하자. 우리가 지금 중요하게 생각 할 것은 타입라이브러리이다. Iadd.tlb가 타입라이브러리 파일인데 이것은 일반 텍스트
파일이 아닌 바이너리 파일이다. 아무래도 언어 독립적으로 만들기 위한 필수 조건이었을 것이다.

이 파일을 일반적으로 열면 알 수 없는 표현으로 나온다. 이 파일을 보기 위한 툴이 있으니 확인해보자. Tools
메뉴에 보면 OLE/COM Object Viewer이 있다. 이것을 선택하고 File 메뉴의 View TypeLib를 선택하고 파일을 열면
된다.

그러면 다음과 같이 나타난다.

 

// Generated .IDL file (by the OLE/COM Object Viewer)

//

// typelib filename: <could not determine filename>

 

[

 uuid(9D807A19-9A1A-4879-A7C0-6D3AFD04F7B8),

 version(0.0),

 helpstring(“\xFFFFFFB6?xFFFFFFC0\xFFFFFFCC\xFFFFFFBA\xFFFFFFEA\xFFFFFFB7\xFFFFFFAF\xFFFFFFB8\xFFFFFFAE\xFFFFFFBF\xFFFFFFA1
\xFFFFFFB4\xFFFFFFEB\xFFFFFFC7\xFFFFFFD1 “)

]

library IcoddyLib

{

   // TLib :     // TLib : OLE Automation :
{00020430-0000-0000-C000-000000000046}

   importlib(“stdole2.tlb”);

 

   // Forward declare all types defined in this typelib

   interface IAdd;

 

   [

     odl,

     uuid(12D90058-C2A5-4950-8355-1DC0189FFD0D),

     helpstring(“여기에 필요한 설명을 적는다.”)

   ]

   interface IAdd : IUnknown {

       HRESULT _stdcall SetFirstNum(long nFirst);

       HRESULT _stdcall SetSecondNum(long nSecond);

       HRESULT _stdcall GetSum([out, retval] long* pBuffer);

   };

};

이것도 제발 실제로 한번 해보길 바랄 뿐이다. 그렇게 중요한 것은 아니지만 말이다.

중요한 것은 한번 본 것은 시간이 오래 걸리지 않는다면 한번쯤 따라 해보라는 것이다. 그래야 머리에 오래 남는
다는 것은 여러분들이 더 잘 알 것이라 믿는다.

자세히 보면 OLE Automation 특성이 있는 것을 알 수 있다. 이 것은 멤버함수의 매개변수가
Variant 구조체에 정의된 데이터 타입만을 사용하겠다는 말이다. Variant 어디서 많이 듣던 말이다. 비주얼 베이직을 사용해본 사람은 다
알겠지만, VC++을 사용하는 사람은 생소할 수도 있겠다. 어쨌든, 이것도 당장 쓰이질 않으니 다음에 자세히 다루도록 하자. 실제로 쓰이기 위한
COM을 만들려면 어쩔 수 없이 해야 한다.

그리고, 저번장 마직막에 클라이언트 사용법을 언급하면서 import 를 사용한 것을 봤을 것이다.

#import “iadd.tlb”

이것은 타입라이브러리를 헤더파일로 변환시켜서 그 헤더파일을 소스에 포함시키는 일을 한다. 그래야 당연히
클라이언트에서 사용할 수 있지 않겠나? 그렇지 않고 어떻게 COM을 사용할까? 도저히 방법이 생각나질 않는군.

그 다음 소스들인 AddComObj.h, AddComObj.cpp, AddComObjFactory.h,
AddComObjFactory.cpp 파일들은 앞에서 모두 언급했던 내용이므로 이번에는 언급하지 않겠다. #2와 #3 #4에서 이것은 충분히
다루었다고 생각한다. 결국 여기는 IUnkonow과 IClassFactory만 잘 이해하고 있으면 충분히 분석이 가능하다.
그럼 Exports.cpp 파일과 Exports.def 파일에 대해 알아보자.

Exports.cpp 파일을 보면 DLLMain 함수를 볼 수 있다. 당연히 여기서 DLL의 초기화 코드를 수행할
것을 예상 할 수 있다. 여기서는 사용되는 dwReason 의 값만 이해하면 될 것 같다. DLL_PROCESS_ATTACH,
DLL_PROCESS_DETACH, DLL_THREAD_ATTACH, DLL_THREAD_DETACH 이 4가지 이다. 대충 이해가 가리라
생각한다. 프로세스가 시작할 때와 해제될 때, 그리고 스레드가 생성되고 종료할 때 적절한 코드를 switch case 문을 통해 구현하면 된다.

Exports.def 파일은 여러분들이 일반적으로 DLL을 작성해봤다면 다 알 것이다. 여기에 대해서는 언급하지
않겠다. 그냥 이것은 다른 EXE나 DLL에서 함수가 호출할 수 있도록 익스포트(Export) 해준다고 이해하면 될 것 같다.

중요한 것은 Exports.cpp와 Registery.cpp에서 구현한 DllCanUnloadNow,
DllGetClassObject, DllRegisterServer, DllUnregisterServer 함수들이다. 이것이 오늘의 핵심이라고
생각하면 된다. COM 서버를 구현하면서 가장 복잡한 부분이라고 할 수도 있고 어떻게 보면 가장 쉬운 부분이라고 할 수도 있다. 하지만, 이
함수들이 정작 중요한 이유는 COM 라이브러리와 직접 연관이 되어 있고 실제 COM 개체를 생성하고 해제하는 역할을 하기 때문이다.

이 함수들이 왜 필요할까? 적어도 내 생각엔 그것들이 COM 라이브러리와의 연결되었기 때문이라고 생각한다.

예를 들면, DllGetClassObject 함수의 경우는 CoGetClassObject와 직접적 연관이 있다.

즉, CoGetClassObject함수는 CoLoadLibrary 함수를 호출하여 해당 in-process 서버
DLL을 메모리에 로드하고, 그 DLL의 DllGetClassObject 함수를 호출하여 클아이언트가 요청한 COM 개체의 클래스팩토리 COM
개체를 생성한 다음 IClassFactory 인터페이스 포인터를 리턴하게 하는 것이다. (COM 개체의 사용은 일단 IClassFactory
인터페이스까지만 일단 게임오버인 것이다.)

내부구현은 간단하다. 앞에서 말한 대로 구현하면 된다. 클래스팩토리 COM 개체를 생성하고 그 인터페이스를 얻기
위해 QueryInterface 메서드를 호출한다. (소스를 보면 금방 이해할 수 있다.)

그리고 DllCanunloadNow 의 경우는 CoFreeUnusedLibraries 함수가 호출하는 함수이다.
즉, COM 라이브러리가 사용되지 않는 DLL을 메모리에서 언로드하고 해제하기 위해 호출하는 함수이다.

마지막으로 DllRegisterServer 함수와 DllUnregisterServer 함수는 뭘 하는 놈일까?

이것은 간단하다.

우리가 COM을 등록하면서 regsvr32  iaddcomobj.dll을 사용할 때
DllRegisterServer 가 호출되고, 반대로 regsvr32 /u iaddcomobj.dll 을 사용해서 등록을 해제할 때 호출되는
함수가 바로 DllUnregisterServer 함수인 것이다.

자 어려운가? 하지만, 걱정할 것 없다. 구현이 어렵다고 해서 걱정할 필요가 없는 것이다. 여기 부분은 대부분이
공통으로 사용하는 부분이다. 따라서 잘된 소스 하나만 있으면 Copy&Paste만 잘하면 될 것 같다. 왜 사용해야 하는지만 이해하자.

 

개인적으로, #1에서인가? 내가 COM을 가장 비슷한 것이 무엇인가 언급한적이 있다. 바로 일반 DLL과 비교를
했었다. 이번 실습을 통해 일반 DLL과의 차이점을 느꼈는가? (아직 DLL도 안 만들어 봤다구? 헐~~ 시간나면 해보길 바란다. 여기서
그것까지 다루기는 조금 벅차다.) 차이점이라고는 COM 라이브러리와 연동되는 추가적인 작업이 전부라는 것이다. 즉 COM 라이브러리에서 해주는
것들이 COM의 특징인 위치 투명성 어쩌고 저쩌고, 언어 독립적이고 이진형태여야 하고 버전 호환성을 제공해야 하네 마네 하는 것들이다. 결국
앞에서 IUnknown과 IClassFactory들을 다루었고 이밖에 오늘 한 Dll… 어쩌고저쩌고 하는 함수들을 다루었다. 물론, 이게 전부는
아니다. 하지만, 결국은 COM의 특징이라고 내세우는 것들이 DLL을 변형한 것에 지나지 않는다는 것이다. 물론, 개인적인 생각이다. 절대
아니라고 생각하는 사람도 있을 것이다. 그런 사람들은 이유를 리플에 달아주면 고맙겠다. 나 역시 개념 정리가 필요하니깐.(그럼 EXE는 뭐냐구?
당신은 EXE와 DLL의 차이를 설명할 수 있는가?)

대충 소스의 흐름을 알아봤다.

내가 COM을 대충 만들어보면서 느낀 점은 이렇다. 대부분의 코드들이 이미 이렇게 해야 한다고 Guide
Line이 정해져 있다. 따라서 우리는 서비스에만 중점적으로 개발역량을 집중하면 된다. 그때그때 필요에 따라 in-process 서버로 할
것인지, 로컬로 할 것인지, 원격으로 할 것인지 정확하게 판단 할 수 있는 능력을 키우고, 전체적인 흐름을 바로 파악할 수 있는 넓은 눈이
필요한 것이다.

여기까지 왔다. 이제는 점점 어려워진다. 지금도 어렵다고 생각하는 사람이 많을 것이다. 특히나, COM
자체를 말로만 듣다가 직접 해보는 사람은 더할 것 같다. 처음 내가 겪었던 것 처럼 말이다. 내가 더 이상 해줄 것은 없다. 물론, 이 경험담은
계속 되겠지만, 더 이상 내가 이해를 얼마나 더 쉽게 해줄 지는 자신이 없다.
점점 말투가 일반적인 책과 다를 바가 없어진다. 되도록 재미있게 하고 싶지만, 내용이 어려워지고 많아
질수록 나의 부족함을 느낄 뿐이다.

지금까지 내 글을 읽어준 모든 분께 감사를 드린다.

 

이제 스스로 파 볼 수밖에 없다. 하나하나 만들어보고 고쳐보고 결국은 COM+ 까지 확장해 가야 한다. 아니,
.NET Remoting 까지 확장하면 더 좋을 것이다.

이제 진실을 말할 때가 된 것 같다. 사실 일반 회사에서 COM 개발 자체가 그다지 필요하지 않다. 잘 쓸 줄만
알아도 별 지장이 없을 것이다. 겨우 몇몇 회사만이 이곳에 투자를 한다. 하지만, 중요한 것은 아직은 COM 개발자가 그렇게 많지가 않다는
것이다. 공급이 수요를 따라가지 못한다는 말이다. 필요로 하는 곳은 적지만, 그만큼 확실히 해두면 일자리는 많다고 생각한다. 다들 취직보단
창업을 생각하고 있을라나?

 

어제 하루종일 벌초를 했더니 엄청 피곤하다. 온 팔뚝에 풀독이 올라 간지럽다. 그나마 덥지 않았던 것이
다행이쥐~. 추석도 가까워 오고, 돈 나갈 일이 꿈같군.. 어디서 돈벼락 안떨어지나 몰라.

Leave a Reply

Your email address will not be published. Required fields are marked *