본문 바로가기

ATL / COM

COM이야기

COM 경험담.. Programing 일반 ::

2006/09/12 08:31

복사 http://blog.naver.com/ddingdol33/140028668838

나의 COM(Component Object Model) 경험담 #1


우선 앞으로의 모든 내용은 반말로 나갑니다. 기분 나쁘시면 읽지 않으셔도 됩니다. 그리고 어떠한 질문도 받지 않습니다. 답할 자신도 없습니다. 앞으로 몇 차례가 더 나갈지는 모르겠습니다. 그냥 제가 (X)리는 데로 할 생각입니다. 그럼 읽지 않으실 분들은 지금 마이크로소프트의 워드 창을 닫으시기 바랍니다. (--;;) 여기저기서 워드 닫는 소리가 들리는 군요
. ^^;
그리고, 이 문서의 내용은 언제든지 변경될 수 있음을 알려드립니다
.

경험담 #1 com 의 개념 잡기 입니다. 최대한 쉬운 말로 나갑니다. 기술적 용어를 최대한 배제하겠습니다. 저 같은 분들을 위해서 입니다
.
그럼 시작하겠습니다
.

------------------------------------------------------------------

내가 COM을 처음 접했던 것이 언제인지 기억이 나질 않는다. 아마도 꽤 오래된 것 같다
.
그 당시 기억을 떠올리면
….
마이크로소프트에서는 앞으로 모든 프로그래밍은 COM으로 통합 될 듯이 광고를 했다
.
난 여기에 현혹 되었고그래 내가 앞으로 가야 할 길은 이것이다라며 공부를 시작하였다. (왠지 있어보일 것 같은 생각도 있었다
.)
하지만, C++, MFC 조금 할 줄 안다고 해결될 문제가 아니었던 것을 그 당시에는 전혀 몰랐었다. 마치 사막에서 우물을 파는 듯한 느낌(이 보다 더한 느낌이었을지도 모른다.)이었고 도대체가 진도가 나아가질 않았다
.
그리고 COM의 수박 겉 핥기만 하고 나서 잠시 그 쪽을 접었다
.
한참 시간이 흘렀다
.
그리고 다시 COM을 쓸 일이 생겼다. 다시 하고 싶지 않았지만 어쩔 수 없이 해야 했다. 그러면서 자연히 과거의 기억이 떠올랐다. 그 당시 왜 그렇게 COM 공부가 어려웠을까 생각해 보았다. 정답은 너무 세부적인 부분에 신경을 쓴 것이 실패 요인이었다. COM을 써야 하는지 이유를 몰랐던 것이다. 그리고 COM 개념을 확실히 잡고 들어가지 않은 점도 간과 할 수 없는 실수였다
.

IUnknown
이 어떻고 QueryInterface()가 어떻고 Proxy는 머고는 전혀 중요하지 않았던 것이다
.
이것은 MFC 프로그래밍을 할 때 WinMain()을 전혀 모르고 프로그래밍을 해도 문제가 없는 것과 같은 것이었다.(여기서 WinMain()을 꼭 알아야 한다고 우긴다면 난 할말이 없다
.)

마이크로소프트의 그 당시 광고처럼 COM은 현재 거의 모든 마이크로소프트의 제품들의 근간 기술이 되었다. ‘.NET’ 이후로 COM이 더 이상 업그레이드가 없을 지라도 COM이 없는 Windows는 상상도 못할 상황이 되어 버렸다
.

그렇다면 이제 본격적으로 COM 이 놈이 어떤 놈인지 알아봐야 한다
.
코딩은 나중에 하겠다. 처음부터 코드가 들어가면 이 글을 읽다가 바로 나가는 사람이 있을

것 같아서 처음에는 간단히 개념만 설명하겠다. 함수만 들어가도 머리를 싸 메는 사람도 있

을 것 같아서 함수도 언급하지 않겠다. 하지만 경험담 #2 부터는 단단히 맘 먹어야 할 것
이다.

먼저 이야기 해 둘 것이 있다. 헷갈릴 까봐 미리 말해둔다. COM 역시 대부분 .DLL로 만들어진다. 따라서 앞으로 언급될 DLL COM이 아닌 일반 DLL이다. 그리고 COM COM으로 언급할 것이다
.
현재 COM과 가장 비슷하게 설명 될 수 있는 놈이 무엇일까? 바로 DLL이다. 하지만, 분명한 차이점이 있다. COM DLL과 같은 놈이었다면 COL(Component Object Library)이 되었을 것이다. 아니, 아예 나오지 않았겠지(그래 분명히 나오지 않았을 것이다
.)
그럼 라이브러리와 모듈의 차이는 무엇일까? 바로 독립성이다. DLL이 실행파일에 의존한다는 것은 DLL의 가장 큰 취약점이라고 할 수 있다. DLL을 업그레이드 한다는 말은 실행파일을 다시 그 DLL을 가지고 다시 컴파일 해야 한다는 말이다. 물론 DLL 하나에 실행파일 하나라면 굳이 이럴 필요가 없을지도 모른다. 하지만 DLL의 목적중의 하나가 공유가 아니었던가. 나 혼자 그 DLL을 쓰는 것이 아니다
.

옛날 도스시절 우리는 많은 라이브러리를 사용했었다. 하지만, 이것은 정적 라이브러리 였다. 그리고 응용 능력이 좋은 마이크로소프트는 동적 라이브러리라며 COM을 광고하듯이 마치 신기술인양 DLL을 광고했던 기억이 난다. 이 동적 라이브러리는 실행 파일들의 공통된 부분을 이진형태(exe파일 같은)로 떼어내서 실행 파일들 끼리우리 그 부분은 너랑 나랑 똑같이 필요하니깐 사이 좋게 나눠 쓰자하는 것과 같다. 정적 라이브러리가 컴파일 시 실행파일에 포함되는 것과 비교한다면 엄청난 발전이라 할 수 있다
.

모든 제품이 그러하겠지만, 처음 나올 때는 생각 못했던 문제점이 발생하기 시작한다. DLL에서 바로 앞에서 언급한 업그레이드 문제가 그것이다. 왜 문제일까
?
잘 생각해보자. 여기서 부터는 조금 머리가 아파올 것이다. 가장 간단하게 vc++6.0으로 프로그래밍을 할 때 컴파일을 하면 기본적으로 mfc42.dll과 연결된다. 물론 static으로 컴파일한다면 DLL을 신경 쓸 필요가 전혀 없어지겠지만, 이것은 모든 필요한 DLL들이 실행파일 모듈에 포함되기 때문에 하드디스크와 메모리 낭비를 가져오게 될 것이다. 자 그럼 이제 .net이 나온 시점에서 생각해보자 vc++.net mfc70.dll을 사용한다. 그렇다면 기존에 만든 프로그램들은 mfc70.dll을 사용할까? 그렇지가 않다 기존 프로그램들은 mfc42.dll 을 그대로 사용하고 vc++.net으로 만든 프로그램들만 mfc70.dll을 사용한다. 결국 프로그램이 새로 나올 때마다 윈도우시스템 디렉터리에는 DLL들이 쌓이게 될 것이고 디스크 메모리는 어쩔 수 없이 낭비 되어 버린다. mfc70.dll을 사용하고 싶다면 기존 프로그램을 새로 다시 mfc70.dll로 새로 컴파일 한다면 mfc70.dll만 있어도 가능할 것이다. 하지만 누가 이 미친 짓을 하겠는가? 사용자들 보고업그레이드 되었으니 새로 다운 받으십시오.’ 이렇게 e-mail을 보낼 것인가
?
이러한 문제는 .DLL 에서 다른 .DLL을 참조할 때도 똑 같은 상황이 발생할 것이다. 결국 .DLL이나 .EXE나 내부는 같기 때문일거다
.

여기서 의문을 제기하는 사람이 분명 있을 것이다. ‘그러면 같은 이름을 쓰면 되지 않겠냐
?
내용은 업그레이드 하더라도 파일 이름을 mfc42.dll 그대로 쓰면 되지 않느냐?’ 맞다 그대로 쓰면 된다. 하지만, 그렇게 쉬운 작업이 아니다. 이것이 해결 되었다면 굳이 COM이 나오지 않았을 지도 모른다
.
그럼 왜 불가능한지 알아보자. 한마디로 얘기 하자면 DLL 설계 자체가 처음부터 잘못 되었기 때문이다. 어플리케이션에서 DLL에 있는 함수를 참조하는 기술은 마이크로소프트에서 아무 생각 없이 구현만 되면 된다고 생각하고 만들었을 것이다. 필요성조차 느끼지 못했을 수도 있다. DLL의 경우 자체 포함된 함수를 서수정보에서 인식할 수 있게 해 놓았다. 따라서 업그레이드 해서 그 서수정보가 바뀐다면 기존 어플리케이션에서는 그 함수들을 인식하지 못한다. 물론 새로 컴파일 하면 가능하겠지만 이것은 커다란 단점이다. 그럼 어느 정도 이해가 되었을 것이다. 정말 내부적인 부분까지는 나도 정확히 모르겠다. 이 부분에서 자세히 알고 싶은 사람은 Application for windows (fourth edition, Jeffrey richter)를 참조 하면 되겠다. 아마 이것만으로도 많이 부족할 것이다. 그렇다면 type-safe linking, name mangling 이라는 용어도 찾아보면 될 것 같다.(과연 실제로 찾아볼 사람이 있을지는 의문
?)

잠시 머리를 식힐 겸 미래의 프로그래밍 방법이 어떻게 진행 될지 생각해보자. 많은 사람들이 앞으로 프로그래밍은 조립형식이 될 것이라고 말하고 있다. 필요한 컴포넌트를 구해서 조립만 하면 원하는 프로그램을 완성할 수 있게 될 것이라고 말한다. 내가 생각하기에도 컴퓨터가 지능을 가지지 않는 이상은 어플리케이션 프로그래머는 컴포넌트의 관계를 설정해 주는 직업이 될 것 같다. ‘너랑 너는 이렇게 연결 되야 해 넌 재랑 통신 해야 해하고 정의만 해주면 되는 것이다
.
참고로 http://www.componentsource.com/ 를 참조 해보면 좋을 것이다. 이곳은 각종 컴포넌트를 만들어 놓고 파는 곳이다. 돈만 많이 준다면 소스까지도 준다
.
그렇다고 누구나 할 수 있는 직업이 될 것이라는 얘기는 아니다. 각 컴포넌트의 특징을 잘 알아야 할 것이고 연결방법 등을 공부해야 하고 하루에도 수십 가지의 컴포넌트들이 쏟아지게 될 것이기 때문에 공부하는 것도 고생일거다. 결국, (?) 미래에는 프로그래머는 컴포넌트 개발자를 가르키는 말이 될 것이고 어플리케이션 프로그래머는 다른 말로 바뀔 듯 싶다. 일반 자동차 영업하는 사람처럼 말이다. 에어백 넣어드릴까요? 알루미늄 휠은 어떻게 할 건가요? ABS는요? 등등
...
내가 하고픈 말은 이런 가능성을 조금이라도 보여준 것이 바로 COM이라는 것이다. 하지만, 앞으로의 COM이 어떻게 될지 아직 아무도 장담하지 못한다. 이유는 바로 .NET 때문이다. 자세한 이유는 나중에 언급하겠다.(결국 이것도 인터페이스 때문이다. 인터페이스가 문제가 있다는 것이 아니라 .NET 객체 역시 인터페이스를 지원한다는 것이다. 개인적으로 업그레이드된 COM이라고는 하고싶지는 않다. .NET 객체는 .NET 객체일 뿐이다. Versioning문제의 해결 역시 .net 의 엄청난 장점이다
.)

다시 COM으로 돌아 와 보자
.
COM
에서 가장 어려운 부분이 인터페이스의 이해부분 이었던 걸로 기억한다.. 하지만 이것만 확실히 한다면 COM은 끝난 거나 다름없다. 마치 옛날 C언어를 할 때포인터는 마녀다. 하지만, 그 마녀를 잡는다면 마녀의 힘을 얻을 것이다라고 한 것과 비슷한 것 같다
.
인터페이스가 뭘까? 많은 사람들이 여기서 막혀버린다. 인터페이스에 대해서는 말도 많고 탈도 많다. 내가 생각하기에는 가상함수가 어떻고 저떻고는 나중에 생각해야 할 문제이다. 인터페이스의 말에 대한 어원을 생각해보자. ‘접촉면이란 단어가 가장 먼저 생각날 것이다. 우리가 윈도우를 쓰다 보면 인터페이스란 말이 나온다. 사용자 인터페이스가 어떻고 저떻고 그런말을 많이 들어봤을 것이다. , 사용자와 윈도우간의 연결을 해주는 부분이라고 해도 좋을 것 같다. 하지만 이것은 개념상의 정의일 뿐이다
.

우리는 어떻게든 이해가 목적이기 때문에 쉽게 생각하자. 황당한 예를 들더라도 이해만 하면 된다
.

그럼 그 황당한 예를 보자
.
먼 미래 인류는 우주로 진출하게 된다. 그리고 사람들은 작은 우주선을 타고 이별 저별 여행 다니면서 외계인도 만난다. 그리고 우주공간 곳곳에 고속도로의 휴게소 처럼 우주 정거장이 있다. 물론 혹성이나 행성에도 있다
.
하나의 우주선이 정거장에 접근하면서 정거장의 출입구와 우주선의 출입구를 정확이 도킹한다. 그리고 긴 연료호스가 나와 정거장의 연료공급 구멍에 정확히 일치시킨다. 사람들은 정거장에서 맛있는 것도 사먹고 화장실도 가기 위해 출입구를 통해 왔다 갔다 하고 그 동안 연료는 자동 공급되고 있다
.
하루에도 몇 백대의 작은 우주선들이 이 정거장에 도킹(?)했다가 떨어지고 그럴 것이다
.
수도 없이 많은 우주선들이 있을 테고 이 도킹 부분이 틀린 우주선은 이 정거장에 가까이 접근은 가능하겠지만 도킹이 불가능하기 때문에 사람이 타고 내릴 수는 없을 것이다. 잘못했다가는 구멍이 생겨서 내장이 다 터져 우주미아가 될지도 모른다. 정거장에서는 출입구와 연료공급 구멍의 크기를 정확히 제한 해 놓았기 때문에 작은 우주선들은 이 출입구 크기에 맞게 도킹장치를 만들기만 하면 사람들이 내리고 타는 데는 문제가 없을 것이다. 잘만하면 외계인(ET)들도 도킹장치를 만들어서 접근이 가능할 것이다. 그리고 외계인들은 자기 나름대로의 도킹장치가 있을 것이고 지구인이 만든 정거장에서 서비스를 받기 위한 도킹장치를 따로 만들 것이다. 한 순간에 외계인 자신의 도킹장치를 포기하구 지구정거장에 맞춰서 바꾸진 못할 것이다. 결국 자기 것은 왼쪽에 만들고 지구인을 위한 것은 오른쪽에다 만든다거나 했을 것이다. 하나만 고집하다가는 신기하게 생긴 지구인 구경을 평생 못하고 죽는 비참한 외계인도 생길 것이다
.

여기서 바로 이 정거장의 도킹장치가 인터페이스가 되는 셈이다. 그럼 정거장은 COM이 되는 건가? 그렇게 생각하는 것이 쉽겠다
.
결국 인터페이스는 출입통로 그 이상도 그 이하도 아니다. 우리는 그 연료 인터페이스를 통해 연료도 공급 받고 출입구 인터페이스를 통해 음식도 공급 받을 수 있고 왔다 갔다 할 수도 있다. 즉 서비스를 받을 수 있는 것이다
.
이 서비스 내용은 정거장마다 경쟁이 심해져서 연료를 10원 깎아 준다거나 화장지를 하나 더 준다거나 음식값을 싸게 해준다거나 해서 정거장마다 틀리다. 갈수록 서비스의 질이 나아지고 있는 것이다.(그럼 DCOM 은 호출 정거장인가? 호출하기만 하면 와서 연료도 주고 음식도 팔고 하는 그런 것
. ^^)

그럼 DLL을 비슷하게 예를 들어보자. DLL은 구식 정거장이다. 정거장이라고 말하기도 부끄럽다. 연료를 넣는 자판기라고 부르는 것이 적당할 것 같다. 컵라면 자판기도 옆에 있다. COM이라는 정거장과 경쟁하기 위해서는 연료를 넣고 나면 화장지도 줘야 한다. 그럼 자판기에 화장지가 나오게 만들어야 한다. 결국 새로운 화장지를 주는 기능이 추가된 자판기로 교체를 해야 하는 불상사가 생긴다. 즉 서비스의 업그레이드를 할 때마다 이 자판기 자체를 바꿔야 한다. 하지만, 이것도 문제가 있다. 이전에 우주선은 새로운 자판기 사용법을 모르기 때문에 옛날 자판기를 그대로 놔두길 원하는 것이다. 자판기 회사는 결국 구식 자판기와 신식 자판기를 옆에 나란히 두어야 했던 것이다
.
유지비도 많이 든다. 전기세가 장난이 아니었던 것이다. 자리세도 점점 많이 내야 한다. 결국 자판기 회사는 망하는 중이다
.

이 예가 맘에 들지 않는다면 자신이 새로 적용을 시켜 예를 만들어 적용시켜도 좋을 것이다
.
자신의 COM개념을 정립할 수만 있다면 어떠한 예도 좋다
.

앞으로 해보겠지만 실제 COM을 구현해보면 생각보다 쉬운걸 알 수 있다. 그냥 DLL 만들듯이 만들면 된다. 그리고 필요한 기능끼리 잘 묶어서 인터페이스에서 정의 하면 된다. 물론 이 방법은 기존 DLL COM으로 업그레이드 할 때 유용하게 사용할 수 있다. 하지만, 이왕이면 인터페이스를 먼저 설계하고 함수들을 추가하는 것이 있어보이지 않겠는가? 왠지 설계능력을 갖춘 것처럼 보이니깐 말이다. (앞으로 여기서 골 때리는 말들이 많이 나온다
.
가상함수니 가상함수 테이블이니 하는 그러한 것들이다. 하지만, 이것은 단지 우리가 약속만 지키면 끝이다. 실제 구현부분은 별개라는 얘기다
.)

난 많은 책들에서 COM의 필요성을 봐왔다. 다들 DLL의 단점 기존 소프트웨어 공학의 단점, 컴포넌트 기반의 소프트웨어의 장점 등을 언급하면서 COM을 해야 한다고 부추긴다
.

하지만, 난 돈으로 생각해서 그 필요성을 느낀다
.
앞의 예에서 물론 우주선을 운영해서 사용자들로부터 요금을 받을 수도 있다
.
하지만, 실생활에서 버스나 택시 기사들이 돈을 많이 버는가? 그렇지 않다는 것을 잘 알 것이다. 가장 쉽게 돈을 버는 방법은 자리를 제공해 자리세를 받던가 지나다니는 우주선으로부터 통행료를 받는 것이 가장 쉬울 것이다. 하지만, 이것은 국가나 대기업이 하고 있는 일이다. 마이크로소프트나 선이나 IBM 등등 이런 회사들이 이미 굳건히 지키고 있다.(결국 .NET의 웹 서비스도 이러한 돈벌이가 아닐까 싶다
.)
그럼 우리는 이 정거장을 운영하는 수 밖에 없다. 이것이 우리가 가장 쉽게 많은 돈을 벌 수 있는 방법인 것이다. 하지만 다른 정거장과 구별되는 무엇인가가 있어야만 경쟁에서 살아 남을 수 있다. 아니면 자리를 잘 잡던가
.

오늘은 여기서 마치겠다
.
다음 경험담부터 서서히 정거장 만들기에 들어갈 것이다. 언제 시작할지는 모르겠다. 앞에

서 언급했듯이 내 맘이다.

------------------------------------------------------------------

e-mail :
icoddy@hotmail.com
msn id : icoddy@hotmail.com
-
박성규 -

 

///

나의 COM(Component Object Model) 경험담 #2


경험담 #1 com의 개념 잡기 였습니다. 개념이 잡히던가요? 기대는 하지 않습니다. 제가 예를 든 것이 말도 안 되는 예라고 생각하셔도 좋습니다. 어쨌든 제 맘대로 경험담 #2를 들려드리겠습니다. 진도가 빠르다고 생각하는 분이 있을지는 모르겠지만 초반은 쉬우니 편하게 읽고 지나가십시오. 그리고 반말은 계속 됩니다
.
기분 나쁘시면 역시나 읽지 않으셔도 됩니다
.

그럼 시작하겠습니다
.

------------------------------------------------------------------

이제 .net 의 시대이다. 도대체 .net 이 뭐길래 이렇게 시끄러운 걸까? 그런데 무시할 수 없는 얘기가 흘러 나온 것이 아닌가. 마이크로소프트는 자신들이 그렇게 신기술임을 강조하던 COM을 더 이상 배울 필요가 없다고 하였다. 이게 무슨 김밥 옆구리 터지는 소리인가? (경험담 #1에서 이미 난 이 내용을 조금 언급했었다. 기억하는가
?)
그러면 COM을 대체할 수 있는 것이 도대체 .net 에서는 무엇인가? 그것은 바로 .net 컴포넌트이다. 기뻐하자. 더 이상 그 복잡한 COM을 할 필요가 없어졌다. 하지만 정말 기뻐할 일일까? 개인적으로는 그렇지가 않다는 것이다. 윈도우가 얼마나 덩치가 큰지는 여러분도 잘 알 것이다. 이 모든 것을 .net 컴포넌트가 대체할 수는 없다는 얘기다. 시간도 무지 오래 걸릴 것이다. 그리고 가장 큰 이유는 속도와 효율성 측면에서 COM .net 컴포넌트 보다 우위에 있다는 것이다. 그래서 COM은 살아 남을 것이다
.
결국 우리는 배울 것이 하나 더 늘었다는 것에 대해 슬퍼해야 한다. 빌어먹을 마이크로소프트. 하긴 마이크로소프트만 그럴까? 다른 곳도 마찬가지로 허구헌날 신기술이라고 떠들면서 하나하나씩 내뱉는다. 개발자는 말 그대로 이제 대가리(?) 노가다맨 그 이상도 그 이하도 아니게 되어 버렸다. 그냥 넘어가자. 불만을 갖는다고 해서 마이크로소프트가 우리 부탁을 들어주는 것도 아니다
.

자 그럼 복습하는 의미에서 소프트웨어 개발 발전사를 COM에 초점을 맞추어서 정리해 보도록 하자
.

그 옛날 도스시절. 이 시절의 프로그래밍을 잘 모르시는 분도 있을 것이다. 내가 처음 대학에 입학했을 때 막 볼랜드 사의 Turbo-C 2.0 이 나왔다. 나보다 나이 많은 사람들은 더 이상한 것들을 많이 했을 것이다. 난 이것을 얻기 위해 선배한테 술까지 샀다. 돌아온 것은 3.5인치 디스켓 한 장에 압축되어 있는 Turbo-C 였다
.
이 시대(?)에는 .c 파일과 .h 파일을 가지고 재사용을 했었다. 말 그대로 소스를 가지고 있었던 것이다. 우리는 #include 를 사용해서 필요한 함수가 있는 파일을 연결해서 사용하였다. 결국 필요한 소스가 모두 있어야만 컴파일 되었고 이 실행 파일은 말 그대로 덩치가 클(?) 수 밖에 없었다.(요즘 프로그램에 비하면 새 발에 피겠지만
.)
생각해보면 지금도 우리는 이 고전적인 방법을 사용하고 있다. 예로 예쁜 버튼을 사용하기를 원한다고 하자. 다행히 누가 CPrettyButton 클래스를 구현해 놨다. 우리는 그것을 쓰기만 하면 된다. 사용하기 위해 CPrettyButton.h 파일과 CPrettyButton.cpp 파일을 프로젝트에 포함시키고 CButton m_button; CPrettyButton m_button; 이렇게 바꿔서 사용하고 있다. 결국 우리는 소스를 가지고 있다가 다음에 또 다른 프로젝트에서 그 파일을 복사해 와서 재사용한다. 소프트웨어 공학면에서 볼 때 구식 방법이다. 그렇다고 자신을 질책 할 것은 없다. 대부분 이렇게 쓰고 있다고 난 장담한다.(아니라고 우기면에이~~ 일부는 쓰지?’ 라고 다시 물어볼 것이다
.)
그리고 절대 고수들의 비급인 Copy & Paste 도 있다
.

그리고 윈도우 프로그래밍으로 넘어오면서 디스크 공간의 낭비를 없애고 업그레이드를 위해 새로 모든 내용을 다시 컴파일 해야 하는 수고를 덜어주기 위해 DLL이라는 동적 라이브러리가 나왔다. 고전적인 방법과 비교해서 예를 들자면 mfc42.dll에 포함된 수많은 API 함수들의 소스를 우리는 알 필요가 없어졌다. 그냥 dll에 있는 함수 형식만 알면 얼마든지 호출만 하면 된다.( 여기서 자세한 언급을 하지 않겠다. ) 하지만 여기서도 단점이 있다. 언어 독립적이지 않았다는 것이다. 내가 알기로 초창기엔 C로 만든 DLL C로 만든 어플리케이션에서만 참조가 가능했던 걸로 기억한다. 그리고 DLL지옥 이라는 유명한 문제점도 나타났다. (하지만, COM 역시 이 문제를 완전히 해결한 것은 아니다
.)

그리고 나서 이러한 문제점들을 또 하나 해결한 COM이 나왔다. 이제 C++로 작성한 COM을 비주얼 베이직에서도 델파이에서도 맘대로 쓸 수 있게 되었다. 어떻게 가능한 것일까? #1에서 말한 것처럼 인터페이스 때문이다. 표준화된 인터페이스가 있기 때문에 가능해졌다
.
그러면 COM은 완전한가? COM에도 분명히 단점이 존재한다. 그 첫번째가 바로 만들기가 어렵다는 것이다. 물론 ATL이라는 넘이 쉽게 해주긴 하지만 COM의 모든 기능을 쓰기에는 부족하다. 초창기 COM을 만든다는 것은 몇 단계를 거쳐야 가능했었다. 왠만한 공부로는 COM을 만든다는 것은 매뉴얼 두 세권 정도 펴놓고 따라 해야만 하는 작업이었다. 두 번째로 DLL지옥이라는 엄청난 문제가 여전히 조금 남아있다는 것이다. DLL에 비하면 좀 나아졌지만 여전하다. 그리고 배포도 조금은 어려운 것 같다
.

자 그럼 여기서 DLL로 작성한 것과 COM으로 작성방법이 어떻게 틀려 졌는지 간단한 예로 알아보자. DirectX 프로그래밍을 해보신 분들은 조금 이해할지 모르겠다. IDirectDraw라는 인터페이스를 사용해서 그리기를 할 수 있다. 그런데 DirectX가 버전업이 되면서 우리는 IDirectDraw2, IDirectDraw3등 이렇게 버전업이 될 때마다 새로운 기능을 사용하기 위해 뒤에 번호를 붙여서 새로운 기능을 사용하였다. 지금 나온 것이 8인가? 그럴 것이다. DirectX 프로그래밍도 COM 프로그래밍이다. 한번 정의된 인터페이스는 바꾸지 않는 것이 표준이다. 왜냐하면 이미 이전 인터페이스를 사용해서 개발된 프로그램을 위해서 이다. 이걸 바꾼다면 그 프로그램은 버그 투성이의 프로그램으로 바뀔 것이다.(사실, 바꿔도 상관없다. 마이크로소프트가 그렇게 하라고 강요할 뿐이다. 누가 내가 만든 COM을 사용할까 라고 생각하면 바꿔도 될 듯 싶다.) 그렇다면 DLL은 어떻게 이 문제를 해결했을까? DLL이라면 directdraw1.dll 부터 directdraw8.dll까지 다 있어야 할 것이다. 어떤 프로그램은 버전 1을 참조하고 어떤 프로그램은 버전 2를 참조할 것이고. 이렇게 해서 그때그때 개발된 프로그램마다 따로 참조해야 하기 때문이다. 그리고 DLL로 한다면 비주얼 베이직용과 델파이용 DLL도 따로 제공해야 했을 것이다. 결국 DLL 개발자는 죽어나가는 것이다.(요즘은 이 말이 통하지 않을 것 같다. 델파이에서도 비주얼 베이직에서도 DLL을 사용할 수 있게 된 것으로 알고 있다
.)
그런데 COM이 나오고부터 그럴 필요가 없어졌다. 하나로 COM 컴포넌트만 바꿔주면 되는 것이다. 그 컴포넌트의 업그레이드는 인터페이스의 추가로 해결 가능해진다. , 인터페이스를 여러 개 구현하면 되는 것이다
.

자 그럼 슬슬 코딩 부분으로 들어갈 때가 된 것 같다. 더 이상 읽고 싶지 않다는 생각이 드는 분도 있을 것이다. 하지만, COM 끝까지 알아보고 싶지 않은가? 정말 희한한 놈이란 것을 장담한다. 재미도 있다. 그러니 조금만 더 믿고 보자
.

그리고 설명 중에 빠진 것이 있다. 바로 ActiveX 이다. 이 넘은 뭘까? 버튼과 같은 컨트롤? 꼭 그런 것은 아니다. 마이크로소프트의 연막작전으로 인해 이것의 정의도 명확하지 않다. 지금의 .net 과 같이 수많은 정의가 따랐었다.(마이크로소프트의 있어보이기 작전이 아닐까라고 개인적으로 생각한다.)  결국, 이 놈도 COM 컴포넌트의 일종이다. 부분집합이라고 해야 하나? ActiveX COM이다. 이것 맞는 말이다. 하지만 COM ActiveX이다 이건 안된다. 대충 이해가 갈 것이다. 내가 말한 부분도 오류는 있다. ActiveX는 하나의 기술이라고 말하는 것이 정확하다. 그러면 앞에서 언급한 ActiveX는 뭐라고 해야 정확할까? 그것은 ActiveX 컨트롤이 정확한 말이다. 이것은 마이크로소프트에서 10개 이상의(몇 개인지 정확히 모르겠다.) 인터페이스를 정의 해서 이 기능을 다 구현하면 그것은 ActiveX 컨트롤로 볼 수 있다고 했다. 즉 이 기능을 구현하면 ActiveX 자격을 주겠다 이런 형식이다.(너를 ActiveX로 임명하노라 이런 식이다
.)

자 그럼 본격적으로 들어가 보자
.
클라이언트/서버수도 없이 많이 들어본 말일 거다. COM 사용 역시 이 말로 표현이 가능하다. 여러 개의 COM 컴포넌트로 구성된 어플리케이션이 있다고 가정하자. 어느 것이 서버일까? 바로 COM 컴포넌트가 서버이고 어플리케이션이 클라이언트가 된다. 클라이언트와 서버의 개념은 크기로 구분되는 것이 아니라 어떤 일을 요청하고 그것을 들어주는 입장에서 구분하다. 그러면 이 말도 쉽게 이해 될 것이라 생각된다
.

오늘은 포기하는 사람을 막기 위해 함수 2개만 설명할 것이다. 더 이상 더 강요하면 다 도망갈 것 같다. 하루에 너무 많은 것을 해도 신상에 해롭다. 머리카락도 빠진다
.
(10, 9, 8, 7, 6, 5, 4, 3, 2, 1 10
초 동안 심호흡 한 번 하자
)
이제 모든 준비가 끝났을 거라 믿는다. 그럼 들어가 보자
.

어플리케이션이 COM 서비스를 사용하기 위해 먼저 COM을 사용할 수 있게 하는 모듈을 포함시킬 필요가 있다. 그 역할을 해주는 함수가 CoInitialize 함수이다. 드디어 함수가 나왔다. 쫄지 말자. 이 함수는 아무 생각 없이 쓰면 된다. 프로그램이 시작할 때 한번 호출해주면 된다. 그리고 마지막에 CoUninitialize 함수를 호출하면 된다. 대부분 MFC 프로그램에서는 인스턴스가 시작되는 부분에 CoInitialize을 넣고 인스턴스가 종료되는 부분에 CoUninitialize 를 넣으면 된다. 그런데 여기서도 주의할 필요가 있다. COM의 경우 스래드와 아주 밀접하게 관련되어 있다.(아파트먼트라는 말도 자주 나왔다.) 그래서 일까? 새로운 스래드를 생성할 때 그 스래드가 COM을 사용한다면 그 스래드에서도 CoInitialize 함수가 호출 되어야 한다. 당연히 스래드가 종료할 때 CoUninitialize를 호출하는 것은 두말할 여지도 없다. 이 두 함수는 항상 짝을 이루어야 한다는 것을 잊지 말자. 그러면 COM을 사용할 준비는 모두 끝났다. 간단하지 않나? 좀더 자세히 설명하자면 COM 라이브러리를 초기화 하는 것이다
.
이제는 COM을 생성하는 단계로 넘어가자. 이 때 쓰이는 함수가 CoCreateInstance 함수이다. 여기에는 5개의 파라미터가 필요하다
.

CoCreateInstance(
        REFCLSID rclsid,                        //COM
개체의 클래스 식별자

        LPUNKNOWN pUnkouter,                //
외부 COM 개체의 Iunknown 포인터
        DWORD dwClsContect,                //
서버 컨텍스트
        REFIID riid,                        //
요청할 인터페이스 식별자
        LPVOID* ppv                        //
리턴된 인터페이스 포인터
);

여기서 5개를 자세히 살펴보자. 모든 파라미터들은 다른 곳에서도 자주 쓰이므로 여기서 확실히 해두면 앞으로 편할 거라 생각한다.
처음에 나온 것이 컴포넌트 식별자이다. 128 비트로 되어있다. 식별자라는 말이 다른 놈과 구별할 수 있게 하는 것이라는 것은 어렴풋이 알 것이다. 이 값은 지구상에서 유일 하다는 뜻으로 Globally unique identifiers 하고 한다. 바로 GUID이다. 4번재 파라미터인 인터페이스 식별자 역시 마찬가지로 GUID를 사용한다. 이것은 중복이라는 것이 없다. 항상 컴퓨터의 고유 번호와 시간, 지역 등의 조합으로 만들기 때문에 중복이 없다고 한다. 하지만, 코드 상에서 변경이 가능하기 때문에 자신의 회사에서 만든 것은 특별한 번호를 부여할 수도 있다. 이 경우 중복 되지 않도록 주의 하자. (자신이 피해를 보는 것은 괜찮지만 괜히 남이
X
빠지게 만든 것을 망칠 수가 있다
.)
그런데 clsid 의 경우 항상 128비트를 입력할 것인가? 이것은 어렵다. 그래서 문자열로 된 식별자를 사용하면 쉽게 할 수 있다. 이것은 IP주소를 www 형식으로 쉽게 변환해주는 방식과 비슷하다. 이것을 해주는 것이 CLSIDFromProgID 라는 함수이다. 이것은 보통 어플리케이션.컴포넌트.버전 형식으로 나타낸다. 마이크로소프트의 워드 어플리케이션 컴포넌트라면  Word.Applicaton.8 이런 형식으로 쓰여질 것이다
.
따라서 보통은


CLSID clsid;
CLSIDFromProgID(L”Word.Application.8”, &clsid)
CoCreateInstance(clsid, ..);

이런 식으로 쓰인다. (결국 오늘 함수를 3개를 해버렸다.)
그러면 clsid에는 복잡한 128비트짜리 고유번호가 들어가게 된다. 굳이 복잡하게 코딩 할 필요가 없어진다
.

두번째 파라미터는 Aggregation에서 사용되는데 지금 하면 머리만 아파온다. 그냥 NULL값으로 넘긴다고 생각하자
.
세번째 파라미터 서버 컨텍스트로 COM 컴포넌트 서버가 클라이언트와 같은 프로세스 영역에서 실행될 지 다른 프로세스 영역에서 실행될지 아니면 아예 다른 시스템에서 실행될 지 여부를 제어하기 위해 사용한다
.
네번째는 인터페이스 파라미터이고 마지막 파라미터는 COM 컴포넌트가 리턴하는 인터페이스 포인터가 저장되는 곳이다
.

이 함수는 성공 하면 S_OK값을 리턴 하고 실패하면 E_OUTOFMEMORY, E_NOINTERFACE, CLASS_E_NOAGGRETION 등을 리턴 할 수 있다
.

이것으로는 이해하기에 부족할 것이라고 생각한다. 내가 생각해도 처음 보는 사람에게는 무슨 말인지 잘 모를 것 같다. 하지만 먼저 산을 보도록 하자. 나무는 나중에 천천히 봐도 늦지 않다. 지금은 먼저 간단한 걸 만들어보는 것이 중요하다고 생각하기 때문에 나중에 자세히 설명하겠다. 우선은 하나라도 만들어 보는 것이 도움이 될 것 같다. 우선은 COM 객체를 생성하는 부분이구나 라고 이해하면 쉬울 것 같다
.

결국 프로그램의 흐름은 대충 이렇게 될 것이다
.

1. CoInitialize(NULL);
2. CoCreateInstance
에 인자로 넘길 각종 변수 선언 및 초기화
;
3. CoCreateInstance(clsid, ……);
4. COM
컴포넌트 사용

5. CoUninitialize();

이 흐름은 여러가지 형태로 바뀌기도 한다. .EXE로 된 COM 컴포넌트의 경우는 조금 달라진다. 이 부분은 나중에 다루겠다.
오늘은 여기까지만 하자 앞에서 말했듯이 너무 많이 하면 몸에 해롭다
.

~~ 그리고 중요한 것이 하나 빠졌다. 초기화를 하기 전에 먼저 COM 컴포넌트가 시스템 레지스트리에 등록이 되어 있는 지 확인해야 한다. 만약 등록되어 있지 않다면 클라이언트는 실시간 에러를 발생 시킬 것이다. 컴파일은 잘 되겠지만, COM을 생성하는 과정에서 에러를 발생시킨다
.

등록하는 방법은 다음과 같다
.

regsvr32 aaa.dll
또는 regsvr32 aaa.ocx 라고 명령 창에서 실행하면 된다. 파일의 패스도 정확히 입력해야 한다
.

등록 해제는 regsvr32 /u aaa.dll 또는 regsvr32 /u aaa.ocx 이렇게 된다
.

만약 out of process 서버(.exe)의 경우는 조금 틀린데 다음과 같이 등록해주어야 한다
.
aaa.exe /regserver

등록해제는 aaa.exe /unregserver 이다
.

왜 등록해야 할까? 등록하지 않는다면 clsid 를 가지고 COM 컴포넌트의 위치를 찾을 수 없기 때문이다. 억지로 찾겠다면 하드에 있는 모든 파일들을 찾아보면서 너 COM이야? 물어봐야 하고 COM이면 식별자를 보고 내가 만들려는 COM 이 맞는 지 또 확인 해야 하는 복잡함이 생기기 때문이다. 따라서 쉽게 레지스트리 정보를 보고 패스를 쉽게 찾아 갈 수 있게 하는 부분이 COM 등록 부분이다
.
이제 COM을 처음으로 사용할 준비는 모두 끝났다
.


오늘은 여기서 마치겠다
.
자야 한다. 다음 경험담은 언제 일지 나도 모르겠다. 어차피 기다리는 분도 없을 거라 생각한다. 그리고 질문은 역시나 받지 않겠다. 질문하지 마라. 대답 안 할 거다. 아니 못한다
.
능력도 안 된다
.

------------------------------------------------------------------

e-mail :
icoddy@hotmail.com
msn id : icoddy@hotmail.com
-
박성규 -
///

나의 COM(Component Object Model) 경험담 #3


벌써 #3 까지 왔습니다. 고맙게도 점수를 주시는 분들도 계셨습니다. 그리고 저의 협박 아닌 협박 때문인지 아무도 질문은 하지 않더군요
. ^^;
네 좋습니다. 물론 대부분의 사람들이 그럴 가치를 느끼지 못해서 그러셨겠지만, 전 역시나 제 맘대로 대부분 경청 태도가 좋군 이렇게 생각 할랍니다
.
그 뒤는 이제 말 안 해도 다 아시리라 생각합니다
.

그럼 시작하겠습니다
.

------------------------------------------------------------------

자 이제 정리할 겸 생각 좀 해보자. 난 아직 COM에 대한 정의는 내리지 않았다
.
#1
#2에서 대충 이런 것이라고 말한 것은 모두 조금씩 잘못된 내용이다.( 속았다고 생각하진 마라. 인생이 원래 그런거다. 앞으로 몇 번은 더 속아야 할 것이다.) 그 것들은 단지 COM의 이해를 돕기 위한 쉬운 그림 그리기에 지나지 않는다
.
그럼 COM 이 도대체 뭘까
?
이것도 Microsoft 홈페이지에서 죽어라 찾아봐도 감을 잡을 수 있을지 모르겠다. 역시 마이크로소프트이다. (이 연막작전은 누구도 당할 수 없다
.)
3
년쯤 전이었던 걸로 기억한다. 난 친구랑 나름대로 공부한 것을 가지고 얘기를 한적이 있었다.(아주 드문 경우다. 보통은 여자 얘기, 술 이야기가 대부분이다.) 그러다가 COM 얘기가 나왔고 COM에 대한 정의를 가지고 한참을 싸웠다. 친구는 COM Component 개념으로 우겼다. 마치 ActiveX에 가깝게 말이다. 그리고 난 일종의 라이브러리라고 우겼다. DLL에 가깝게 말이다.(결국 비슷한 말이었지만, 자존심 문제였다. 거짓임이 감이 오더라도 그 자리에서는 양보할 수 없다.) 지금 이 글을 읽는 당신은 무엇이라고 우기겠나
?
어쨌든, 친구와 난 둘 다 틀렸다. 그것도 완전히 틀린 것이다. 이것은 모두 마이크로소프트의 잘못이다. 그냥 COM은 이것이다 라고 정의만 하면 되는데 왜 하지 않는지 모르겠다
.

자 그럼 마이크로소프트에서 말하는 COM은 무엇인가? 답은 이렇다
.
오브젝트와 시스템이 개방적이고 변화 가능한 방식으로 상호 동작할 수 있는 방법을 정의하는 다수의 기술에 대한 바이너리 사양이다
.’
~ 훌륭한 정의가 아닌가? 우리의 마이크로소프트에서 COM을 정의 해 주셨다. 그것도 정확하게 말이다. 이 글을 읽자마자 바로 COM이 뇌 속 깊이 와 닿지 않는가
?
(
정막이 흐른다) 정말 그런 사람이 있다면 그 사람은 프로그래밍이 아닌 소프트웨어 공학쪽으로 추천하고 싶다
.
대부분의 사람은 이 막연한 말에 응 그렇구나 하고 아무 생각 없이 넘어간다. 좀 어렵네 하고 말이다. 하지만 그러면 안 된다. 더 이상 진도가 나갈 수 없는 것이다. 개념도 잡지 않은 상태에서 무슨 COM을 하겠다는 말인가? 이 부분을 확실히 집고 넘어가야만 발전할 수 있다. 자 그러면 자세히 살펴보자. 오브젝트란 말은 무엇인가? 여기서 오브젝트는 하나의 프로세스가 될 수도 있고 컴포넌트가 될 수도 있다. 결국 이것도 애매하게 정의 해 놓았다.(쥑일~) 그리고 시스템 역시 마찬가지다 여러 가지 의미로 해석될 수 있다. 그래 좋다. 프로세스간 또는 스래드간 또는 컴포넌트간 또는 기타 어플리케이션과 컴포넌트간, 수도 없이 많은 경우의 수가 생긴다.(MS 너희가 원하는 것이 이것이냐?) 이들간에 상호 동작할 수 있는 방법을 정의하는 다수(?) 여기서 또 나왔다. 다수(적당히 많은)란 말도 애매하다. 결국 COM은 여러 가지 기술들 또는 규약을 복합적으로 부르는 말이다
.
이런 떠그럴~ 더 어렵잖아. 결국 괜히 해석했다. 그렇다. 우리는 이 정의를 외울 필요가 없다. 그러면 우리가 해야 할 일은 무엇인가? 우리는 프로그래머다. 따라서 기술만 알면 된다
.
따라서 프로그래머 입장에서 COM Componet와 인터페이스, ActiveX, ATL, 오토메이션, COM 스래딩 모델등등 COM에서 사용되는 기술들을 알면 되는 것이다. 이 모든 것이 다 COM이다
.
이제 이해가 되었는가? COM은 틀을 제공할 뿐이다. 이런 규칙만 지키면 너희는 다 COM에 포함될 수 있다
.

~~ 한 숨 돌리자. 자 어렵게 COM의 정의를 내렸다. 여기서 불만인 사람도 있을 것이다. 불만 가져도 좋다. 자신이 확실히 COM을 알고 있고 이것에 대한 정의 내릴 수 있다면 이번 한번만 답글을 허용하겠다. 대신 질문은 여전히 금지다. 간단히 COM에 대한 정의만 내려라
.
그리고 생각해보자. 모두 머리를 맞대고 말이다
.

그럼 이제 #2에 이은 프로그래밍을 해보자. #2에서 CoInitialize CoCreateInstance를 했다. 그리고 부가적으로 따라오는 두 함수도 했다. 이것들은 언급하지 않겠다
.
여기서 개체를 생성하고 이용한다고 할 때 이 개체는 COM 컴포넌트이다. 그리고 이 모든 것이 COM의 범주에 속하는 기술인 것이다. (지겹다 이제 COM에 대한 정의와 관련된 것은 그만하자
.)

앞으로 내가 언급할 내용의 대부분은 COM 컴포넌트가 될 것이다. 그냥 컴포넌트라고 해도 문맥상 COM 컴포넌트라고 생각하면 된다. 이 컴포넌트는 인터페이스를 가진다. 그리고 이 인터페이스는 모든 컴포넌트들이 가지고 있는 기본 인터페이스인 IUnknown 인터페이스에서 상속 받는다. 점점 재미가 떨어지고 있다. 공부하고 싶은 마음이 이걸 보는 순간 없어 지려고 할 것이다
.
하지만 걱정하지 말자 상속에 대해 두려움을 갖고 있다면 이것도 해결할 수 있다. 모든 두려움은 실제 내부까지 깊숙히 들어가 보려고 하기 때문에 생기는 것이다. 겉에서 둘러 보면서너 상속이구나하고 알아 보기만 하면 된다. C++에서 개체 상속은 많이 들어봤고 실제로 해본 사람도 많다. 앞에서 본 CPrettyButton역시 CButton에서 상속 받지 않았나? 그럼 개체 상속과 인터페이스 상속은 어떻게 다른가
?

간단한 예로 이해하자. 앞에서 난 우주정거장을 컴포넌트로 예를 들었다. 이렇게 생각할 때 개체 상속은 기본 서비스를 가지고 있는 우주정거장을 가져와서 화려한 네온 간판을 달고 내부 인테리어를 하고 필요한 음식과 팔 수 있는 연료를 준비하는 과정이라고 생각하면 된다
.

그럼 인터페이스 상속은 무엇인가? 인터페이스를 다른 우주선들과의 도킹장치라고 예를 들었으니 이것도 거기에 맞춰서 생각해 보자. 처음 우주정거장은 서비스는 가지고 있지만 이것들을 어디로 통해 제공해야 하는지는 모른다. 따라서 연료 서비스를 위해 연료공급호스도 추가해야 하고 음식 서비스를 위해 출입구도 만들어야 한다. 즉 우주선의 도킹 장치를 만들어야 한다는 것이다. 만약 이것이 없다면 서비스는 존재하지만 우주선들은 그 서비스를 사용하지 못하게 된다는 것이다. 그런데 여기서 중요한 것이 있다 이 부분에서는 실제 도킹장치를 만드는 것은 우주 정거장에서 해야 한다
.
바로 인터페이스 상속이라는 말은 도킹장치의 설계도를 얻어 왔다는 것이다. 지구인우주선의 도킹장치의 규격과 외계인 우주선의 도킹장치 규격을 알아 왔다는 것이다
.
그럼 최소한 한 군데라도 팔아서 이윤을 남기려면 외계인용은 만들지 못하더라도 지구인용은 만들어야 하지 않겠나. 최소한 굶어 죽지 않으려면 말이다. 돈을 더 벌고 싶다면 외계인용도 만들면 금상첨화일 것이다
.

그리고 컴포넌트는 최소한 IUnknown 인터페이스만이라도 가지고 있어야 한다. 이것은 정거장을 운영하기 위한 최소한의 필수 조건이라고 생각하면 되겠다
.
그렇다고 해서 이것만 가지고 할 수 있는 것은 아무것도 없다. 돈은 벌 수가 없다는 말이다.따라서 지구인용 도킹장치 하나는 최소한 만들어 놔야 한다.  도킹장치가 없는 정거장은 정거장이 아니다. 왜냐하면 그것은 우주를 둥둥 떠다니는 고철 덩어리에 불과하기 때문이다
.

자 그럼 이제 정거장을 만들어보자
.
COM
의 기본적인 문법을 모두 무시하겠다. 이걸 지키면 코드가 복잡해지고 이해하는 것 조차도 힘들어진다. 괜히 사서 고생을 하지 말자. 물론 나중에 다 이해가 되면 당연히 완전한 문법으로 만들 것이다
.
인터페이스의 이름을 붙일 때 우리는 잠정적으로 앞에 ‘I’를 붙인다. IUnknown, IClassFactory 등등 이렇게 된다
.

이제 아래의 코드를 함 자세히 살펴보자. 이게 바로 정거장이다
.
하지만, 이 코드가 실제로 돌아 갈 것이라 생각하면 큰일이다. 절대 돌아가지 않는다. 그냥 이해를 돕기 위해 뺄 건 다 뺀 부분이다
.

Class C
정거장 : public I지구인, public I외계인

{
public:
C
정거장();
        ~C
정거장
()

        //IUnknown
메서드

        HRESULT __stdcall QueryInterface(REFIID riid, LPVOID* ppv);
        ULONG __strcall AddRef(void)
        ULONG __strcall Release(void)

        //I
지구인 메서드
        HRESULT __stdcall
휘발류연료팔기(short 금액);
HRESULT __stdcall
비빔밥팔기(short 금액
);
       
        //I
외계인 메서드

        HRESULT __stdcall
미네랄연료팔기(short 금액);
HRESULT __stdcall
미네랄식료품팔기(short 금액
);

private
        short
매출
;
        short
순익
;
        DWORD m_cRef;                //
현재 도킹하고 있는 우주선 수

}

그런데 자세히 보면 IUnknown 에서는 상속 받지 않았는데 내부에 선언되어 있다. 그것은 I지구인과 I외계인 모두가 IUnknown에서 상속 받았기 때문이다. 모든 인터페이스는 IUnknown 에서 상속 받는다는 것을 잊지 말자. 앞의 코드에서 처럼 IUnknown 인터페이스는 3개의 메서드로 구성되어 있다.

QueryInterface(REFIID riid, LPVOID* ppv);
AddRef(void)
Release(void)

이 세 가지다. 이걸 보면 인터페이스란 것이 그냥 메서드 선언의 집합이 아닌가 하는 생각도 든다. 하지만, 이것은 위험한 생각이다. 이해는 쉬울지 몰라도 그렇게 해 놓은 이유가 있는 것이다. 설명은 다음 코드를 보면 알 수 있을 것이다. 자세한 설명은 하지 않겠다. 왠만한 책에 보면 정말 장황한 설명이 많이 있으니 참조하면 좋을 듯 싶다
.

이제 남은 것은 각각의 메서드들의 실제 구현이다. 이것을 우리는 C++에서 오버라이드라고 한다. 재정의라고 우리말로 번역해서 말하는 사람도 있다
.

그럼 실제 구현 부분을 보자
.

//IUnknown
메서드

HRESULT __stdcall C
정거장::QueryInterface(REFIID riid, LPVOID* ppv)
{        
        I
지구인과 I외계인 인터페이스중 하나를 ppv에 넘겨준다
;
        //
여기서는 원하는 인터페이스를 돌려주면 된다
.
        //
성공유무를 리턴한다
.
}
ULONG __strcall C
정거장
::AddRef(void)
{
       
여기서는 도킹해 있는 우주선 수를 하나 증가 시킨다
;
}

ULONG __strcall C
정거장
::Release(void)
{
       
여기서는 도킹해 있는 우주선 수를 하나 감소 시킨다
;
}

//I
지구인 메서드

HRESULT __stdcall C
정거장::휘발류연료팔기(short 금액)
{
       
매출을 금액만큼 증가 시킨다
;
       
순익을 금액/10 만큼 증가 시킨다
;
        return S_OK;
}
HRESULT __stdcall C
정거장::비빔밥팔기(short 금액
)
{
       
매출을 금액만큼 증가 시킨다
;
       
순익을 금액/10 만큼 증가 시킨다
;
        return S_OK;
}
       
//I
외계인 메서드

HRESULT __stdcall C
정거장::미네랄연료팔기(short 금액)
{
       
매출을 금액만큼 증가 시킨다
;
       
순익을 금액/20 만큼 증가 시킨다
;
        return S_OK;
}
HRESULT __stdcall C
정거장::미네랄식료품팔기(short 금액
)
{
       
매출을 금액만큼 증가 시킨다
;
       
순익을 금액/20 만큼 증가 시킨다
;
        return S_OK;
}

자 대충 이해가 갈 것이다. 주석은 필요가 없을 것 같다. 코드 내용자체가 주석이 아닌가?(아니라고 생각해도 어쩔 수 없다. 더 이상은 내 능력 이상이다
.)
인터페이스 정의에는 어떠한 내부 구현코드도 없다. 실제 구현은 C정거장에서 한다
.
왜 그럴까? 정거장 마다 그 내부 사정에 따라 다르게 구현 되기 때문이다. 원가를 많이 낮춘 곳은 순익이 많이 남을 테고 그렇지 않은 곳은 그 반대일 것이다. 결국 내부구현은 정거장에서 알아서 할 일이다
.

여기서 IUnknown이 왜 중요한 걸까? 잘 생각해 보자. 정거장은 최대한 많은 돈을 벌어야 한다. 그런데 만약 우주선이 한대도 도킹해 있지 않은 상태라고 가정해보자. 괜히 전기세 낭비하면서 내부를 풀로 가동해야 할까? 전기를 아껴야 돈도 그만큼 더 벌린다. 즉 우주선이 한대도 없다면 그 때 전기를 아끼기 위해 꺼야한다
.
기억이 가물가물한 사람을 위해 다시 한번 말하면 우주선은 어플리케이션에 비교된다. 즉 어플리케이션이 COM 컴포넌트를 참조하고 있는지 확인하면서 COM 컴포넌트는 스스로 언제 사라져야 할 지 아는 것이다. 그럼 여기서 쿼리인터페이스는 무엇인가? 모든 인터페이스가 IUnknown 에서 상속 받는다고 했으니 I지구인 인터페이스에도 쿼리인터페이스 메서드가 있다. 이것은 어디다 쓰는 걸까
?
외계인이 지구인 출입구로 왔다. 근데 자신이 쓰려는 미네랄연료와 미네랄식료품이 없는 것이다 그래서 묻는 것이다. 외계인 도킹장치는 어디 있냐고. 그럼 지구인 출입구는 쿼리인터페이스 메서드를 사용하여 외계인 출입구 위치를 알아와서 외계인에게 가르쳐 주는 것이다
.

IUnknown
얼마나 쉬운가? 왜 있어야 하는지 의문도 풀렸다. 책으로 공부하면 IUnknown 며칠을 잡고 있어도 이 놈이 뭐 하는 놈인지 잘 모른다. 적어도 난 그랬다
.
오늘 가장 중요한 IUnknown에 대해서 알아봤다
.


오늘은 여기까지 하자. 대부분 소스를 보는 순간 보기 싫은 마음이 굴뚝 같았을 것이다
.
이해한다. 나도 그렇다. 담은 것은 또 언제일지 나도 잘 모르겠다. 아직까지는 하루하루 가지만 내 성격상 언제 또 퍼질지 모른다. 한 번 퍼지면 1주일은 그냥 잠수 탄다. 기다리지 마라. 그냥 나오면 나왔구나 하고 생각하면 된다. 이 글을 읽었다는 자체가 마지막까지 다 읽었다는 가정 하에 말한다. 내 말을 100% 그대로 받아 들이면 큰일 난다는 것이다. 언제 또 오늘 처럼 말을 뒤집을지 모른다. 모든 것은 이해하기 위한 과정일 뿐이다
.
잠 온다
.


------------------------------------------------------------------

e-mail :
icoddy@hotmail.com
msn id : icoddy@hotmail.com
-
박성규 -
///

나의 COM(Component Object Model) 경험담 #4


자 이제 네 번째 입니다. 조만간 진짜 COM  컴포넌트를 구현 해 보겠지만, 아직 기초가 부족합니다. 조금만 참읍시다. 조금만 더하고 실제 코딩을 하겠습니다
.
이제 제 글을 읽는 분들은 뒤에 말을 하지 않더라도 아실 겁니다
.

그럼 시작하겠습니다
.

------------------------------------------------------------------

잠시 잡담 좀 하자. 잡담이 제일 재미 있지 않나? 머리 아프게 COM 어쩌고 저쩌고 이 젊은 나이에 뭐란 말인가? 여러분들은 이 글을 읽는 목적이 무엇인가? COM을 알기 위해서? 아니면 COM 컴포넌트를 직접 만들고 싶어서? 그냥 나의 말투가 재미 있어서? 아니면 정말 심심해서인가
?
내가 처음 COM을 공부할 때는 아무런 목적 의식이 없었던 것 같다. 남들이 잘 안하고 어렵고 MS에서 아주 강조하는 중요한 기술이라서 하려고 했었다. 결국, 돌아온 것은 COM앞에서 비참하게 무너지는 내 자신이었다
.
이제는 목적의식을 가져야 한다. 그래야 머리에도 잘 들어 온다. 그런 의미에서 내가 생각하는 COM을 해야 하는 이유를 말하겠다. 게임 프로그래밍을 하는 사람은 DirectX를 해야 하고 인터넷과 관련된 사람들은 ActiveX ASP를 할 것이다. 그리고 엔터프라이즈급의 프로그래밍을 하는 사람은 DCOM COM+ 도 해야 한다. 여기 모든 곳에 COM이 숨어있다. DirectX를 처음 코딩 해 본 사람은 그 코딩 방식에 당황하지 않은 사람은 없었을 것이다. 일반 Win32 API로 코딩 하는 것과는 너무나 틀렸기 때문이다. COM을 모르는 사람은 그냥 책에 그렇게 써 있으니 따라 하면서 구현한다. 대부분 이렇지 않나 싶다
.
프로그래밍의 분야에서는 오히려 COM과 관련이 없는 분야가 더 적다고 해도 과언이 아니다. MS가 운영체제 내부를 거의 COM으로 도배를 해 놓았으니 어쩔 수 없다. 그리고 일반 어플리케이션 프로그래머들도 이제는 적응을 해야 한다. 지금까지는 CPrettyButton 같은 일반 클래스들을 복사해서 사용해 왔지만, 서서히 COM 컴포넌트를 사용해야 할 것이다
.
그리고난 절대 COM과 관련된 프로그래밍을 하지 않아라고 장담하는 사람들 조차도 자신도 모르게 사용하고 있다는 것을 알고 있는 지 모르겠다. 쉘이 바로 그 대표적인 예이다. 단축 아이콘을 만들고, 아이콘 트레이를 사용하고 하는 것에서  실제 코딩에는 COM과 관련된 부분이 없을지라도 내부적으로 COM을 사용한다는 것을 말이다
.
바로 이런 이유에서 COM을 알아야 한다. 더 가치 있고 더 비싸게 팔 수 있는 프로그램을 만들려면 COM이 필수인 것이다. 자 이제 여러분들의 몸값을 올릴 수 있는 COM을 계속 해보자.(그렇다고 내 글을 읽는 다고 해서 몸값이 올라가진 않는다. 자신과의 끝없는 싸움을 해야 하는 일이다
.)

#3
에서 짜가 COM 컴포넌트를 구현해보면서 IUnknown 인터페이스에 대해서 알아 봤다
.
그럼 오늘은 뭘 할 것인가
?
바로 IClassFactory 인터페이스 이다. IUnknown이 어떤 일을 하는 지 알았으니 이제는 IClassFactory가 어떤 일을 하는 놈인지 알아보자
.
먼저 이 부분은 조금 어렵다. 그래서 하나의 전제를 가지고 시작하겠다
.
어렴풋이나마 이 말을 기억한 상태에서 읽어 나가자
.

클래스팩토리는 메인 컴포넌트를 생성하기 위한 보조 컴포넌트이다. 이 보조 컴포넌트는 오직 하나의 인터페이스 IClassFactory만을 가지고 있다
.

여기서 왜 알아 봐야 하는데? 라고 의문을 가지는 사람이 있을 것이다. 그렇지 않는 사람은 반성해야 한다. 내가 무슨 말을 하건 딴지 걸 생각부터 해야 발전 할 수 있다. 누가 무슨 말을 해도 마찬가지이다. 교수님이 강의 할 때도 딴지 걸 준비부터 해봐라. 강의가 재미 있어진다.(그렇다고 실제로 하진 마라. 점수 제대로 못 받는다
.)
COM
인터페이스는 크게 두 가지로 나뉘어진다. 표준 COM 인터페이스와 커스텀 COM 인터페이스이다
.
COM
에 의해 기본적으로 제공되는 인터페이스는 표준 COM 인터페이스이고 프로그래머가 특정한 목적에 따라 새로 정의한 인터페이스가 커스텀 COM 인터페이스이다. IUnknown, IClassFactory는 바로 표준 COM 인터페이스에 속하는 것들이다. 따라서 우리는 표준 COM 인터페이스를 이해하지 않고서는 COM을 이해하는 것이 불가능하므로 무조건 이해하고 넘어가야 한다
.


Factory -
해석해보면 공장이라는 말이다. ‘공장 : 뭘 만드는 곳 이잖아?’ 바로 COM 컴포넌트의 인스턴스를 생성하는 놈이 이놈이다. 즉 이놈도 IUnknown 만큼이나 중요한 놈이었다. 그럼 이놈을 찬찬히 뜯어보자.(그렇다고 이놈이 실제로 생성하는 건 쥐뿔도 없다. 앞에서 우리가 IUnknown의 쿼리인터페이스를 직접 한 것과 같이 우리가 일일이 new 하면서 COM 개체를 생성해 줘야 한다. 이 부분은 나중에 보면 알 것이다
.)

#1
에서 CoCreateInstance 함수에서 COM 컴포넌트를 만든다고 했는데 이건 또 무슨 소리냐 하는 분들이 있을 것이다. 이유를 설명하겠다
.

사실, 난 소스 가지고 이러쿵저러쿵 설명하는 거 제일 싫어 한다. 짜증난다. 지겹다. 책 덮고 싶어진다. 그런데 내가 그렇게 하게 생겼다. 하지 말아 버릴까 보다. 일단 오늘 한번만 해보고 반응을 봐야 겠다. 소스를 가지고 설명이 시작되면 일반 책과 크게 틀려지지 않기 때문이다. ‘여러분 제 할일 끝났으니 이제 책 보세요~~’ 이거랑 같은 말인 것이다. 아 서서히 잠수 타고 싶어진다
.

어쨌든, 그건 그거도 우리는 하던 얘기를 계속 해야 한다
.
자 다음 코드를 먼저 살펴보자
.

STDAPI CoCreateInstance(
REFCLSID rclsid,
LPUNKNOWN pUnkOuter,
DWORD dwClsContext,
REFIID riid, LPVOID * ppv
)
{
        *ppv = NULL;
        IClassFactory* plFactory = NULL;
        HRESULT hr = CoGetClassObject(
rclsid,
dwClsContext,
NULL,
IID_IClassFactory,
(LPVOID*)&pIFactory
);

        If(SUCCEEDED(hr)){
                Hr = pIFactory->CreateInstance(pUnkOuter, riid, ppv);
                pIFactory->Release();
        }
        return (hr);
}

여기서 직감적으로 pIFactory->CreateInstance 에서 컴포넌트가 생성될 것이라는 것을 알 수 있다. 결국 CreateInstance 메서드는 IClassFactory 인터페이스의 메서드였다. 어쨌든, CoCreateInstance 에서 만든다고 해도 틀린 말은 아니었다. 내부에서 생성하니 말이다.(우길 사람은 우겨라. 친구랑 CoCreateInstance에서 생성한다. 아니다. CreateInstance에서 생성한다고 싸우는 것은 어리석은 짓일 것이다
.)
그럼 왜 IClassFactory인터페이스가 있어야 하는지도 답이 나왔다
.
#2
의 내용을 상기해보자


1. CoInitialize(NULL);
2. CoCreateInstance
에 인자로 넘길 각종 변수 선언 및 초기화;
3. CoCreateInstance(clsid, ……);
4. COM
컴포넌트 사용

5. CoUninitialize();

이것은 COM 컴포넌트를 사용하는 전체적인 흐름이다라고 했다.
여기서 CoCreateinstance를 호출할 때 내부에서 필요로 하는 것이 바로 IClassFactory이기 때문이다
.

위의 코드는 COM 라이브러리 내부에 구현되어 있는 부분이다. COM 라이브러리는 또 뭐야 하는 분이 있을 거다. 그건 COM을 사용 가능하게 해주는 일종의 API라고 생각하면 되겠다
.
좀더 자세히 말하자면, COM을 사용하는 모든 어플리케이션에서 유용하게 사용될 수 있는 컴포넌트 관리 서비스를 제공해 준다는 말이다. 구체적인 예를 들면, CLSID를 가지고 레지스트리에서 실제 COM 서버가 어디에 위치하는지를 찾아낸다거나 COM 개체를 생성하고, 메모리를 관리해주는 등등의 일을 해주는 기본적인 기능을 하는 것도 이 COM 라이브러리가 해주는 역할이다. Co- 어쩌고 저쩌고 시작하는 함수는 전부 COM 라이브러리라고 함수라고 생각하면 되겠다
.

앞에서 모든 인터페이스는 IUnknown에서 상속 받는다고 했으니 IClassFactory 역시 예외는 아닐 것이다. 그럼 어떻게 선언 되어 있는지 한번 구경 해보자. IClassFactory COM 컴포넌트가 반드시 제공해야 하는 인터페이스이므로 주의 깊게 보자
.

Interface IClassFactory : public IUnknown
{
        virtual HRESULT __stdcall CreateInstance(
LPUNKNOWN pUnkOuter,
REFIID riid,
LPVOID* ppv) = 0;
        virtual HRESULT __stdcall LockServer(BOOL block) = 0;
}

그냥 메서드 두개를 추가한 것이 다다. 우리가 오버라이드해야 할 메서드가 두 개 늘었다는 것이다. 그 이상도 그 이하도 아니다. 앞으로 얼마나 더 오버라이드를 해야 할까? 두고 보 자. (기뻐해라~~~~~~~~~~~~~~ 사실은 이 두개의 인터페이스만 하면 실제 COM 컴포넌트의 구현은 어느 정도 가능해진다. 그럼 다음에는 진짜 COM을 만들어서 테스트의 기쁨을 누려보자
.)
그럼,  C정거장Factory를 만들 때 이렇게 만들면 되겠군
..

class C
정거장
Factory: public IClassFactory
{
public:
            //interface IUnknown methods
            HRESULT __stdcall QueryInterface(REFIID riid , void **ppObj);
            ULONG   __stdcall AddRef();
            ULONG   __stdcall Release();


            //interface IClassFactory methods
            HRESULT __stdcall CreateInstance(IUnknown* pUnknownOuter,
                                                 const IID& iid,
                                                 void** ppv) ;
            HRESULT __stdcall LockServer(BOOL bLock) ;

private:
            long m_nRefCount;
};


한 김에 바로 두 메서드를 오버라이드 해보자
.

HRESULT __stdcall C
정거장
Factory::CreateInstance(IUnknown* pUnknownOuter,
                                           const IID& iid,
void** ppv)
{
        // aggregate
을 지원하지 않는다
.
        if (pUnknownOuter != NULL)
        {
                        return CLASS_E_NOAGGREGATION ;
        }

        //
실제 COM 컴포넌트 인스턴스를 생성한다
.
        C
정거장* pObject = new C정거장
;
            if (pObject == NULL)
                {
                        return E_OUTOFMEMORY ;
                }

        //
클라이언트에서 요청한 인터페이스를 요청한다
.
        return pObject->QueryInterface(iid, ppv) ;
}

CreateInstance 메서드는 CoGetClassobject 함수에 전달된 CLSID 에 대응되는 COM 컴포넌트의 인스턴스만을 생성한다. 이 말은 CLSID가 인자로 필요 없다는 뜻이다. 하나의 COM 컴포넌트와 클래스팩토리는 한 쌍으로 묶여 있기 때문이다
.

참고로 CoGetClassobject COM 라이브러리 함수인 것을 알 수 있다. 앞에 Co가 붙었으니 말이다. 이 함수가 하는 일은 레지스트리에서 CLSID와 연결된 COM 컴포넌트 서버를 메모리에 로드 한다
.

HRESULT __stdcall C
정거장
Factory::LockServer(BOOL bLock)
{
        return E_NOTIMPL;
}

LockServer는 왜 필요한가? 앞에서 정거장을 예로 들면서 우주선이 하나도 없으면 정거장의 전기를 모두 내린다고 했다. 그런데, 잘못하여 우주선의 수를 잘못 계산하게 되어 버린 것이다. 그러면 우주선이 있는데도 전기를 차단시켜 버렸고 정거장은 그 우주선 사람들로부터 욕을 바가지로 얻어 먹게 될 것이다. 이 문제에 대한 보험이 바로 이 메서드라고 생각하면 되겠다. COM 컴포넌트가 여러 클라이언트에서 사용하는 것이 확실 하다면

다음과 같이 수정하면 된다.

HRESULT __stdcall C
정거장
Factory::LockServer(BOOL bLock)
{
        if(block)
                ++g_cLocks;
        else
                --g_cLocks;

        return S_OK;
}

이렇게 되면 컴퍼넌트의 언로드 시기를 검사할 때 두 가지를 검사하면 된다. 앞에서 구현한 우주선의 수가 0이고 g_clocks 0일 때 그렇게 하면 된다는 말이다. 우주선의 수가 0인지만 검사하는 것보다는 안정적일 것이다
.

IClassFactory도 끝났다. 하지만, IUnknown 에 비해 어렵다고 생각할 것이다. 지금은 그냥 IUnknown과 같이 구현해야 하는 필수 과정이라고만 생각하자
.

우리가 COM 컴포넌트 프로그래밍을 하다 보면 짜고 치는 고스톱이라는 느낌을 받게 된다. 말 그대로 IUnknown 이랑 IClassFactory는 이런 메서드들을 구현해라 하고 우리는 COM 컴포넌트를 만들면서 거의 내용이 바뀌지 않는 코딩을 어쩔 수 없이 강요 당하게 된다
.
IUnknown
IClassFactory는 해 주는 것이 전혀 없다. 그냥 이런 메서드 구현 해라 라면서 은근히 강요만 할 뿐이다. 내가 다한(?) 거나 마찬가지인 것이다. 그리고 이 부분의 코딩은 MS가 하라는 데로 따라 하기만 하면 된다. 내가 맘대로 건드릴 부분도 아니다
.

오늘은 IClassFactory를 이해 하려고 노력했다. 하지만, 아직 감이 오질 않는다
.
다시 한번 이것을 이해하는 방법은 이렇게 생각하는 것이 쉽다
.
클래스팩토리는 메인 컴포넌트를 생성하기 위한 보조 컴포넌트이다. 이 보조 컴포넌트는 오직 하나의 인터페이스 IClassFactory만을 가지고 있다
.


내용이 점점 복잡해진다. 나도 두서없이 말하고 또 말하고 그런 느낌이다. 앞으로는 좀 머리 속으로 정리 좀 하고 올려야 겠다. 그럼 아무래도 시간이 걸리겠쥐
?

오늘도 고생들 하셨다. 오늘은 좀 구체적으로 나갔다. 그런데 이게 도움이 될지 모르겠다. 차라리 책이 더 낳지 않을까 싶다. 물론 내가 설명하는 방식과 책은 차이가 있을 것이다. 책보다 더 설명을 잘 할 자신은 없다. 왠지 이제는 갈림길에 선 느낌이다
.
오늘은 푹 자야겠다. 맨날 비몽사몽간에 글을 써서 미안한 감이 없지 않지만, 낮에 한번 읽어보니 그렇게 엉터리 말은 없는 것 처럼 보인다. 사실, 내가 미안해 할 이유도 없다. 그렇지 않나? 읽기 싫으면 안 읽으면 그만인데.         .ㅡ ㅡ;(밤길 가다 칼 맞을라
..)

------------------------------------------------------------------

e-mail :
icoddy@hotmail.com
msn id : icoddy@hotmail.com
-
박성규 -
///

나의 COM(Component Object Model) 경험담 #5


드디어 다섯번째 까지 왔습니다
.
얻으신 것이 있었나요? 없었다구요? ㅜㅜ
;;
어쨌든 그건 상관 없습니다. 이번은 내용이 좀 깁니다. 그렇다고 할 게 많은 것은 아닙니다
.
자세한건 나중에 뒤에 보시면 아실 테고
.
생각보다 많은 분들이 좋아해 주셨습니다. 기분이 좋았습니다. 잠시 개인적인 얘기를 하려고 합니다. 저의 일과는 이렇습니다
.
회사 퇴근해서 밥 먹고인어 아가씨본 다음(이거 정말 재미 있습니다. 요즘은 이거 보는 낙으로 살죠 ^^) 바로 9시 뉴스 보면서 세상이 어떻게 돌아가나 봅니다
.
그리고 요 며칠(4일 동안인가 봅니다.) 이 글 쓰느라 보통 3 ~ 4시간 보내고 새벽 1시나 3시쯤에 글 데브피아에 올린 다음 잡니다. 그래서 비몽사몽간에 글을 씁니다. 따라서 주절주절 앞뒤 안 맞는 얘기도 많았을 겁니다. 하루를 쉬니 머리가 조금은 맑아 졌습니다. 그래도 앞뒤가 잘 안 맞을 겁니다.(원래 이렇습니다. 비몽사몽 어쩌고는 다 핑계입니다. ^^) 오늘은 시간이 꽤 걸릴 것 같습니다. 잠 못 잘 지도 모릅니다. 회사에서 요즘 밤에 뭐 하느냐고 의심의 눈초리로 째려봅니다. [ㅡ ㅡ
]
잡담 그만하고 빨리 하라구요? ! 알겠습니다
.

그럼 시작하겠습니다
.

------------------------------------------------------------------

오늘 설명 하지 않겠다. 설명은 #6에서 본격적으로 하겠다
.
오늘은 그냥 흐름을 파악하면 된다. 그냥 이렇게 만들면 되는 구나 라고 생각만 하면 될 듯 싶다. (중간에 설명이 없더라도 섭섭하게 생각하지 마라
.)
COM
컴포넌트를 만드는 방법은 다양하다. 사람이라는 것이 간사해서 한번 맛들인 방법을 끝까지 고집하게 된다. 그래서 ATL을 사용해본 사람은 절대 다른 방법으로 하려고 하지 않는다. 하지만, ATL COM을 완벽하게 지원하지 않는 다는 걸 알아야 한다. 그렇다고 ATL에서 지원 하는 것 이상 만들 자신도 없지만 말이다. 그래도 기분 나쁘잖아~~. 있어 보이는 척 하는 거 빼면 시체인 난데.. 그럴 순 없쥐
~.
오늘 여러분은 개념 이해하느라 머리 쥐어 뜯을 일이 없다. 그냥 아무 생각 없이 따라 하기만 하면 된다. 그럼 COM 서버 한번 만들어 보자. (서버란 말이 나오니, 왠지 서버 프로그래머가 된 것 같다
. ^^)
먼저 오늘 만들 놈에 대해서 미리 어떤 일을 하는 놈인지 알고 들어가면 쉬울 것이다. 이번에 만들 COM 컴포넌트가 하는 일은 두 숫자를 입력 받아서 더한 결과를 돌려준다. 간단하다. 더 이상 말이 필요없다. 대부분의 COM 예제들이 이런 걸로 알고 있다
.
그리고 먼저 밝혀 둘 것이 있다. 이 소스는 codeguru 사이트의 COM 란에서 가져온 소스를 내 나름대로 조금 편집한 소스이다. 모든 기능을 죽이고 우리나라 정서(?)에 맞게 조금 수정했다. (아무래도 내가 만들면 사람들이 믿지 않을 것 같다
.)
그럼 시작해보자
.
먼저 새 프로젝트를 열자
.



아주 친숙한 화면이 나타났다. COM을 만든다고 ATL COM AppWizard를 선택하면 안 된다.
Win32 DLL
을 선택하고 프로젝트 명을 넣자. 다 끝났다면 OK 버튼을 누른다
.



An empty DLL project
를 선택하고 Finish 버튼을 누른다.
자 기본적인 준비는 끝났다
.
그리고 이제 .idl 파일이 필요하다. 인터페이스를 정의하는 파일이다. 그러려면 GUID도 하나 필요하다. GUID를 쉽게 만드는 방법이 있다
.
실행에서 다음과 같이 명령어를 입력해 보자
.



그럼 다음과 같은 창이 하나 뜬다.



말 그대로 GUID를 랜덤하게 계속 만들 수 있다.
여기서 하나를 만들어서 Copy 버튼을 누른다
.
그리고 소스에서 GUID가 필요한 부분에서 Paste 하면 나타난다
.

새 파일을 하나 열고 다음과 같이 타이핑한다. 아니다. Copy & Paste 하면 되겠다
.

import "unknwn.idl";

[
uuid(12D90058-C2A5-4950-8355-1DC0189FFD0D),
helpstring("
여기에 필요한 설명을 적는다
.")
]
interface IAdd : IUnknown
{
    HRESULT     SetFirstNum(long nFirst);
    HRESULT     SetSecondNum(long nSecond);    
    HRESULT     GetSum([out, retval] long *pBuffer);
}

[
uuid(9D807A19-9A1A-4879-A7C0-6D3AFD04F7B8),
helpstring("
라이브러리에 대한 설명을 적는다
.")
]
library AddComObjLib
{
importlib("stdole32.tlb");
importlib("stdole2.tlb");

    interface IAdd;    
}

여기서 잠시 IDL이 뭔지 알아보자
.
IDL(Interface Definition Language)
해석하면 인터페이스 정의 언어란 말이다. 일반적으로 인터페이스는 C++ 언어로 표현이 가능했다. , 순수가상함수로 만들 수도 있다. 하지만, IDL로 만드는 것이 여러모로 노가다 작업을 줄일 수 있다. 그리고 그렇게 어렵지도 않다
.
이 언어는 MIDL(Microsoft Interface Definition Language) 컴파일러로 컴파일 할 수 있다
.

도스창에서 midl IAdd.idl 이렇게 실행하면 컴파일 된다. 여기서 여러가지 부산물들을 얻을 수 있다. 우리가 사용할 수 있는 헤더파일도 만들어 준다. 그리고 가장 중요한 것은 나중에 설명하겠지만, Proxy Stub 코드를 만들어 준다는 것이다. 그리고 또 한가지 타입라이브러리를 만들어 준다는 것이다
.
대충 이렇게 알고 넘어가자. 이 부분도 하루종일 해야 하는 부분들이다. 이 쪽을 파다가는 오늘 실습은 그냥 포기해야 한다
.











그럼 다시 노가다 작업으로 들어가자. IAdd.idl 로 파일이름을 저장하고 Project메뉴를 사용해서 프로젝트에 추가한다
.



그럼 다음과 같이 나타날 것이다.



Project
메뉴에서 Settings.. 메뉴를 클릭한다.
다시 거기서 Iadd.idl 파일을 선택한다. 컴파일을 먼저 해보기 위해서다
.

Always use custom build step
를 체크하고 다음 텝을 선택한다
.



위의 그림과 같이 명령어를 입력한다. 이 과정이 귀찮으면 도스창에서 직접 ‘midl IAdd.idl’ 을 직접 입력해도 상관없다. 결과는 같은 테니깐 말이다.



그럼 다음과 같이 같은 폴더에 5개의 파일이 덤으로 생성된다.

그리고 추가로 COM 컴포넌트를 다 만들고 나서 레지스트리에 추가하는 것도 귀찮으니 그것도 설정을 미리 해버리자. 아래 그림과 같이 프로젝트를 선택하고 마지막 텝의 Post-build step를 선택하고 명령어를 입력한다. 그러면 컴파일이 끝나면 알아서 이 명령어를 실행시킨다
.



그 다음 부터는 아래의 파일들을 전부 위의 주석에 나온 파일 이름대로 다 저장하자.
그리고 다 끝났다면 *.cpp *.def 파일을 프로젝트에 추가한다
.


, 그럼 지금부터 노가다를 좀 해라
.


나는 노가다 하는 동안 좀 쉬어야 겠다. 커피도 고프고 담배도 고프다
.





~
~
~
~
~
~
~
////////////////////////////////////////////////////////////////////
// AddComObj.h
파일
////////////////////////////////////////////////////////////////////
#include    "IAdd.h"

extern long g_nComObjsInUse;

class CAddComObj : public IAdd
{
private:
    long m_nFirst , m_nSecond; //operands for addition        
    long m_nRefCount;   //for managing the reference count        

public:        
    //IUnknown
인터페이스의 메서드를 구현한다.
    HRESULT __stdcall QueryInterface(REFIID riid, void **ppObj);
        ULONG   __stdcall AddRef();
        ULONG   __stdcall Release();
       
    //IAdd
인터페이스의 메서드들
...
    HRESULT __stdcall SetFirstNum( long nFirst);
    HRESULT __stdcall SetSecondNum( long nSecond);
    HRESULT __stdcall GetSum( long *pBuffer);
                   
    CAddComObj()
        {
        m_nRefCount=0;        
        InterlockedIncrement(&g_nComObjsInUse);      
        }       
       
    ~CAddComObj()
        {
        InterlockedDecrement(&g_nComObjsInUse);                
        }           
};

////////////////////////////////////////////////////////////////////
// AddComObj.cpp
파일

////////////////////////////////////////////////////////////////////
#include    
#include    "AddComObj.h"
#include    "IAdd_i.c"

/////////////////////////////////////////////////////////////////////
// IAdd
인터페이스의 메서드들을 구현한다.
//
// SetFirstNum :
더할 숫자들 중 첫번째 수를 지정한다
.
// SetSecondNum :
더할 숫자들 중 두번째 수를 지정한다
.
// GetSum :
두 숫자의 합을 얻어 온다
.
/////////////////////////////////////////////////////////////////////
HRESULT __stdcall CAddComObj::SetFirstNum(long nFirst)
{
    m_nFirst = nFirst;
    return S_OK;
}

HRESULT __stdcall CAddComObj::SetSecondNum(long nSecond)
{
    m_nSecond = nSecond;
    return S_OK;
}

HRESULT __stdcall CAddComObj::GetSum(long *pBuffer)
{
    *pBuffer = m_nFirst + m_nSecond;        
    return S_OK;
}

/////////////////////////////////////////////////////////////////////
// IUnknown
인터페이스의 메서드들을 구현한다
.
//
다음의 3개 메서드가 기본이쥐
~~~~~
// AddRef()
// Release()
// QueryInterface(REFIID riid, void **ppObj)
/////////////////////////////////////////////////////////////////////
ULONG   __stdcall CAddComObj::AddRef()
{
        return InterlockedIncrement(&m_nRefCount);
}

ULONG   __stdcall CAddComObj::Release()
{    
        long nRefCount = 0;
        nRefCount = InterlockedDecrement(&m_nRefCount);
       
        //
참조 카운트가 없으면 스스로 해제한다
.
        if (nRefCount == 0) delete this;

        return nRefCount;
}

HRESULT __stdcall CAddComObj::QueryInterface(REFIID riid, void **ppObj)
{
    if (riid == IID_IUnknown)
        {
                *ppObj = static_cast(this);
        AddRef();
        return S_OK;
        }
       
    if (riid == IID_IAdd)
        {
                *ppObj = static_cast(this);
        AddRef();
        return S_OK;
        }
       
   
    *ppObj = NULL;
    return E_NOINTERFACE;
}


////////////////////////////////////////////////////////////////////
// AddComObjFactory.h
파일

////////////////////////////////////////////////////////////////////
extern long g_nComObjsInUse;

class CAddComObjFactory : public IClassFactory
{
private:
    long m_nRefCount;

public:
    CAddComObjFactory()
        {
        m_nRefCount=0;
        InterlockedIncrement(&g_nComObjsInUse);
        }
       
    ~CAddComObjFactory()
        {
        InterlockedDecrement(&g_nComObjsInUse);
        }
       
       
    // IUnknown
인터페이스의 메서드들...
    HRESULT __stdcall QueryInterface(REFIID riid, void **ppObj);        
        ULONG   __stdcall AddRef();
        ULONG   __stdcall Release();    
       
       
        // IClassFactory
인터페이스의 메서드들
...
        virtual HRESULT __stdcall CreateInstance(IUnknown* pUnknownOuter,
                const IID& iid,
                void** ppv) ;
        virtual HRESULT __stdcall LockServer(BOOL bLock) ;
};

////////////////////////////////////////////////////////////////////
// AddComObjFactory.cpp
파일

////////////////////////////////////////////////////////////////////
#include    
#include    "AddComObjFactory.h"
#include    "AddComObj.h"

/////////////////////////////////////////////////////////////////////
// IUnknown
인터페이스의 메서드들을 구현한다.
//
// AddRef
// Release
// QueryInterface  
/////////////////////////////////////////////////////////////////////
ULONG   __stdcall CAddComObjFactory::AddRef()
{
        return InterlockedIncrement(&m_nRefCount) ;
}

ULONG   __stdcall CAddComObjFactory::Release()
{
        long nRefCount = 0;
        nRefCount = InterlockedDecrement(&m_nRefCount);
        if (nRefCount == 0) delete this;
        return nRefCount;
}

HRESULT __stdcall CAddComObjFactory::QueryInterface(const IID& iid, void** ppv)
{    
        if ((iid == IID_IUnknown) || (iid == IID_IClassFactory))
        {
                *ppv = static_cast(this);
        }
        else
        {
                *ppv = NULL;
                return E_NOINTERFACE;
        }

        reinterpret_cast(*ppv)->AddRef();
        return S_OK;
}

/////////////////////////////////////////////////////////////////////
// IClassFactory
인터페이스의 메서드들을 구현한다
.
//
// LockServer
// CreateInstance
/////////////////////////////////////////////////////////////////////
HRESULT __stdcall CAddComObjFactory::CreateInstance(IUnknown* pUnknownOuter,          const IID& iid,                                                                          void** ppv)
{
    // Aggregation
을 사용하지 않는다
.
        if (pUnknownOuter != NULL)
        {
                return CLASS_E_NOAGGREGATION;
        }
       
    CAddComObj* pObject = new CAddComObj;
        if (pObject == NULL)
        {
                return E_OUTOFMEMORY;
        }
       
    return pObject->QueryInterface(iid, ppv);
}

HRESULT __stdcall CAddComObjFactory::LockServer(BOOL bLock)
{
        return E_NOTIMPL;
}
////////////////////////////////////////////////////////////////////
// AddComObjGuid.h
파일

////////////////////////////////////////////////////////////////////
#ifndef __AddObjGuid_h__
#define __AddObjGuid_h__

// // {4EE0DC95-64F3-4ad6-A1FC-191A9FAEA849}
static const GUID CLSID_AddObject =
{ 0x4ee0dc95, 0x64f3, 0x4ad6, { 0xa1, 0xfc, 0x19, 0x1a, 0x9f, 0xae, 0xa8, 0x49 } };

#endif



////////////////////////////////////////////////////////////////////
// Exports.cpp
파일
////////////////////////////////////////////////////////////////////
#include    
#include    "AddComObj.h"
#include    "AddComObjFactory.h"
#include    "AddComObjGuid.h"

HMODULE g_hModule = NULL;
long g_nComObjsInUse = 0;

///////////////////////////////////////////////////////////////////////////////
//
여기가 DllMain 이다.
// DLL_PROCESS_ATTACH : DLL
이 프로세스의 주소 영역에 맵핑된다
.
///////////////////////////////////////////////////////////////////////////////
BOOL APIENTRY DllMain(HANDLE hModule, DWORD dwReason, void* lpReserved)
{
        if (dwReason == DLL_PROCESS_ATTACH)
        {
                g_hModule = (HMODULE)hModule ;
        }

        return TRUE ;
}

///////////////////////////////////////////////////////////////////////////////
// COM
라이브러리의 CoGetClassObject 함수에서 DLL DllGetClassObject함수를 호출하고
,
//
이 함수에서 실제로 클래스팩토리 COM 개체를 생성하게 된다
.
///////////////////////////////////////////////////////////////////////////////
STDAPI DllGetClassObject(const CLSID& clsid, const IID& iid, void** ppv)
{
    if (clsid == CLSID_AddObject)
        {
                // CAddComObjFactory
를 생성한다
.
        CAddComObjFactory *pAddFact = new CAddComObjFactory;

        if (pAddFact == NULL)
                {
            return E_OUTOFMEMORY;
                }
        else
                {
            return pAddFact->QueryInterface(iid , ppv);
                }
        }   
       
    return CLASS_E_CLASSNOTAVAILABLE;
}

///////////////////////////////////////////////////////////////////////////////
// COM
라이브러리의 CoFreeUnusedLibraries 함수는 DLL DllCanUnloadNow 함수를 호출한다
.
//
, DLL을 해제 시켜도 좋은지 물어본다
.
///////////////////////////////////////////////////////////////////////////////
STDAPI DllCanUnloadNow()
{
    if (g_nComObjsInUse == 0)
        {
        return S_OK;
        }
    else
        {
        return S_FALSE;
        }
       
}







;Experts.def
파일이다
.
DESCRIPTION     "Simple COM object"

EXPORTS
                DllGetClassObject           PRIVATE
                DllCanUnloadNow             PRIVATE
                DllRegisterServer           PRIVATE
                DllUnregisterServer         PRIVATE





////////////////////////////////////////////////////////////////////
// Registry.h
파일

////////////////////////////////////////////////////////////////////
#ifndef __Registry_H__
#define __Registry_H__

HRESULT RegisterServer(HMODULE hModule,
                       const CLSID& clsid,
                       const char* szFriendlyName,
                       const char* szVerIndProgID,
                       const char* szProgID) ;

HRESULT UnregisterServer(const CLSID& clsid,
                         const char* szVerIndProgID,
                         const char* szProgID) ;

#endif






////////////////////////////////////////////////////////////////////
// Registry.cpp
파일
////////////////////////////////////////////////////////////////////
#include    
#include    
#include    "AddComObjGuid.h"

#define     AddObjProgId    "IcoddyLib.Sum"

extern HMODULE g_hModule;


BOOL   HelperWriteKey(HKEY roothk, const char *lpSubKey, LPCTSTR val_name, DWORD dwType, void *lpvData, DWORD dwDataSize)
{
       
    HKEY hk;
    if (ERROR_SUCCESS != RegCreateKey(roothk,lpSubKey,&hk) ) return FALSE;
       
    if (ERROR_SUCCESS != RegSetValueEx(hk,val_name,0,dwType,(CONST BYTE *)lpvData,dwDataSize)) return FALSE;
   
    if (ERROR_SUCCESS != RegCloseKey(hk))   return FALSE;

    return TRUE;
       
}

// COM
개체를 시스템 레지스트리에 등록할 때 Regsvr32.exe 에 의해 호출 된다.
HRESULT  __stdcall DllRegisterServer(void)
{
    WCHAR *lpwszClsid;
    char szBuff[MAX_PATH]="";
    char szClsid[MAX_PATH]="", szInproc[MAX_PATH]="",szProgId[MAX_PATH];
    char szDescriptionVal[256]="";
       
    StringFromCLSID(CLSID_AddObject, &lpwszClsid);
   
    wsprintf(szClsid,"%S",lpwszClsid);
    wsprintf(szInproc,"%s\\%s\\%s","clsid",szClsid,"InprocServer32");
    wsprintf(szProgId,"%s\\%s\\%s","clsid",szClsid,"ProgId");
       
       
    wsprintf(szBuff,"%s","icoddy's sum");
    wsprintf(szDescriptionVal,"%s\\%s","clsid",szClsid);
       
    HelperWriteKey (HKEY_CLASSES_ROOT, szDescriptionVal, NULL, REG_SZ, (void*)szBuff, lstrlen(szBuff));
       
       
    GetModuleFileName(g_hModule, szBuff, sizeof(szBuff));
    HelperWriteKey (HKEY_CLASSES_ROOT, szInproc, NULL, REG_SZ, (void*)szBuff, lstrlen(szBuff));
       
    lstrcpy(szBuff,AddObjProgId);
    HelperWriteKey (HKEY_CLASSES_ROOT, szProgId, NULL, REG_SZ, (void*)szBuff, lstrlen(szBuff));
       
       
    wsprintf(szBuff,"%s","icoddy's sum");
    HelperWriteKey (HKEY_CLASSES_ROOT, AddObjProgId, NULL, REG_SZ, (void*)szBuff, lstrlen(szBuff));
       
       
    wsprintf(szProgId,"%s\\%s",AddObjProgId,"CLSID");
    HelperWriteKey (HKEY_CLASSES_ROOT, szProgId, NULL, REG_SZ, (void*)szClsid, lstrlen(szClsid));
       
    return 1;        
}

// COM
개체를 시스템 레지스트리에 등록할 때 Regsvr32.exe 에 의해 호출 된다
.
HRESULT  __stdcall DllUnregisterServer(void)
{
    char szKeyName[256]="",szClsid[256]="";
    WCHAR *lpwszClsid;        
   
        wsprintf(szKeyName,"%s\\%s",AddObjProgId,"CLSID");
    RegDeleteKey(HKEY_CLASSES_ROOT,szKeyName);
    RegDeleteKey(HKEY_CLASSES_ROOT,AddObjProgId);
       
       
    StringFromCLSID(CLSID_AddObject, &lpwszClsid);
    wsprintf(szClsid,"%S",lpwszClsid);
    wsprintf(szKeyName,"%s\\%s\\%s","CLSID",szClsid,"InprocServer32");
    RegDeleteKey(HKEY_CLASSES_ROOT,szKeyName);
       
    wsprintf(szKeyName,"%s\\%s\\%s","CLSID",szClsid,"ProgId");
    RegDeleteKey(HKEY_CLASSES_ROOT,szKeyName);
       
    wsprintf(szKeyName,"%s\\%s","CLSID",szClsid);
    RegDeleteKey(HKEY_CLASSES_ROOT,szKeyName);
       
    return 1;        
}



여기가 끝이다
.
마지막으로 컴파일을 해보자
.



이렇게 나오면 성공한 거다.
진심으로 축하한다. 드뎌 COM 컴포넌트를 만든 것이다
.

그럼 이제 클라이언트 프로그램을 만들어서 실제로 잘 동작하는 지 확인 해야 한다. 내가 이 수고를 덜겠다. 해보니깐 잘된다. 밑에 그림 보이쥐? 그럼 됐다. 저거 조작한 거 아냐?라고 의심하는 사람은 잘 봐라. 결과가 읽기 속성으로 되어있다. 그래도 조작한 거라고
?
우쒸~그래 조작했다. 어쩔 건데? 여긴 할 게 별로 없으니 소스나 한번 훑어 보고 지나가라
.



결과 보기 버튼을 누르면 결과가 나온다.
void CAddComClientDlg::OnButtonSum()
{
        // TODO: Add your control notification handler code here
        UpdateData(TRUE);

        HRESULT hr;
        hr = CoInitialize(NULL);
        if(FAILED(hr))
        {
                AfxMessageBox("COM
라이브러리를 초기화 하지 못했습니다
.");
                SendMessage(WM_CLOSE);
        }

IcoddyLib::IAddPtr pIcoddyLib;    
pIcoddyLib.CreateInstance("IcoddyLib.Sum");
pIcoddyLib->SetFirstNum(m_nFirst);
            pIcoddyLib->SetSecondNum(m_nSecond);
            m_nSum = pIcoddyLib->GetSum();

        UpdateData(FALSE);
}
오늘은 따로 설명을 하지 않겠다. 오늘 이것을 나에게 요구한다면 나 그만 둘거다. 협박하는 거다. 절대 강요하지 마라. 기분 나쁘면 언제든지 그만둔다. 내 맘이다.(~~ 또 돌 날라오넹
)

, 여러분들 너무 지쳤을 것이다
.
설명은 다음 경험담 #6에서 본격적으로 하겠다. 열심히 깊이 팔 준비를 하고 오면 고맙겠다
.

오늘의 핵심은 이 과정을 기억하라는 것이다. 소스는 나중 일이다. 이 과정만 알면 다음에 언제든지 소스 참조 하면서 만들면 된다
.


------------------------------------------------------------------


~~~ 날 다 샜다
.
대충 예상은 했었지만, 정말 이렇게 될 줄이야
.
TV
좀 보다가 씻고 출근해야 한다. 밥은 뭐 해먹지? .. 즉석 미역국이 있었네. 그거 해 먹어야 겠다. 800원인데 두개 들어 있다. , 한 개 400원 꼴이니, 정말 싸고 맛있게 먹는 거다. 여러분도 라면으로 끼니 떼우지 말고 이런 거 사먹어라. 이건 3분도 안 걸린다. 몸에도 좋다. 아침 먹는 사람이 그렇지 않은 사람보다 수명이 10년이 더 길다고 한다. 진짜다. 믿어라.(사실은, 나도 귀찮아서 일주일에 한번도 아침 못 먹는다
.)

그럼 날밤 새도 끄떡없는 건강한(?) 프로그래머 세계를 꿈꾸며 이만 끝내야 겠다
.

------------------------------------------------------------------

e-mail :
icoddy@hotmail.com
msn id : icoddy@hotmail.com
-
박성규 -
///

나의 COM(Component Object Model) 경험담 #6


친구랑 바닷가 갔었습니다. 놀러 간 것은 아니고, 그냥 바람 쐬러 갔었습니다. 정확히 말하면 바닷가는 아니군요. 바다가 보이는 산(해발 513m)이라고 해야 정확하겠군요. 전망이 정말 좋습니다. 동해가 훤히 보이는 조그마한 절입니다
.
차가 올라가기 때문에 올라간다고 힘들진 않습니다. 머리 식힐 때는 그곳 만한 곳이 없죠. 그리고 바닷가를 갈 수 있도록 등산로도 되어있습니다. 대신 내려갔다가 다시 올라와서 차를 타고 다시 내려가야 하는 이상한 코스죠. (2시간 정도
)
여러분은 바람 쐬러 갈 때 주로 어디를 가는 지 궁금하군요
.
친구랑 이런 얘기 저런 얘기 했습니다. 그 녀석은 전문대 교수자리 라도 하면서 박사 과정을 밟는 중입니다. 부러웠습니다. 그래도 목표가 있으니 말입니다
.
프로그래머 과연 밝은 미래인지 다시 한번 생각해 봅니다
.
그냥 열심히 해 보는 수 밖에요
.


, 그럼 시작하겠습니다
.

------------------------------------------------------------------

저번 시간에는 실습을 했다. 두 수를 넣고 더하기를 해주는 COM 컴포넌트를 구현해봤다
.
어떻게 실제로 해본사람이 있을까? 내가 생각 할 때는 전체의 5% 미만일 거라 생각한다. 대부분 아예 보질 않았거나 샘플을 다운 받아 놓았을 뿐일 거다. 다행이 다운 받은 소스를 한번이라도 훑어 본 사람은 그나마 관심이 조금은 있는 사람일 것이다
.

그건 그렇고, 다시 인터페이스를 생각해보자. 우리는 인터페이스를 공부했지만, 아직 이것은 애매한 부분이 없지 않다
.
인터페이스의 경우 바이너리로 배포된다고 했다. 우리가 COM을 사용할 때 인터페이스는 COM이 무엇을 제공하는지 알려 줄 뿐 어떻게 그 기능을 제공하는 지는 알 수가 없다. 실제, 이것이 COM의 목적일 수도 있다. 내부 구현은 어떻게 되어 있던 상관이 없다는 말이다
.
일반적으로 이것은 WhatHow에 비교된다. 클라이언트 입장에서는 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:

[
  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의 특징인 위치 투명성 어쩌고 저쩌고, 언어 독립적이고 이진형태여야 하고 버전 호환성을 제공해야 하네 마네 하는 것들이다. 결국 앞에서 IUnknownIClassFactory들을 다루었고 이밖에 오늘 한 Dll… 어쩌고저쩌고 하는 함수들을 다루었다. 물론, 이게 전부는 아니다. 하지만, 결국은 COM의 특징이라고 내세우는 것들이 DLL을 변형한 것에 지나지 않는다는 것이다. 물론, 개인적인 생각이다. 절대 아니라고 생각하는 사람도 있을 것이다. 그런 사람들은 이유를 리플에 달아주면 고맙겠다. 나 역시 개념 정리가 필요하니깐.(그럼 EXE는 뭐냐구? 당신은 EXE DLL의 차이를 설명할 수 있는가
?)

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

------------------------------------------------------------------

여기까지 왔다. 이제는 점점 어려워진다. 지금도 어렵다고 생각하는 사람이 많을 것이다. 특히나, COM 자체를 말로만 듣다가 직접 해보는 사람은 더할 것 같다. 처음 내가 겪었던 것 처럼 말이다. 내가 더 이상 해줄 것은 없다. 물론, 이 경험담은 계속 되겠지만, 더 이상 내가 이해를 얼마나 더 쉽게 해줄 지는 자신이 없다
.

점점 말투가 일반적인 책과 다를 바가 없어진다. 되도록 재미있게 하고 싶지만, 내용이 어려워지고 많아 질수록 나의 부족함을 느낄 뿐이다
.
지금까지 내 글을 읽어준 모든 분께 감사를 드린다
.

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

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

------------------------------------------------------------------


e-mail :
icoddy@hotmail.com
msn id : icoddy@hotmail.com
-
박성규 -
///

나의 COM(Component Object Model) 경험담 #7


제 글을 읽어주시는 분들에게 먼저 고맙다는 말을 전하고 싶군요. 몇몇 분들은 칭찬까지 해 주시더군요. 사실, 엄청 X 팔립니다
.
그래도, 기분은 좋았습니다. 맘 같아서는 COM 뿐만 아니라 COM+, DCOM부터 .NET Remoting 까지 나가고 싶은 심정입니다. 하지만, 아직 실력이 안 되는 관계로 잠시 늦춘다고 생각 하겠습니다
.
그건 그렇고, 지금 기분은 좋지만, 제 의도와 다르게 나가고 있다는 것이 무척 부담스러워집니다. 첨엔 그냥 재미 삼아 쓴 거 였는데, 이제는 중단하면 많은 분들이 마무리 안하고 끝낸다고 욕할 것 같습니다. 언제 그만두더라도 욕 안 먹으려고 무책임론까지 내세웠는데
,

~~~~.

하지만, 버뜨, 이런다고 제가 책임감 있는 놈처럼 보인다면 큰 착각입니다. 언제든지 중단 될 수 있음을 상기해 주세용~~~~~~~~~~~~.(빨리 도망 갈 준비를 해야 하는뎅. 나에게 자유 아니면 밥을 달라~~~~~~. ~ 배고파
~….)


그럼 시작하겠습니다
.

------------------------------------------------------------------

저번 강의에 대해 어렵다는 의견이 대체로 많았던 것 같다. 걱정하지 마라. 오늘 완전히 이해를 시켜 주겠다. 사실, 그림이 도움이 되긴 되었을 것이다. 하지만, 조금 부족하지 않나 싶다. 왜냐하면, 화살표만 그어져 있다고 해서 그걸 완전히 이해하는 것은 힘들다. 노력이 필요하다는 얘기다. 이게 호출 된 다음 다음에 호출되는 건 뭐지? 이러면서 하나하나 따라가야 한다
.

그리고, 몇 분이 질문을 주셨다. 주로 #5에서 한 실습과 관련해서 이런 저런 쪽이 되지 않는다는 내용과 특이하게 한 분은 DLL 지옥에 대해 다시 한번 질문 하셨다. (아주 예리 하시다. 나도 자세히 모르기 때문에 대충 설명하고 넘어 가려고 했는데 가슴을 콕 찌르는 질문이었다
.)

그럼 다시 DLL 지옥에 대해 간단하게 알아보고 넘어가자. COM 역시 이 문제를 안고 있다고 했다. 그나마 .NET에서 이 문제를 Assembly 라는 개념을 도입해서 해결하였지만, 이것 역시 문제점이 없는 것은 아니다
.
그럼 .NET 에서는 어떻게 이 문제를 해결 했을까? .NET에서는 .NET 컴포넌트를 버전별로 관리를 한다. 같은 이름으로 여러 개의 DLL이 있어도 상관이 없다는 뜻이다. 원리는 간단하다. (아직 해보질 않아서 이게 맞는지는 장담을 못하겠다.) .NET 프레임워크를 설치하면 windows 디렉토리 밑에 Assembly 라는 디랙토리가 생긴다. 여기서 .NET 컴포넌트를 버전별로 관리를 한다. 같은 파일 이름으로 존재하더라도 버전만 틀리면 따로 관리가 된다는 얘기다. 여기서 필요로 하는 모듈이 있는 위치를 알아서 로드 해준다. 그럼 이것과 DLL 지옥과 무슨 연관이 있을까? 자세히 한번 보자
.
일반 DLL이나 COM 모듈의 경우 처음 배포될 때는 문제가 없다. 하지만, 업그레이드를 할 경우를 생각해보자
.
우리가 ComInfo.dll 이라는 컴퓨터의 정보를 얻어 내는 COM 모듈을 만들어서 이 모듈을 여러 회사에 비싸게 팔았다. 그리고 A라는 회사는 이 모듈을 사서 aa 라는 어플리케이션을 만들었다. 그리고 대부분 학교에 aa라는 어플리케이션이 깔린 것이다
.
한참 후에 우리는 새로운 기능이 추가된 ComInfo.dll을 새로 만들었다. 그리고 이번엔 이걸 이용해서 B라는 회사에서 bb 라는 어플리케이션을 만든 것이다. 문제는 bb 라는 어플리케이션이 학교에 보급되면서 발생하기 시작한 것이다. aa 라는 어플리케이션과 bb 라는 어플리케이션이 같이 깔린 곳에서 aa 라는 프로그램이 죽어 버리는 것이다. 왜 이런 문제가 발생할까? 그 이유는 간단하다. ConInfo.dll 의 구 버전을 새 버전이 덮어 씀으로 해서 aa 라는 어플리케이션이 그 새로 업그래이드 된 DLL의 잘못된 함수를 호출하거나 함수 내용이 바뀌어 잘못된 연산을 수행했기 때문이다. (이것이 DLL 지옥이다.) 그렇다고 새로 개발 할 때마다 매일 파일 이름을 새로 지을 것인가
?
물론, 이 문제도하위 호환성을 확실히 고려해서 새로 만든다면 문제가 되지 않는다
.
하지만, 여러분들도 많이 개발 해봤겠지만, 이것이 쉬운 작업이 결코 아니다라는 것을 잘 알 것이다. 엄청난 시간과 노력 그리고 테스트가 필요하다. 한마디로 번거롭다
.
그냥 같은 파일 이름으로 된 두개 이상의 파일이 있고 이것을 어플리케이션에 따라 적절히 로드만 시켜주면 될 텐데 말이다. 하지만, 지금 DLL의 경우는 이것이 불가능하다. 동시에 같은 이름의 DLL이 로드 될 수 없기 때문이다. COM 역시 하나의 시스템에 하나의 버전만 가지도록 설계되었기 때문에 문제가 발생한다
.

자 이해가 되었는가? 이것이 COM과 직접 연관이 없다고 하더라도 COM에 이런 문제가 있다는 것 정도는 알아야 할 것 같아서 다시 한번 언급해봤다.(내가 생각해도 너무 완벽한 설명 같다.  ~~ ~~ 또 돌 날라오넹. 제발 돌 좀 그만 던져라. 이제 아프다
.)
자 그럼 처음에 말했듯이 #6에서 한 것이 어렵다고 한 분들이 많아서 그 부분을 완전히 이해하도록 하자
.

다음 그림들을 보면 이제 확실히 이해 할 수 있을 것이다. 이 그림들은 마이크로소프트사에서 제공한 COM Spec에 있는 그림이다. 어렵게(?) 구한 것(구걸 해서 구한 것이 아니라 어디 있는지 찾는 것이 힘들었다는 얘기니 착각하지 않도록)이니 꼭 기억하기 바란다
.
내가 그려보려고 했지만, 불가능했다. 직선만 있으면 이쁘게 다시 그려보려고 했는데 곡선화살표들이  중간중간에 있는 것이 아닌가? 할 수 없이 영어로 설명 되어 있는 그림을 그대로 썼다
.



<
그림 : COM 서버의 일반적인 구조. >


서버 모듈의 기본적인 구성이다. 서버 모듈이 COM 개체와 동일한 의미가 아니다라는 것을 알 수 있다. 서버 모듈에는 실제 COM 개체와 그 개체를 생성할 수 있는 클래스팩토리가 쌍으로 있다는 것을 확인 할 수 있다. 우리는 클래스팩토리 개체를 사용해서 실제 COM 개체를 생성하고 그 인터페이스를 받아온다는 걸 #6 에서 이미 배웠다. (이 정도 그림은 그릴 수 있었다. 하지만, 다음 그림은 장난이 아니다. 그래서 포기한 것이다. 지저분 하더라도 이해하길 바란다
.)


그럼 이제 가장 중요한 그림을 보자. 이것만 이해하면 지금까지 한 모든 내용을 이해했다고 생각해도 별 무리가 없을 듯 하다
.
 


<
그림 : in-process 서버의 경우. >


그림이 복잡하다고 생각 되는가
?
1
번부터 번호가 붙어 있으니 따라가다 보면 쉽게 이해가 될 테니 걱정 할 것 없다
.
그럼 이제 그림을 분석해보자. (이왕이면 여자 그림이라던가 칼라로 된 그림이었으면 더 확실히 분석 할 수 있는데 흑백이라서 영 그럴 맘이 생기질 않는다. 그래도 해야지 어쩌겠나
.)

그림에서 크게 3개로 분류되어 있는 것을 볼 수 있다. Client, DLL, COM 이다. Client COM 컴포넌트를 사용하는 일반 어플리케이션이라고 생각하면 된다. 그리고 COM COM 라이브러리라고 생각하는 것이 좋겠다. 마지막으로 DLL COM 서버 모듈이라고 생각하면 된다. 꼭 이렇게 적용되는 것이 아니지만, 대부분이 이러한 형식일 것이다
.

여기 호출 순서에서 몇 개가 빠져 있다. 그 몇 개는 간단하다. 그리고 이미 앞에서 다 했었다. 먼저, CoInitialze를 호출하여 COM 라이브러리를 초기화 한다. 그 다음 CoCreateInstance함수를 호출한다. 그러면 CoCreateInstance 함수는 내부적으로 CoGetClassObject를 호출한다
.

그 다음 부터는 그림을 쭉 따라가보자
.

이 과정은 한마디로 객체를 생성해서 그 객체의 인터페이스를 얻어오는 과정으로 요약할 수 있다
.

1.        CoGetClassObject
함수를 호출한다. 레지스트리에서 클래스를 찾아 DLL의 위치를 알아올 것이다
.
2.        CoLoadLibrary :
여기서 그 DLL을 메모리에 적재할 것이다
.
3.        DLL
DllGetClassObject의 주소를 받아오기 위해 GetProcAddress를 호출한다
.
4.        
클래스팩토리를 생성하기 위해 DllGetClassObject 익스포트 함수를 호출한다
.
5.        
클래스팩토리를 생성하고 IClassFactory 인터페이스 포인터를 리턴한다
.
6.        
클래스팩토리 포인터를 리턴한다
.
7.        CreateInstance
함수를 호출한다
.
8.        ClassFactory
에서 실제 개체를 생성한다
.
9.        IClassFactory
의 메서드를 사용해 실제 개체의 인터페이스를 얻어온다
.
10.        Use Object :
얻어온 인터페이스에 구현된 메서드를 사용한다
.

사용이 끝난 다음은 CoFreeUnusedLibraries 함수를 사용해서 DLL을 언로드 해야 한다. 이 함수에서 DllCanUnloadNow 함수를 사용해서 정말 언로드 해도 되는 지 확인한다. 그리고, 마지막으로 CoUninitialize 함수를 호출하고 끝낼 것이다
.

쉽지? 어려우면 다들 COM 서적 하나씩은 들고 있을 거라 생각한다. 그걸 참조 해봐라
.
(
책은 죽어도 안보는 사람이 있다. 돈 주고 사기는 왜 샀나 모른다. 우리집에도 사 놓고 안보는 책이 한 박스는 된다. 조만간에 중고 판매에 내 놔 봐야겠다. 돈 좀 되겠지
?)





이왕 한 김에 다음 그림은 참조 삼아 확인하기 바란다. 이것은 지금 당장 자세히 볼 필요는 없다
.



<
그림 : 로컬 서버의 경우. >

자 이제 한 눈에 들어 오는가? 이제 #6에서 했던 것이 어렵다고 한 사람은 조금 나아졌을 테고 쉽다고 한 사람은 완전히 이해를 했으리라 생각한다. (하지만, 역시 COM 라이브러리를 완전히 알지 못하는 이상 상세한 내용은 어렵기 마련이다
.)

외울 사람은 외워도 좋겠다. 쉽지는 않을 것이다. 나도 못 외운다. 그냥 보면 알겠지만, 굳이 외울 필요가 있을까? 아무튼, 이제 in-process 서버는 대충 돌아가는 것을 다 알았다
.


앞으로 우리는 로컬 서버와 원격서버에 대해 해야 한다. 이제부터는 또 장난이 아니다. 지금부터 본격적으로 어려운 개념이 나오기 시작할 것이다. 앞에서 말한 것을 한번 되짚어 보자. COM이 위치 투명성을 제공한다고 하였다. COM 서버가 어디에 있던지 클라이언트에서는 크게 달라지지 않는다는 말인데, 그럼 그 위치 투명성을 어떻게 COM이 제공하는지 알아봐야 한다
.
여기서 어려운 말들, 처음 들어보는 말들이 나오기 시작한다. 미리 겁먹지는 말자. 역시 알고 보면 X도 아닌 것들이다
.

자 한번 또 생각해보자.(이놈의 생각은 언제까지 계속 해야 하는지 지겨워 지는군
.)
클라이언트가 COM 서버의 인터페이스 포인터를 얻어와 COM 개체를 사용하였다. , 자신의 함수를 호출하듯이 그냥 호출하면 되는 것이다. 어차피 같은 메모리 영역에 로드되어 있으니 아무런 문제가 될 것이 없다. 실제로 그 경우와 같다
.
하지만 로컬서버나 원격서버의 경우는 완전히 다르지 않는가? 다른 프로세스의 주소 영역이나, 저 멀리 떨어져 있는 PC에 있는 COM 서버의 인터페이스 포인터를 어떻게 맘대로 호출해서 쓸 수가 있다는 말인가? 주소가 일치 하지 않는 것은 당연할 것이다. 그럼 해결책은 무엇일까
?

간단하다. 중간에 연결을 대행해주는 한 놈을 만들면 될 것이다
.

COM
에서 두 프로세스의 통신은 LPC RPC라는 방법을 사용한다고 언급 했었다. 특히나, DCOM의 경우는 RPC를 사용하여 네트워크상에서 통신한다
.

그럼 그 중간에 연결해 주는 놈을 우리가 설계해 보자 어떻게 만들면 될까? 잘 생각해봐라. 당신이 이 정도의 설계능력 가졌다면 이미 중급으로 올라설 준비가 된 것이다. 코딩을 하라는 것이 아니다. 원리를 생각해 보라는 것이다
.

시간을 갖고 천천히 생각해봐라
~~~.

생각해 보았는가? 다양한 방법들이 나왔을 것이다. 어느것이 정답이다 아니다라고 말할 수는 없다. 그럼 COM은 어떤 방식을 선택했는지 보자. (자신이 생각한 방식과 비교해 보면 재미있을 것이다
.)

COM
에서는 클라이언트 프로세스 영역에 Proxy 라는 놈을 만들어서 다른 프로세스 영역에 있는 COM 인터페이스를 똑같이 흉내낸다. 그러면 클라이언트는 그 Proxy가 실제 COM 개체인줄 알고 사용할 것이다. 그리고 서버 프로세스 영역에서는 Stub를 만들어서 그 반대 역할을 한다. 이때, 클라이언트측의 데이터를 전송할 수 있는 표준 형식으로 변환하는 것을 마샬링이라 하고 그 반대 작업을 언마샬링이라고 한다. 다시 말하면, Proxy는 마샬링 작업을 한 후에 클라이언트에게 필요한 데이터를 넘겨준다. 그리고, 클라이언트로부터 어떠한 요청이 들어오면 Stub는 언마샬링 작업을 통해 COM 개체에 있는 메서드를 호출한다. 그리고 다시 그 결과는 다시 반대로 클라이언트에게 넘겨준다
.
여기서 네 개의 어려운(?), 그리고 이상한 말들이 나왔다. 프록시, 스터브, 마샬링, 언마샬링 이다. 일단 넘어가자. 나중에 다 할 놈들이다
.

일종의 속임수인가? 그렇다고 생각하자. .NET에서 COM을 사용하는 것도 어차피 속임수니깐 말이다. 말이 나온 김에 잠시 언급하자면 .NET에서 COM을 사용할 수가 있다. 관리 코드에서 어떻게 비관리 코드를 사용하는 것이 가능할까? 그 반대도 가능하다. 이것은 단지 COM 컴포넌트를 .NET 컴포넌트라고 속여서 .NET 어플리케이션이 쓸 수 있도록 만든 것이다
.

개인적으로 이것과 비슷한 개념이라고 생각한다. 다시 한번 말하지만, 겁먹어서 될 건 하나도 없다. 괜히 포기만 빨라질 뿐이다. 전부 X도 아니다 라고 생각해야 한다
.
10번씩 따라해 봐라. ‘COM, X도 아닌게 억수로 까부넹’ (난 어설프게 영화 친구 따라하는 게 아니다. 여기 내가 사는 지역 말투 그대로다
.)

그건 그렇고 이제 큰일이다. 이걸 어떻게 또 이해해야 하나. 오늘 다 해버릴까? 아니면 다음 시간에 할까? 오늘 많은(?) 걸 했으니 그만하자.(뭘 많이 했냐고 따지는 사람이 있을 것이다. 저 번에 한 걸 재탕한 거잖아 라고 하는 사람도 있을 것이다. 그치만, 너도 타자 한번 쳐봐라. 장난 아니다. 얼마나 힘든데, 하루에 7 8 A4지에 타자하는 것이 그렇게 쉽지가 않다. 그래도 하루에 거의 한번씩 나오니 여유를 좀 가지자. 나도 힘들다
.)

오늘 전체적인 흐름을 복습했다. 그리고 앞으로 할 내용이 무엇인지도 언급했다
.
프록시, 스터브, 마샬링, 언마샬링. 이거 그렇게 어려운 것이 아니다. 포기하지 마라.(계속 강조한다.) 우리가 IDL 파일을 컴파일 했을 때 5개의 파일이 생성된 것을 봤다. 여기서 프록시와 스터브를 잘 지원해주니깐 걱정할 것 없다. 이거 하면 자동으로 마샬링, 언마샬링 이해 될 거구, 저절로 차근차근 다 해결 될 거다
.

앞으로는 주로 이런 개념위주가 될 듯 싶다. 어떻게 COM에서 이런저런 문제들을 해결했는지 알아보게 될 것이다
.

------------------------------------------------------------------

오늘은 여기서 마치겠다
.
친구가 고기 사준단다. 지금 나가봐야 한다. 참고로, 절대 고기 먹으려고 여기서 마치는 거 아니다. 정말이다. 믿어주라. 몸보신도 좀 해야 좀더 좋은 글이 나오지 않겠나. ^^; 먹는 얘기 그만 해야 겠다. 이 글 읽는 분들 배 고프겠다
.
어쨌든, 오늘도 수고 많았다. 열심히 공부하자. 공부해서 남 주는 거 아니라고 부모님께서 늘 말씀하시지 않았나. 그리고 결혼 안 한 분들, 앤 없는 분들, 남자는 능력이 있어야 여자가 따른다는 걸 명심하자. 프로그래머 사실 여자한테 별로 인기 없는 거 다 잘 알 거다. 맨날 밤샘한다고 놀아주지도 않지, 꽤재재한 몰골로 항상 피곤에 절어있지. 어느 여자가 좋다고 하겠는가? 돈이라도 많이 벌어 줄려면 열심히 공부하는 수 밖에 없다
.

어떤 분들은 내가 여자가 없을 거라 생각 할 수도 있을 것 같다. 얼마나 시간이 남아 돌면 이런 글이나 쓰고 앉아 있나 하고 생각하는 분도 있을 것이다. 사실은 나 결혼했다. 작년에 했다. 왜 마누라랑 안 놀고 이 짓 하냐고? ㅜㅜ 마누라가 임용준비 한다고 설 갔다. 12월달에 내려 온단다. 결혼한지 얼마 됐다고 날 홀아비로 만들어 놨다. 그래서 밤에 할 일도 없고 외롭고 해서 이 글을 쓰게 된거다. 알고보면 나 불쌍한 놈이다. 그러니 제발 욕하지 마라
.

그럼 내일도 모두들 즐겁게 보내길
..

------------------------------------------------------------------

e-mail :
icoddy@hotmail.com
msn id : icoddy@hotmail.com
-
박성규 -
///

나의 COM(Component Object Model) 경험담 #8


점점 지쳐 갑니다
.
여러분들도 마찬가지겠죠
? ^^
이번 주는 회사일 때문에 집에서도 시간이 잘 안 나는군요. 앞으로는 시간이 조금은 더디게 나갈 것 같은 불길한 예감이 드는군요. 처음엔 내용이 허접이라도 진도라도 빠르면 욕을 적게 먹겠지라고 생각했는데, 이제는 진도까지도 늦어지니 할 말이 없습니다
.
가벼운 맘으로 읽을 수 있는 글을 쓰려는 제 의도가 자꾸 무거운 글로 바뀌고 있습니다. 그만큼, COM 이라는 놈이 만만치가 않습니다. 어쩌겠습니까? 그래도 하겠다고 맘 먹은 이상은 해봐야지 않겠습니까? 이제 산 중턱까지 올라간 느낌입니다. 이제 점점 호흡이 가빠지고 몸은 점점 말을 듣지 않을 겁니다. 오로지 산 정상에 섰을 때의 기분을 상상하면서 참는 것만이 산을 정복할 수 있는 유일한 방법일 것 같네요. 아니면 그냥 헬기라도 한대 대절할까요
? ^^;

그럼 시작하겠습니다
.

------------------------------------------------------------------

지금까지 COM에 대해 대충은 감이 잡히리라 생각한다. 어떤가
?
내 생각으로는 대부분의 사람들은역시 COM은 할 것이 아니다.’ 이것일 것이다. 아닌가? 그렇다면 그 사람은 우리가 일반적으로 말하는 초보 딱지는 땠다고 할 수 있을 것 같다
.
역시 Code 가 설명에 들어가면서 생긴 문제인 것 같다. Code란 것이 빠질 수 없는 것일까? 사실, 개인적으로 진정한 지식 전달자는 Code 가 한 줄도 필요 없이 지식을 전달 할 수 있어야 한다고 생각한다. ‘David Chappell’ 이라는 사람이 있다. ‘Understanding ActiveX and OLE’ 이라는 책으로 유명한 사람이다. 여기서 내가 아는 체 한다고 생각하는 분도 있을 것이다. 하지만, 나도 친구한테 최근에 들은 사람이다. 그래서 이 사람에 대해 조금 아주 조금 알아봤다. ‘Understanding Windows 2000 Distributed Services’ 라는 책도 쓴 사람이었다. 이 사람은 Code를 한 줄도 넣지 않고 설명하기로 유명하다고 한다. 심지어는 COM관련 연구발표회에서 IUnknown, 프락시/스텁 등등 COM에 대한 전반적인 설명하면서도 거의 몇 시간동안 코드를 한 줄도 설명하지 않고 발표 했다고 한다. 대단하지 않은가? 그의 책을 읽어보면, 책도 그리 두껍지 않다. 우리나라의 왠만한 번역서의 반 두께도 되지 않는다. 하지만, 지식 전달 차원에서 이 사람을 따라갈 사람을 없을 것 같다. 왜 이런 일이 가능한 것일까? 그것이 바로 경험에서 우러나는 노하우인 것이다. 아직 난 경험이라고 내세울 것이 눈꼽만큼도 없다. 그래서 나의 설명은 당연히 한계점에 부딪힌다. COM 내가 아무리 설명해도 이해하지 못할 부분이 많을 것이다. 분야도 좁은 분야가 절대 아니다. 결국 COM 서적 한 권 정도는 화장실 갈 때 조차도 끼고 다니면서 독파를 해야 한다. 그리고 천천히 이책 저책 읽어 보는 것이 중요하다고 생각한다
.
물론, 실습도 빠질 수 없다. 하지만, 중요한 것은 COM을 왜 써야 하는지에 대한 동기 유발이다
.
말이 나온 김에 다른 것도 알아보자. COM을 지금 하고 있지만, COM+ 라는 말도 많이 들어 봤으리라 생각한다. 이것은 또 뭐야 하는 사람도 있을지 모르겠다. 하지만, 이것 역시 COM을 한단계 업그레이드 한 것에 불과하다. 지금까지 한 모든 COM 관련 내용들을 COM+ 라고 해도 아무도 딴지 걸 사람은 없다. COM+ 역시 COM을 그대로 사용하고 몇 가지를 추가한 것일 뿐이기 때문이다. 한마디로 표현하자면 COM+ COM을 대체하는 것이 아니고, 여러분이 COM에 대해 알고 있는 모든 것은  COM+에서도 응용할 수 있다는 것이다
.
DCOM
역시도 마찬가지이다. 물론, 이것도 COM+ 의 한 부분이다
.



그림은 시대흐름과 전체적인 관계를 보는 데 참조하면 되겠다. 설명하자면 이렇다.
1993
MS COM을 도입해서 1996 Windows NT 4.0의 일부로 DCOM을 넣었다. 그리고 그 해 후반에 Option Pack 을 통해서 MTS(Microsoft Transaction Server)를 배포하였다. 그리고 1998년에 COM+ 에서 MTS COM을 아주 효과적으로 통합하였다. 그렇다고 해서 COM+ MTS COM을 합친 것이라고만 생각해서는 안 될 것이다. 갑자기 왠 트랜잭션 서버냐고 반문하는 사람들이 많을 것이다. 나 역시 이것이 왜 필요하고 COM과는 전혀 별개의 문제라고 생각했다. 트랜잭션이라는 말은 데이터베이스에서 듣던 말인데, COM 하고 연결이 되었을까? 트랜잭션이란는 말을 데이터베이스에 한정시켜서 생각한다면 이 부분은 이해가 불가능하다. 데이터베이스와 COM은 무관하니깐 말이다. 트랜잭션이라는 말 뜻에 의미를 두어야 한다. MTS가 통합되면서 COM은 다음과 같은 점이 나아졌다. 결국 COM+에 추가된 기능이라 해야 할 것 같다
.

자동 트랜잭션

개체 사용 기간 서비스
스레딩 및 동시성 서비스
보안 서비스

그리고 MTS와는 상관없이 다른 서비스들도 추가 되었다.

COM+
이벤트

대기열 구성 요소
구성요소 로드균형
기타 등등.

여기서 그림과는 조금은 차이가 있지만, 나의 생각은 이렇다
.

COM < DCOM < MTS < COM+

이런 포함관계와 발전관계를 말하고 싶다
.
자세한 것은 나중에 다루도록 하자. 상당히 머리가 아파오는 부분들이다
.
이 모든 것을 이해 하려는 것은 엄청난 시간과 노력이 필요하게 된다. 윈도우 2000 분산형 환경, COM, ADO, DTC, MSMQ, ADSI, SSL 등등 잘 들어보지 못했던 많은 분야도 반드시 알고 넘어가야 하는 부분이다. 도대체 COM과 연관되지 않은 운영체제 기술이 있을까 라는 생각이 들 정도로 방대하다. 그래서 COM 개발자들이 그리 많지 않은지도 모르겠다.(정말 방대한 분량이다. 언제 이걸 다 하나? 내가 설명을 너무 넓게 시작한 나머지 안 다루는 것이 없어진 것 같다는 느낌이다
.)

지금 두서없이 나가고 있다고 생각하는 사람들이 많을 것이다. 여기서 잠시 정리 하자. 결국 내가 하고픈 말은 COM+ 가 나왔다더라 또는 DCOM이 어떻다더라 .net 컴포넌트가 어떻다더라는 말에 대해 신경 쓸 필요가 없다는 것이다. 모든 기초는 COM 에서 시작한다. COM 을 완전히 이해 한다면 다른 것들은 조금의 노력만으로 저절로 따라오는 보너스 같은 것들이다라는 것을 강조하고 싶다. 물론, 실전에 당장 들어가고 싶다면 Code가 중요한 요소가 될 수 있다. 하지만, 앞으로 또 어떻게 바뀔지 모른다. C#도 나왔다. 컴포넌트 기술은 하루하루 틀리게 발전하고 있다. 그 컴포넌트를 구현하는 방법은 계속 발전해 갈 것이다. 여기서 Code에 연연할 필요가 없는 이유가 바로 이러한 이유 때문일 것이다
.

생각지도 않게 말이 길어졌다. 왜 마샬링을 하는데 이런 긴 말들이 나왔을까? 잘 생각해 보자. #5에서 실습을 하면서도 마샬링과 관련된 코딩을 한 적이 없다. 결국 인프로세스에서는 굳이 마샬링이 필요가 없기 때문이다. 하지만 좀더 나은 서비스를 제공하기 위해서는 필수적으로 구현해야 한다. 대부분 프로세스 경계를 드나드는 로컬서버나 원격서버에서 필요한 것들이다. 따라서 DCOM COM+를 조금 언급해봤다. COM의 최대 장점인 위치 투명성을 제공하기 위해 많은 복잡한 것들이 필요하게 되었고, 그중의 하나도 바로 이 마샬링이다. 그리고 한 분이 질문 해주셨다. 왜 클래스팩토리가 필요한가라고 말이다. 이것 역시 위치 투명성과 직접 관련이 있다. 클라이언트에서 인프로세스 COM 개체를 new 연산자를 사용해서 생성하는 것도 가능하다. 꼭 클래스팩토리를 이용할 필요가 없는 것이다. 하지만, 왜 굳이 이 클래스팩토리를 사용해서 COM 개체를 생성하는 것일까? 그 대답이 바로 위치 투명성을 위한 것이다. 일단 new 연산자를 통해 개체를 생성한다는 것은 자신의 메모리 영역에 개체를 로드한다는 것이 된다. 하지만, 다른 프로세스에서 동작한다거나 멀리 떨어진 PC 상에서 동작하는 COM 컴포넌트를 그 같은 방법으로 생성할 수는 없지 않겠는가? 결국COM이 어디에 있던 클라이언트에서는 같은 생성방법을 제공할 필요가 있었던 것이다. 그래서 나오게 된 것이 클래스팩토리라고 생각하면 쉬울 것 같다
.
또 말이 새어 나가려고 하는군. 빨리 여기서 끊어 버리자
.

오늘 그것에 대해 알아보겠다고 했었다. 그럼 알아봐야지. 왜 자꾸 엉뚱하게 새어 나가려구 하냐구(아무래도 머리가 너무 복잡해서 정리가 안 되는 것 같다. 넓은 아량으로 이해하시길 바란다
.)

그럼 저번 장에 이어서 계속 말을 이어 나가보자. #7 에서 우리는 마샬링, 언마샬링, 프락시 스터브에 대해 언급했다
.
사실, 대부분의 COM 관련 서적에서 이 내용은 중간 이후에 언급이 되기 시작한다. 하지만, 내 생각은 다르다. 초반에 언급하고 대충 무엇이란 것을 알고 넘어가는 것이 나중에 더 도움이 될 것 같다
.
혹시 CORBA(Common Object Request Broker Architecture) 라고 들어 봤는지 모르겠다. 나 역시 이 부분에 대해서는 잘 모른다. 대충은 MS DCOM 과 경쟁하는 놈으로 알고 있다.( MS 진영이다. 역사 역시 DCOM 보다 오래 되었고 한 때 우리나라에서도 엄청난 바람이 분적이 있다.) 대부분 컴포넌트는 JAVA로 구현 되고 있고(따라서, 당연히 SUN에서 강력하게 밀어주고 있다. 하지만, 요즘은 또 EJB가 한참 주가를 올리고 있다. 말 그대로 JAVA 만으로 모든 것을 해결하겠다는 의지가 아닐까?)  인터페이스의 지원을 위해 IDL 파일을 역시 사용한다. 신기하게도 여기서도 프락시나 스텁 마샬링, 언마샬링은 그대로 적용된다.(인터페이스의 개념 역시 거의 동일하다.) 결국 분산 컴포넌트라는 놈에게 이 모든 개념은 필수적으로 갖추어져 있어야 한다는 얘기이자 개념상의 문제라는 것을 알 수 있다. 결국 컴포넌트이든 분산컴포넌트이든 간에 COM 에 대한 완벽한 이해는 모든 컴포넌트 기반 기술을 이해 하는 데 버릴 것이 하나도 없는 지식이 될 수 있다는 것이다
.
자바의 빈즈를 하더라도 COM과 하나하나 매치가 가능하다. 빈즈의 이놈은 COM의 이놈이 하는 역할이랑 똑 같네. 어 이 놈은 COM의 이 놈이잖아. 이런식으로 말이다
.
참고로, CORBA에 대해 궁금해 하시는 분들은 http://www.omg.org/ 를 참조하기 바란다
.
(1989
년 분산 객체의 상호 운용을 위한 개방형 내부 구조를 정의하고자 결성된 OMG CORBA라는 이름으로 분산 객체에 대한 버스 구조를 정의하였다. OMG는 현재 약 800개 업체가 가입하여 활동하고 있는 거대 그룹이 되었다
.)

다시 본론으로 들어가자.(~~~~! 미치겠다. 왜 자꾸 엉뚱한 소리를 하는지 원~ 나 자신이 제어가 되질 않는다.) 결론은 이거다. 우리가 오늘 하는 프락시, 스텁등등이 결국 마샬링 때문에 나온 기술이라는 것을 알아야 한다. , 마샬링 하나를 하기 위해 프락시나 스텁을 사용해야 하고 그 반대 개념인 언마샬링도 알아야 하는 것이다
.

도대체 마샬링이 뭘까? 앞장에서 말한 것이 이해가 쉬웠는가? 여전히 오리무중이다. 어쨌든, 진도를 나가려면 이 놈을 파헤쳐야 한다
.
마샬링을 정의 해보자
.
마샬링은 한마디로 하나의 메커니즘에 가깝다고 할 수 있다. , 클라이언트가 사용하고자 하는 객체의 형태에 관계없이 같은 방식으로 인터페이스를 사용할 수 있게 하는 메커니즘인 것이다. 기술적인 측면에서 다시 설명해 보겠다. 단일 주소공간에서 마샬링이 필요 없다고 했다. 그것은 컴포넌트를 사용한다고 가정할 때 동작이 일어난다는 것은 메서드 인자들이 메서드 호출을 위한 스택으로 push 되고 실행 될 때 pop 되기 때문이다. 하지만, 실행형 컴포넌트들과 원격 컴퓨터에서 실행되는 것은 마샬링에 대한 신경을 써야만 한다. 왜냐? 마샬링이 메서드의 호출과 인자를 전송가능한 패킷으로 포장해서 컴포넌트로 패킷을 전송하기 때문이다. 그리고 그 반대과정이 언마샬링이라는 것은 충분히 짐작이 가능하다
.

참고로, 마샬링은 크게 세가지로 나뉜다
.

표준 마샬링
(Standard marshaling)
타입라이브러리 마샬링
(Type livrary marshaling)
커스텀 마샬링
(Custom marshaling)

위의 세가지 이다
.
표준 마샬링의 경우 MIDL로 컴파일 해서 나온 파일들을 가지고 사용한다
.
옛 기억을 잠시 상기해보자. 특히 MIDL IDL 파일을 컴파일 했을 때 생겼던 파일들을 말이다
.

iAdd.h                : C
C++ 버전의 인터페이스 정의

iAdd_i.c                : IID
CLSID 상수 정의
iAdd_p.c        :
프락시/스텁 마샬링 코드
iAdd.tlb                :
타입 라이브러리(IDL 파일의 바이너리 버전)
dlldata.c        :
마샬링 코드에 대한 DLL 엔트리 포인트


기억나쥐? 이 파일들을 컴파일하고 링크하면 해당 인터페이스를 정확하게 마샬링해주는 프록시/스텁 DLL을 생성할 수 있다. 간단할 것 같지 않은가? 다 만들어 주는데 말이다. 실습은 나중에 하자. 지금은 아직 시기가 아니다.

두번째 나온 타입라이브러리 마샬링은 오토메이션 마샬러를 사용한다. 이것은 IDispatch 인터페이스를 구현해서 비주얼베이직이나 자바 스크립트값은 언어와도 동작한다. 여기에 대해선 이정도만 알고 넘어가자
.

그리고 마지막 커스텀 마샬링이다. 가장 기본적인 마샬링이라고 할 수 있다. 마샬링 과정에 대해 제어권을 완전히 가져와서 하는 것이다. 따라서 구현하기가 꽤 까다로울 것이라고 짐작된다. 효율을 중시 한다면 이 커스텀 마샬링을 사용해야 할 것이다
.

그럼 이제 이해를 해보자. 원격에 있는 COM 서버를 사용하기 위해서는 그 서버를 대체해줄 것이 필요하고 그 대체품은 클라이언트와 같은 프로세스에서 실행 되어야 한다. 그래야만 클라이언트에서 인식이 가능하지 않겠는가? 여기서 그 대체품으로 프락시나 핸들러가 필요하게 된 것이다. 프록시는 원격의 서버를 순수하게 대행하지만, 핸들러는 대행을 할 수도 있고, 자신이 구현할 수도 있는 혼합 형태이다. 어쨌든 둘 다 원격의 객체와 클라이언트의 연결을 위한 대행의 역할을 한다. 클라이언트가 서버의 함수를 호출할 때 넘겨지는 인자, 그리고 서버 함수의 리턴값은 프로세스의 경계를 넘어서 유효해야 한다. 이 측면 또한 마샬링이 개입되는 곳이다
.
인자의 형태에 따라 다른 형태의 마샬링이 일어나기도 한다. DWORD같이 간단한 타입은 직접 복사가 된다. 그러나 어떤 영역을 가리키는 포인터가 넘어갈 때는 그 영역 전체가 프로세스 경계를 넘어 복사되어야 한다. 이 작업이 그렇게 만만한 작업이 아니다. 배열을 마샬링할 때를 생각해보자. 마샬러에서 어떻게 배열의 크기를 알 수 있을까? 그리고 전체 배열을 알았다 치더라도 아직 앞의 몇 바이트만 사용했을 수도 있다. 그걸 다 보낼 것인가? 가장 심각하게 걱정되는 것은 원형 링크드리스트를 생각해보자. 끝이 없을 것이 아닌가? 이것도 엄청 머리가 아플 것임을 알 것이다. 나중으로 미루자..ㅋㅋ (귀찮은 건 지금 다 나중으로 미루고 있다. 나중에 정말 이것을 다 하게 될지도 의문이다. 아무래도 괜히 시작했다는 느낌이 든다. 아예 말을 안 꺼내고 대충 넘어갈 걸 그랬나
?)
여기서 마샬링의 전체적인 기본 메커니즘을 살펴보자. 이 부분이 가장 중요하다
.
클라이언트는 CoGetClassObject를 실행하여 원격 서버를 실행하고, 원격 서버는 CoRegisterClassObject함수를 통해 마샬링을 시작하게 된다. 이 과정을 통해 일단 서버의 IClassFactory가 클라이언트에 넘겨진다
.
좀더 자세히 살펴보자
.
·        CoRegisterClassObject
안에서 COM은 객체에게 클라이언트 프로세스에 포함될 프록시의 CLSID를 요구한다. 만일 객체가 CLSID를 제공하지 않을 때는 COM은 표준 마샬링 프록시를 사용한다
.
·        COM
은 객체에게 마샬링 패킷을 요구한다. 마샬링 패킷은 프록시가 객체와 연결할 때 필요한 정보들을 담고 있다. 객체가 제공하지 않으면 역시 COM은 표준 패킷을 사용한다
.
·        COM
은 프록시 CLSID와 마샬링 패킷을 클라이언트에 넘긴다
.
·        
클라이언트의 프로세스에서 COM 1에서 얻어진 CLSID로 프록시를 생성하고, 2에서 얻어진 마샬링 패킷을 가져온다
.
·        
이제 프록시는 클라이언트가 CoGetClassObject에서 요구한 인터페이스의 포인터를 넘긴다. 클라이언트는 이 포인터(보통 IClassFactory)로 실제 객체를 생성할 수 있다
.
·        1,2
과정은 CoMarshalInterface함수가 담당하고, 3과정은 Service Control Manager가 담당한다. 4,5과정은 CoUnmarshalInterface가 담당한다
.

이제는 정리할 시간이다
.
마샬링이 하는 역할은 결국 원격 컴퓨터에 또는 다른 프로세스공간에 있는 COM 서버를 자신의 공간 안에 있는 것처럼 사용하게 해주는 메커니즘이다. 그러기 위해서는 클라이언트의 주소 공간안에 인터페이스의 v-table을 재생성해야 한다. 그리고 이 역할을 프락시가 한다
.
그러면, 클라이언트에서는 프락시를 호출할 때 프락시는 스텁과 통신하고 스텁을 개체에 있는 실제 호출을 할 것이다. 그리고 여기서 프락시와 스텁의 통신은 RPC을 사용할 것이다
.
아직 잘 모르겠다고? 사실 나도 더 이상 어떻게 설명할 방법이 없다. 나의 한계이다. 더 공부가 고픈 사람은 책을 사서 보기 바란다. COM 관련 도서가 그렇게 많지는 않지만, 그래도 몇몇 책은 꽤 볼만하다.(참고로, 난 책장수가 아니다
.)

총정리를 할 겸해서 다음 그림을 보면서 전체적인 윤곽을 머리에 넣어보자. 대충 보면 알 것 같고 앞에서 다 설명한 것들이니 따로 설명은 하지 않겠다
.




( Clients always call in-process code; objects are always called by in-process
code. COM provides the underlying transparent RPC.)

그림의 글자가 잘 안 보이는 사람은 그림을 확대해보길 바란다. 나도 잘 안 보인다. 확대하니깐 보이던데 여기서는 지면 관계상 확대할 수 없다. 다들 마우스로 그림잡고 드래그 한번 해봐라. 심심하지도 않고 재미 있잖아~~~~


복잡하다고 생각되면 다음 그림처럼 쉽게 이해해도 된다
.



( Components of COM’s distributed architecture )

결국 마샬링 하나를 가지고 하나의 장이 끝나 버렸다. 에겅~~~ 언제 이 많은 것을 다 다룰지 막막하기만 하다. 왜 이렇게 진도가 안 나가는 것일까? 여러분은 어떤 것이 더 좋은가? 그냥 알아들을 정도로만 설명하고 빨리 COM을 끝내는 것이 좋은가? 아니면 하나하나 자세히 알아보면서 COM을 끝내는 것이 좋은가? 이제는 조언이 필요하다. 어떻게 나아가야 할지 길을 잃은 느낌이다. 난 솔직히 후자가 좋긴 하다. 하지만, 기간이 오래 걸리는 만큼 부담 역시 커진다. 전자의 경우 부담은 적겠지만, 이 강좌를 할 의미가 조금은 퇴색될 듯 싶다. 왜냐하면, 전자의 경우 대부분의 책이 지향하는 바니깐 말이다. 전자의 경우는 책을 읽는 것이 훨씬 도움이 될 듯 싶기도 하다.

어쨌든 말도 많고 탈도 많은 COM 공부 하시느라 오늘도 정말 수고들 하셨다. 다시 한번 강좌가 늦어지는 점에 대해 사과를 한다. 옛날 같으면 어림도 없는 일이다. 그냥 내가 하고싶을 때 하면 그만이기 때문이다. 그런데, 내가 조금은 변한 것 같다. 왜일까? ㅋㅋ 알아서들 생각하기 바란다
.
그럼 다음강좌에서 보자
.

------------------------------------------------------------------

세상이 공평하다고 생각하십니까
?
요즘은 자꾸 회의가 드는군요. 누구는 좋은 회사 들어가서 돈 많이 받으면서 편하게 생활하고 누구는 박봉에 거의 밤새다시피 일하고, 개인적으로 저는 중간쯤에 끼어 있는 것처럼 느껴집니다만, 많은 프로그래머들이 제 밥그릇 못 찾아 먹는 걸 볼 때마다 왜 화가 나는지 모르겠습니다. 이만한 노가다 직종도 없는데 말입니다. 프로그래머라는 직업이 남들한테는 좋아보일지 모르겠지만, 아시는 분은 다 아시다시피, 정말 피곤한 직업입니다. 왜 프로그래머는 대접을 받지 못하는 걸까요? 정말 노가다 직장이라서 그럴까요? 별다른 기술 없이 시간과 몸으로 떼우는 직업이라서
?
왜 프로그래머가 되려고 그렇게 공부했을까요
.

~~~~~~~~~~ 한숨이 절로 나옵니다. ㅜㅜ
;;

그래도 내일을 또다시 찾아오겠죠
.
아무리 삶이 그대를 속일지라도 내일도 모두들 즐겁게 보내길
..

------------------------------------------------------------------


e-mail :
icoddy@hotmail.com
msn id : icoddy@hotmail.com
-
박성규 -
///

나의 COM(Component Object Model) 경험담 #9


저의 글을 누군가가 기다리고 있다는 것은 참으로 기분 좋은 일입니다
.
하지만, 그만큼 부담되는 일이기도 합니다. 친구에게 컴포넌트에 대한 궁금한 사항을 물어볼 때마다 프로그래머의 한계를 느낍니다. 과연 프로그래머가 밑바닥까지 원리를 알 필요가 있을까? 라고 반문하고는 합니다. 하지만, 가장 큰 문제는 COM 관련서적을 봐도 이해를 할 수가 없었다는 겁니다. 도대체가 무슨 말인지 이건 또 뭐야? 도저히 알 수 없는 말들 뿐이라는 것이 가장 큰 문제점이었습니다. COM을 처음 접하시는 분들 대부분이 저와 같은 경험을 하셨을 거라고 생각합니다. 기본 원리를 모르는 상태에서 또는 이 말뜻을 모르고서는 책을 읽어가면서 코딩을 하는 것이 아무런 플러스가 되지 않았죠. 오히려 시간만 축내는 결과를 가져오죠. 제가 하는 내용의 가장 큰 중점은 COM을 알고자 하는 것이 아닙니다. 여러분이 공부할 때 조금이라도 책을 이해하는데 도움이 되었으면 하는 겁니다. 제가 COM을 자세히 다루면 얼마나 자세히 다루겠습니까? 코딩을 하면 얼마나 잘 할 수 있겠습니까? 대충 제 말뜻 아시겠죠
?

그리고, 많은 분들께서 책의 내용보다는 이해할 수 있는 심층(?)적인 부분을 요구 하셨습니다. 저 역시 일반적인 코딩방법이 적힌 책은 지루합니다. 제가 깊은 부분까지는 알지 못하지만, 제가 아는 부분에 한해서는 최대한 자세히라기 보다는 쉽게 설명하려고 합니다. (사실, 쥐뿔도 모릅니다. 눈치 채신 분들도 이제는 많아 지셨습니다. 들키기 전에 그만 뒀어야 했는데. 이미지 관리는 제 체질이 아닌가 봅니다
.)

그럼 시작하겠습니다
.

------------------------------------------------------------------

이제 몇 가지만 더 공부하면 COM의 기초는 대충 끝이 날 것이다. 그 다음은 COM+로 넘어가야 한다. 사실, COM에서 COM+을 빼고 말한다는 자체가 우스운 일이다. 왜 그런지를 생각해 보았는가? COM 컴포넌트를 왜 만들고 있는가? 결국 모듈화가 가장 큰 목적이었다. 하지만, 이것은 기존의 DLL 이라던가 소스차원에서도 충분히 가능한 일이 아니었는가
?
지금의 컴퓨팅환경은 예전과 많이 틀려졌다. 로컬에서만 돌아가는 프로그램은 의미가 없어졌다는 얘기도 된다. 앞으로의 프로그래밍 방향은 인터넷환경과 네트웍환경을 빼놓고는 빈 껍데기에 불과하다. , 분산환경이 그만큼 중요해졌다는 말이다. COM의 한 부분을 차지하고 있는 ActiveX가 그렇고 DCOM 역시 분산환경을 빼놓고는 말하는 것이 불가능하다. COM+ 역시 COM을 좀더 잘 활용해 보려는 의도에서 나온 것이다. 아직 확실한 결정이 나온 것은 아니지만, COM #10쯤에서 끝낼 생각이다. 그리고 새롭게 COM+에 대해 #1에서 다시 시작하려고 한다. 물론 COM에서 나왔던 얘기가 반 이상이 되지 않을까 생각한다. 거기서 COM에서 부족했던 내용을 보완할까 생각중이다. 그것이 끝나면 다시 .NET에서는 컴포넌트의 발전 방향이 어떻게 흘러가는 지 할 예정이다. 현재의 내 실력으로는 어림도 없는 일이란 것을 난 잘 알고 있다. 하지만, 한번도 해보질 않은 사람과 이제 시작하려는 사람에게는 조금은 도움이 될 것이라는 것이 내 생각이다. #10은 너무 내용이 모자란다고 생각하는 사람이 있을 것이다. 하지만, COM+에서 부족한 부분을 다룰 예정이니 시간을 가지고 천천히 지켜 봐주길 바란다. (‘COM+를 하지 않고서는 COM을 하는 의미가 없다라고 자신 있게 말할 수 있을 것 같다
)

그건 그렇고 오늘 할 것에 대해 해야 한다
.
오늘은 IDispatch 인터페이스와 스래딩 방법에 대해 알아보려고 한다. 역시 개념상 어려운 부분이다. IDispatch는 자동화, , 오토메이션과 직접적인 관련이 있는 인터페이스이다. 한번쯤은 다들 들어 봤을 것이다. 나 역시 이 부분을 수도 없이 들어왔다. 하지만, 이것을 이해 한다는 것은 정말 힘든 부분이었던 걸로 기억한다. 그리고 나머지 한가지인 아파트먼트. 이것도 귀에 딱지가 앉을 만큼 들어 봤으리라 생각한다. 하지만, 이것 역시 뭘 하는 놈인지 알 길이 없다. 급하게 생각하지 말자. 천천히 생각해 보면 답이 나올 것이다
.

오토메이션. 너무 막연하지 않은가? 자동화라고 번역해도 막연하기는 마찬가지 이다. 그럼 우리가 알고 있는 오토메이션은 무엇인가? 예전에 OLE 자동화라고도 불리었다. 직관적인 정의를 보면, 자동화는 한 어플리케이션에서 다른 어플리케이션을 구동 시키는 것으로 클라이언트와 서버가 서로 자동적으로 통신을 하며 실행되는 방법이다
.
여기서도 서버와 클라이언트 개념이 존재한다. 자 한번 보자. 자동화 클라이언트는 자동화 컨트롤러라고도 불리우며 다른 어플리케이션의 객체를 이용하는 어플리케이션이다
.
자동화 서버 또는 자동화 컴포넌트는 자신의 객체를 제공해 주는 어플리케이션이다. 엄밀히 말하면 서비스를 제공해주는 어플리케이션이라고 해야 할 것 같다. 예를 들면 워드에서 액셀 시트를 끌어와서 워드의 한 기능인양 쓰는 것이 예가 될 것 같다
.
그리고 자동화 서버는 메서드, 속성, 이벤트 이 세가지 형태로 자동화 클라이언트에게 기능을 제공하는데, 이것은 비주얼 베이직에서 일반적으로 사용하는 ActiveX 컨트롤과 틀리지 않다. 예를 들어 어떤 차트를 보여주는 컨트롤이 있다고 하자. 그 차트컨트롤에는 값을 계산해주는 메서드가 있을 것이고 차트를 보여주는 여부를 결정하는 visible 속성이 있을 것이다. 그리고 마우스가 클릭 되거나 값이 변경되었다는 이벤트등이 있을 수 있다
.
좀더 세부적으로 살펴보자. 오토메이션을 알기 위해서 다시 COM에 대해 알고 넘어가야 할 문제점들이 몇 가지 있다. 도대체 COM 개체가 무엇인가? 개체 또는 오브젝트. 일종의 추상화로 데이터와 메서드를 포함하는 것이라고 C++ 또는 OOP에서 들어 봤을 것이다. COM 개체 역시 마찬가지이다. 데이터는 개체에 의해 메모리에 저장된 정보이고, 메서드는 그 개체가 서비스를 제공해주는 코드이다. 단지, COM 개체에서는 이 메서드들을 인터페이스란 놈으로 그룹화를 지어 놓았다는 것이 특징이라면 특징일 것이다. 최소한 COM 개체는 IUnknown 인터페이스 하나는 가지고 있을 것이다. 그렇다고 이것 하나만 가지고 있는 COM 개체는 아무짝에도 쓸모가 없다. 아무런 서비스도 제공하지 않는 놈을 어디다가 써먹겠는가? 이 놈은 아마 메모리 공간만 차지하는 식충이와 같을 거다. 여기서 잠시 깊게 생각해보자. COM 개체는 여러 클라이언트들이 연결 될 수 있다. 그 클라이언트들은 COM 개체의 특정 인터페이스의 메서드를 요청할 것이고 그렇게 하기 위해서는 클라이언트들이 COM 개체의 인터페이스 포인터를 알아야만 한다. 여기서 문제점이 발생한다. COM 개체와 클라이언트가 같은 언어로 만들어졌다면 클라이언트는 COM 개체의 사용방법을 쉽게 알 것이다. 하지만, COM 이란 놈이 언어 독립적으로 만들어지지 않았는가. 결국 다른 언어에서도 접근할 수 있는 방법을 제공해야만 했다. 알다시피, COM 인터페이스는 가상메서드테이블과 일맥상통하는 말임을 COM을 조금 공부해본 사람이라면 누구나 알 것이다. 일반적으로 우리는 vtable 또는 v-table이라고 한다. 그럼 인터페이스는 모두가 vtable 인터페이스인가? 그렇지는 않다. 바로 우리가 여기서 해야 할 dispatch 인터페이스란 놈이 또 있는 것이다.(여기서 우리는 IDispatch 인터페이스와 연관 된 놈임을 연상할 수 있다.) 왜 이 놈이 생겼을까? 그 이유가 바로 가상메서드테이블을 지원하는 않는 언어들이 있었기 때문이었다. 예를 들면 예전의 비주얼 베이직 같은 언어가 그러하다. 하지만, 요즘은 이것도 지원하는 것으로 알고 있다.(내가 비주얼 베이직을 잘 모르기 때문에 오류가 있을 수도 있다. 하지만, 마이크로소프트에서 말하는 것이니, 아마도 맞을 것이다.) 그렇다고 이 dispatch 인터페이스가 필요 없어진 것은 아니다. 여전히 비주얼베이직 스크립트 언어라던지 자바스크립트언어에서는 필요로 한다
.
dispatch
인터페이스는 일반적으로 디스핀터페이스(dispinterface)라고 불리기도 한다. 그럼 여기서 다시 한번 생각해보자. C++에서의 문자열과 비주얼 베이직에서의 문자열 처리 방식은 다를 것이다. 그리고 다른 데이터 타입역시 처리 방법에서 미묘한 차이가 있을 거라 생각한다. 그런데 이 가상메서드 테이블에 있는 메서드들의 변수 유형을 어떻게 맘대로 사용할 수 있겠는가? 결국 이 문제를 해결하기 위해 dispinterface 란 놈을 만들었고, dispinterface를 사용하는 것을 오토메이션이라고 한다. 그럼 우리는 dispinterface 에서 공통적으로 쓰이는 매개변수를 정할 필요가 생겼다. 비주얼 베이직에서도 무리 없이 쓸 수 있게 말이다. 그래서 나온 것이 variant 라는 유형이고 이걸 사용하면 해결이 될 것이다. 이런 결론이 나온 것이다. Variant 라는 데이터형을 잘 모르는 사람도 있을 것 같다. 비주얼 베이직을 사용하는 사람은 대부분 아주 편리하게 사용하고 있을 거라 생각한다. 데이터 형에 신경 쓸 필요 없이 variant라고 선언하면 데이터 형은 비주얼 베이직에 의해 알아서 적절하게 데이터의 포멧이 해석되기 때문이다. 결국 언어 때문에 생긴 거였음을 우리는 알았다
.

, 대충 이 IDispatch 인터페이스가 왜 필요한지 알아봤다
.
그럼 여기서 인터페이스의 종류가 vtable 인터페이스와 dispatch 인터페이스 이 두 종류만 있는 것인가? 그렇지가 않다. 이 두 가지의 메모리 구조를 조합한 이중 인터페이스가 그것이다. 인터페이스의 종류를 앞에서는 표준 인터페이스랑 커스텀 인터페이스로 나눴는데, 이건 또 뭐야 하는 사람도 있을 것 같다. 이건 단지 나누는 방법의 문제가 아닐까 싶다. (한번 생각해 보던지 아니면 앞의 강좌를 참조하면 되겠다
.)
이 이중인터페이스의 경우는 비주얼 베이직에서 만든 COM 개체의 경우 기본적으로 이중 인터페이스를 사용해서 메서드를 제공한다고 한다. 그럼 이중인터페이스가 나온 배경도 살펴보자. 자동화 개체를 구현하기 위해서는 dispatch 인터페이스를 사용해야 한다. 하지만 이 방법은 C++ 과 같이 인터페이스에 직접 접근할 수 있는 언어 입장에서는 오히려 실행속도가 늦어지는 단점을 가진다. 이유는 간단하다. 나중에 하겠지만, IDispatch 인터페이스의 Invoke 메서드를 통해 자동화를 지원하는 메서드에 접근하기 때문이다. 이것으로 메서드 호출과정이 두 세단계는 더 늘어나기 때문이다. 그래서 마이크로 소프트에서는 C++ 같이 인터페이스에 직접 접근이 가능한 언어를 위해서 IDispatch 인터페이스에서 상속받아 만든 인터페이스를 같이 노출시킬 것을 권장하였다. 그러면 비주얼 베이직 스크립트 언어는 dispatch 인터페이스를 총해 자동화 개체의 메서드에 접근하고 C++ 같은 언어는 커스텀 인터페이스로 바로 접근할 수 있게 된다
.
지금까지 대충 어떻게 해서 dispatch 인터페이스가 필요하게 되었고 단점도 조금은 알아봤다. 그럼 본격적으로 IDispatch 의 내부 메서드들를 디비보자
.
IDispatch
인터페이스에는 기본적으로 4개의 메서드가 추가되어 있다
.
GetTypeInfoCount, GetTypeInfo, GetIDsOfNames, Invoke
이렇게 4개이다. 당연히 이것도 IUnknow 에서 상속받았으니 QueryInterface, AddRef, Release가 포함 되어있는 것은 자명한 일일테구
.
사실 이 네 개의 메서드를 구현하는 것은 생각보다 복잡하다. Invoke 메서드를 알겠지만, 인자도 매우 복잡하다. 그래서 COM에서는 IDispatch 인터페이스의 헬퍼메서드들을 제공한다. DispGetIDsOfNames, DispGetParam, DispInvoke, CreateStdDispatch, CreateDispTypeInfo 메서드등등이다. 일단 여기에 대해서는 언급하지 않겠다. 왜냐하면 이부분 역시 책에 자세히 설명 되어 있고 우리가 오늘 할 IDispatch를 알아보는 것과는 직접 관련이 없다고 생각하기 때문이다
.
그럼 각각의 IDispatch인터페이스의 메서드가 하는 역할은 무엇일까
?
먼저 GetTypeInfoCount 메서드를 알아보자. 이 메서드는 개체에 대한 타입 정보가 있는지 알아온다. 타입정보를 제공한다면 1을 그렇지 않으면 0을 리턴하도록 한다. 그리고, GetTypeInfo 메서드에서 타입정보로의 포인터를 리턴한다. , 인터페이스에 대한 타입정보를 얻을 때 사용하는 메서드이다. 타입정보만 얻어오면 게임오버가 아닌가? 타입정보에 메서드의 정보가 다 나와 있기 때문이다. 하지만, 실제로 중요한 메서드는 GetIDsOfNames 메서드와 Invoke 메서드가 되겠다. GetIDsOfNames 메서드는 특정 메서드나 속성 이름에 대응되는 DISPID라는 값을 얻어온다. 그러면 Invoke 메서드에서 해당 코드를 실행하게 할 수 있다. 여기서 메서드가 아닌 속성(Property)이란 말이 나왔다. 여기에 대해 잠시 알아보자. COM 인터페이스는 메서드의 그룹화라고 할 수 있었다. 그리고 데이터 멤버를 설정하고 값을 얻어오기 위해 우리는 일반적으로 Set 또는 Get으로 시작되는 메서드를 사용한다. 하지만 비주얼 베이직에서는 이런식으로 하지 않는다. 그냥 바로 값을 넣는 방법을 사용한다
.
예를 들면, m_edit.width = 100 이런식으로 될 것이다. C++ 이라면 m_edit.SetWidth(100) 라는 메서드를 사용하겠지만 말이다
.
IDL
에서 이 메서드를 속성으로 취급하는 방식을 가지고 있다. IDispatch를 위해 만든 것이다
.
속성을 정의 하는 IDL의 간단한 예를 보자
.

Interface Iobject : IDispatch
{
        [propput]
        HRESULT Prop ([in] VARIANT_BOOL bValue)
        [propget]
        HRESULT Prop ([out, retval] VARIANT_BOOL *bValue);
}

대충 보면 알 듯 싶다. [in] Set_ 이런 함수의 역할 일 것이고 [out] Get_ 이런 함수의 역할을 할 것이라는 걸 충분히 짐작할 수 있다
.

. 지금까지 내가 개념위주로 설명하긴 했지만, 코드역시 조금은 포함이 되어 있었다. 그리고 인프로세스 서버만 중점적으로 다룬 것을 눈치 채신 분들도 있을 것이다. 왜 로컬과 원격서버를 자세히 다루지 않았을까 라고 의심하는 분들도 계시리라 생각한다. 조금은 개인적인 생각이긴 하지만 내 생각엔 앞으로 대부분의 COM 컴포넌트는 DLL 인 인프로세스 서버로 구현될 것 같다. COM+ 에서 자세히 보면 알게 되겠지만, 인프로세스라고 해서 원격이나 로컬에서 실행되지 못할 이유가 없는 것이다. COM+에서 알아서 해주기 때문에 이제는 어디에 쓰일지 미리 예상하고 거기에 맞게 구현할 필요가 없다는 말이다. 잘 생각해보면 왜 이런 생각이 나왔는지 알 수 있다. DLL로 구현하면 다 쓸 수 있다. 하지만, EXE로 만들 경우는 분리된 프로세스에서만 실행 될 수 밖에 없을 것이 아닌가? 물론, 원론적인 COM의 이해를 위해서는 모두 해야 할 것이다. 하지만, 대충 봐서 알겠지만, COM 자체 구현의 차이점은 없었다는 것도 봤을 것이다. 단지 부가적인 DLL 모듈을 작성할 것인가? 아니면 실행 모듈을 작성할 것인가의 차이었지, COM 인터페이스를 선언하고 메서드를 구현하는 것에는 차이가 없었다는 얘기이다. 그냥 내 생각은 이렇다. 한가지만 알아도 다 되는데, 굳이 다 할 필요가 있을까 라는 의미였다. 큰 차이점이 있는 것도 아니고 말이다. (그래도 하고 싶다면 말리진 않겠다
.)

마지막으로 스래딩방법이다. 먼저 이것을 하기 전에 아파트먼트라는 단어에 익숙해져야 한다. ‘아파트먼트라는 말은 우리가 일반적으로 쓰는 우리가 살고 있는 아파트라고 불리는 영문식 표기이다. 그런데 이게 또 COM 이랑 무슨 상관인가? 난 아무리 생각해도 아파트먼트랑 별 상관이 없는 것 같다. 다른 좋은 말들을 놔두고 왜 하필 헷갈리게 아파트먼트인가? 아무리 연관 지어서 생각하려고 해도 만만치가 않았다. 그래도 이해하려면 억지로 끼워 맞춰서라도 이해해야 한다. 가장 황당한 부분이 아닌가 싶다
.
아파트먼트는 일반적으로 STA(Single-Threaded Apartment) MTA(Muiti-Threaded Apartment) 두 가지로 나뉜다. 그런데 요즘은 NA(neutral apartment)(TNA(thread-neutral apartment)라고도 불린다)가 추가되었다. 아마도 이 NA가 앞으로는 표준이 되지 않을까 생각해본다
.
그럼 세부적으로 알아보자
.
프로세스는 하나 또는 이 이상의 STA를 가질 수 있는데, 그럼 이 STA가 도대체 무엇인가
?
해석하면 단일 스래드 아파트먼트이다. 잠시 우리는 여기서 아파트먼트를 하나의 공간으로 생각해보자.(사실, 공간은 아니다.) 일단, STA 에는 실행 스래드를 하나만 가질 수 있다. 하지만, 하나의 프로세스가 여러 개의 STA를 가지는 것이 가능하기 때문에 동시에 여러 메서드를 호출하는 것도 가능은 할 것이다. 그리고, 이 호출은 윈도우 메시지 큐를 사용하기 때문에 자동으로 동기화 되고 디스패치된다. (여기서 주의 할 점이 있다. 인프로세스 서버의 경우 윈도우가 메시지 처리를 하기 때문에 메시지 루프를 가질 필요가 없다.) 쉽게 말하자면, STA는 여러 개의 COM 개체를 관리할 수도 있고 하나만 관리 할 수도 있다. 하지만, 한번에 실행은 한 놈만 실행 시킨다는 것이다. 이번엔 너의 메서드를 호출해 주도록 하지, 담엔 너, 담엔 너 이런 식으로 말이다
.

두번째인 MTA 의 경우는 대충 예감할 수 있을 것이다. 하나의 프로세스는 단 하나의 MTA만 가질 수 있다. MTA 역시 STA와 마찬가지로 여러 개의 COM을 가질 수 있다. 대신 동시에 여러 개의 메서드 호출을 처리할 수 있는 장점이 있다. 하지만, STA 처럼 동기화에 신경 쓸 필요가 없는 것이 아니기 때문에 동기화 개체들을 사용해서 직접 동기화를 제공해야 한다. 대신 퍼포먼스가 뛰어나다는 장점이 있다. 아무래도 STA를 여러 개 사용할 경우 생기는 여러 개의 동시작업으로 인한 스래드 컨택스트 스위칭이 줄어들기 때문이 아닐까라고 생각한다
.
이 부분의 자세한 내용은 책을 참조하기 바란다. 책이 기술적으로 훨씬 자세하게 설명되어 있다. 내가 할 말은 이 아파트먼트를 일종의 스래드 모델로 이해하기 바란다는 거다. 이때는 이런 방법으로 운영하면 좋을 것이다. 또는 이럴 때는 이렇게 하는 것이 퍼포먼스가 뛰어 날것이다. 이런 것만 알면 된다. COM 컴포넌트 하나 달랑 쓰는데 MTA 쓰는 것도 무식한 짓이고, 대용량 서버에 몇 만대의 클라이언트가 붙는데 여기서 STA를 쓰는 것도 무식한 짓이다. 대충 개념만 알고 적절하게 선택만 해주면 나머지는 COM 이 알아서 해준다
.

참고로, 비주얼 베이직의 경우 STA로만 프로그래밍이 가능하다. 여기서 우리는 높은 퍼포먼스를 위해 MTA NA를 써야 하는데 COM 개체 작성에서 왜 C++을 써야 하는 지 알 수 있다. 이밖에도 C++에서만 제공할 수 있는 세부적인 사항들이 많다. 이런 점들 때문에 COM 의 최고의 언어로 C++을 꼽는 이유이다
.
오늘은 여기서 마치겠다. 막판에 오니깐 빨리 끝내고 싶은 맘이 굴뚝 같아 진다. 이 글을 읽는 분들께는 미안하지만, 왠지 COM+에서 새로운 맘으로 더 좋은 글을 쓰고 싶기에 여기서는 조금의 미완성으로 남기고 싶다
.

그럼 다음강좌에서 보자
.

------------------------------------------------------------------

다음이 COM의 마지막이 될 듯 싶습니다. 혹시나 모자라면 더할 지는 모르겠지만, #10에서는 지금 까지 나온 용어들과 개념을 총정리 할 생각합니다. 이정도 개념만 알면 책 보는데는 지장 없겠지 하는 정도가 될 것 같습니다
.
오늘 하루도 많이 애 쓰셨습니다. 특히나 오늘은 더 어렵죠
? ^^;

그리고 잡담입니다
.
점점 IT 쪽 상황이 어려워 지고 있습니다. 정확히 말하면 일반 어플리케이션으로 패키지를 만들어서는 회사 존립 자체가 어려워 지고 있습니다. 웹도 이제는 서비스 개념이 아니면 힘들어 집니다. 옛날 홈페이지 구축해주고 돈 버는 시대는 갔습니다. 얼마 하지도 않습니다. 인건비 겨우 빠집니다. 일반 패키지 만들어도 돈 주고 사는 사람도 없습니다. 돈을 벌려면 회사를 상대로 상품을 팔아야 합니다. 개인 사용자가 얼마나 돈이 있겠습니까? 그리고 불법복제가 몸에 벤 사람들입니다. 저 역시 마찬가지 이지만, 집에서 쓰는 제품들 중에 정품이 얼마나 될까요? Copy 팔지 않더라도 비싸게 파는 것이 훨씬 개발자에겐 편하고 이득도 클 겁니다. 새로운 아이디어도 점점 찾기 힘들어 집니다. 회사 입장에서는 개발 시간이 길어지는 프로젝트는 꺼려 할 겁니다. 일반 API로 프로그래밍을 한다는 것도 이제는 조금은 지양해야 합니다. 시간 많이 걸리죠. 노력 많이 필요하죠. 이걸 기초라고 생각하시는 분들이 많이 계십니다. 하지만 전 그렇게 생각하지 않습니다. API가 왜 기초입니까? 돌아가는 내부 구조 역시 모르는 것은 마찬가지입니다. 정말 기초는 리눅스 커널 분석하면서 새로 API를 만드는 것이 기초입니다. 어차피 이런 거 안 할 거라면 COM으로 편하게 작업하는 것이 낫지 않을까요? 마이크로소프트도 이제는 API 가 아닌 COM 하나 던져주고 이걸로 짜면 돼 라고 말합니다. 이제 말 안 해도 제 말뜻 아시리라 생각합니다. 한번쯤 고민해보시길 권장합니다. 커피 한잔 마시면서 또는 담배 한대 피면서 말이죠
.

그럼 오늘하루도 즐겁게
^^


------------------------------------------------------------------


e-mail :
icoddy@hotmail.com
msn id : icoddy@hotmail.com
-
박성규 -
///

나의 COM(Component Object Model) 경험담 #10



드디어 마지막입니다
.
너무 오랫동안 잠수를 하였군요
. ^^;
확실히 생각보다 글을 쓴다는 것은 힘든 일입니다. 뒤로 가면 갈수록 내용은 부실해지고 날림공사가 되어 가는 기분입니다. 아무래도 빨리 끝내야겠다는 맘이 이런 결과를 가져오지  않았나 싶습니다. 이게 생각보다 큰 부담감으로 작용하더군요. 이런 강좌 아닌 강좌가 처음이라서 그럴 수도 있을 것 같기는 한데, 다음에는 좀 여유를 가지고 천천히 일관 되도록 써봐야겠습니다. COM 경험담의 마지막인 만큼 유종의 미를 거둬야 겠죠
. ^^;





그럼 시작하겠습니다
.






오늘은 커넥션 포인트(Connection Point)에 대해 중점적으로 다루고 나머지 다루지 못했던 부분인 타입 정보와 코드 재사용을 위한 통합과 포함에 대한 간단한 이해와 지금까지 해왔던 내용에 대한 요약을 해 보도록 하자
.

먼저 커넥션 포인트다. 이 커넥션 포인트와 함께 등장하는 싱크라는 개념 역시 중요하다. 천천히 알아보자. 여기서 잠시 인터페이스를 보자면, IConnectionPointContainer, IEnumConnectionPoints, IConnectionPoint, IEnumConnections 이 네 개의 인터페이스가 커넥션 포인트를 지원하기 위한 인터페이스이다.(.. 여기서 자세하게 알 필요가 없다. 이 글 끝에 첨부한 내용을 참고하면 되겠다
.)
그럼 도대체 커넥션 포인트가 무엇이란 말인가? 대충 짐작이 가능하듯이 뭘 연결해준다는 의미 같아 보인다. 간단하다. COM 서버에서 어떤 일이 발생했음을 클라이언트에게 알릴 수 있도록 해주는 놈이 바로 이 커넥션 포인트이다. (이놈이 연결하고 무슨 상관이냐고? 좀더 지켜 보도록 하자.) 지금까지 우리는 클라이언트에서 일방적으로 서버에게 서비스를 요청하는 얘기만 해왔다. 하지만, 이제는 서버에서도 클라이언트에게 어떤 이벤트의 전달이나 서비스를 요청할 수도 있다는 걸 알아야 한다
.
아직 무슨 말인지 잘 모르겠다고? 간단하다. 지금까지의 내용을 다시 떠 올려보자. 우리는 클라이언트에서 COM 개체를 생성해서 그 인터페이스를 얻어내고 그 인터페이스를 통해 메서드를 호출을 통해 그 서비스를 사용하였다. 하지만, COM 개체도 자존심은 있다. 왜 맨날 주기만 해야만 하는가? 나도 좀 받아보자라고 충분히 생각할 수 있다. 그렇다고 해서 클라이언트의 메서드를 호출할 수는 없는 일이다. 왜냐? 클라이언트가 어떤 메서드를 구현할지 어떻게 알겠는가? 알 방법이 없다. , 그 메서드의 메모리 번지가 어디에 생성될지는 컴파일러만이 알 것이다. 결국 우리는 이 메모리 번지를 알 수만 있다면 특정 메서드를 호출하는 것이 가능하겠지만, 지금은 불가능하다. 그리고 만드는 사람 맘이니 어쩔 수 없다. 그래서 생각한 것이 이벤트라는 놈이다. 그냥 나한테 이런 일이 생겼어라고 클라이언트에게 알려주기만 하면 된다. 여기서 이 이벤트라는 놈이 바로 커넥션포인트라는 메커니즘으로 구현된다. 이제 알았겠지? (이 설명에서 약간의 문제점이 있다. 나중에 설명하겠지만, 클라이언트의 메서드를 호출한다는 말이 맞을 수도 있다. 이것은 조금 있다가 알아보자
.)

여기서 또 많은 용어가 나오기 시작한다. 이걸 하기 위해서는 앞에서 잠시 언급한 싱크라는 놈도 알아야 하고 소스 인터페이스라는 놈도 알아야 한다. 항상 그랬듯이, 그럼 알아봐야지. 결국 이 두 놈 역시 떼어놓고는 말할 수 없다. 특이하게도, 이 두 놈의 메서드는 서로 일치한다
.
(
여기서 어느 정도 이제 감을 잡으신 분들도 있을 것이다. 그리고, 말하지 않고 넘어온 중요한 전제가 있다. 이것은 모두 자동화와 관련된 내용임을 알고 있어야 한다. 그래서 앞으로는 클라이언트 서버의 개념보다는 자동화 컨트롤러와 자동화 개체라는 말을 사용하겠다. 어느것이 서버이고 클라이언트인지는 말하지 않겠다
.)

그럼, 커넥션포인트의 핵심인 소스 인터페이스와 싱크라는 놈이 하는 역할이 무엇인가? 간단하게 말하자면, 이벤트를 발생시키는 놈과 이벤트를 받아들이는 놈이라 표현해야 할 것 같다. 따라서 소스 인터페이스는 자동화 개체에서 구현될 것이고 싱크라는 놈은 자동화 컨트롤러(클라이언트)에서 구형되는 놈이다. 여기서 문제점이 발생한다. 싱크 역시 인터페이스로 구현된다. 그런데 자동화 클라이언트라는 놈은 일반적인 우리가 자주 만들던 MFC 프로그램들이다. MFC로 만들진 않겠지만, VC 사용자가 대부분 MFC 사용자라는 것을 감안할 때, 난감한 일이 아닐 수 없다. 왜 난감하냐구? COM 개체를 만드는 것도 아닌데, 단지 사용하기 위해서 이벤트라는 놈을 받아들이기 위해서도 COM 개체의 구현방법을 알아야 하기 때문이다.(싱크라는 놈이 COM 개체라고 해도 무방하기 때문이다.) 결국 우리는 자동화 컨트롤러에서 COM 개체의 일종인 싱크 개체를 구현해야 한다는 말이다. 그러면, 자동화 개체에서 이벤트가 발생하게 되고 이 자동화 컨트롤러에서 구현한 싱크 개체의 메서드들을 호출하는 것이다. 이것으로 앞에서 언급한 메서드를 호출한다는 말이 맞을 수도 있다는 말이 무슨말인지 조금은 보완이 되었으리라 생각한다
.

잠시 정리해 보도록 하자
.
이 부분은 생각보다 아주 어려운 부분이다. 그러면서도 아주 중요하게 취급되는 부분이기도 하다. COM의 중급자라면 이 부분이 아주 유용하게 쓰이는 부분이기 때문에 잘들 사용하고 계실 것이다. 하지만, 이 강좌를 보고 있는 대다수의 초보자분들은 도대체가 무슨 말을 하는 건지도 잘 이해하지 못하시는 분들이 많을 것이다. 항상 그랬듯이 간단하게 이해할 수 있는 방법을 생각해 봐야 한다. 내가 코드를 가지고 또 설명을 하면 포기하는 사람을 늘일 뿐일 것 같다. 간단히 개념을 설명하는 것도 만만치 않은 작업이긴 하지만, 나름대로 간단하게 정리 해 보도록 하자
.
여기서부터 좀 주의 깊게 들어주길 바란다
.
자동화 개체에서 먼저 어떤 이벤트가 있는지, 이런 이벤트가 발생할 때 이런 메서드를 호출할 것이다라고 먼저 정의한다.(당연히 IDL에서 하겠지…) 이것이 소스 개체이다. 이 말은 소스 인터페이스에서 정의 한다는 말이다
.
그 다음은 자동화 컨트롤러에서 싱크 개체를 구현하는데 이 싱크 개체의 구현은 자동화 개체에서 정의한 소스 인터페이스의 메서드들을 싱크 개체에 맞도록 구현한다. 왜냐하면 결국 자동화 개체에서 이 싱크 개체의 메서드를 호출하기 때문이다
.
그러면 여기서 커넥션 포인트가 해주는 역할은 무엇일까? 그것은 바로 이 두 놈을 연결해주는 역할을 하는 것이 이 커넥션 포인트가 해주는 일이다. 이것은 또 어떻게 가능할까? 이것은 커넥션포인트의 메서드들을 살펴보면 답이 나온다
.
이 두 놈들을 연결시키고 해제 시키는 역할을 하는 메서드가 IConnectionPoint::Advice 메서드와 IConnectionPoint::Unadvice 메서드이다. 사실 내부는 간단하다 앞장에서 했듯이 QueryInterface 메서드로 가능하게 된다. , IOutGoing 인터페이스를 받아옴으로써 가능해지는 일인 것이다. , 앞에서 말한 메서드의 메모리 번지를 얻어 온다고 해야 할까? 하여튼 그런 개념일 것이다. 그리고 싱크에서 중요한 것이 IOutGoing 인터페이스인데 이 IOutGoing에 대해서도 알아 봐야 한다
.
싱크 개체의 구현은 IUnknown IOutGoing 인터페이스로 구현되는데 IUnknwon은 언급할 필요가 없고 IOutGoing 에 대해서만 조금 알고 넘어가자. 이 인터페이스의 메서드는 GotMessage 라는 메서드가 있다. , 메시지를 받았을 때 작동하는 놈인 것 같다는 생각이 들것이다. 여기서 이벤트가 발생할 때 자동화 컨트롤러에 알리면 자동화컨트롤러에서 구현한 싱크 개체의 이 메서드가 호출됨을 알 수 있다
.

, 지금까지 숨가쁘게 넘어왔다. 잠시 숨 좀 돌리자
.
결국은 여러분 대부분이 ATL을 사용해서 프로그래밍을 하게 될 것이다. 말리진 않겠다. 사실 나 조차도 ATL이 편한 것은 사실이다. 하지만, 프로그래밍을 하다 보면 ATL에서 제공하는 메크로에 대해 황당한 생각이 자주 드는 것이 사실이다. 왜냐하면 어떻게 돌아가는지 내부를 전혀 모르기 때문이다. 조금은 개념을 생각하면서 프로그래밍을 하는 것이 나중을 생각해서는 더욱 시간을 아끼는 일이라고 말하고 싶다. 비주얼 베이직에서든, 자바에서든 싱크를 구현하는 방법이 다양하게 지원되고 있다. 비주얼 베이직의 경우 아주 간단하다. 자바역시 C프로그래밍보다 훨씬 간단하다. 가장 힘든 사람은 C 프로그래머들이다. 하지만, 어렵다고 생각할 이유는 없다. 간단한 샘플만 하나 가지고 있다면 그대로 적용만 시키면 되기 때문이다
.

앞에서 IConnectionPointContainer, IEnumConnectionPoints, IConnectionPoint, IenumConnections의 인터페이스들이 있다고 언급했었다. 사실 중요한 것은 IconnectionPointContainer, IconnectionPoint 라고 생각한다. 커넥션 포인트컨테이너의 경우는 커넥션 포인트를 얻기 위해 필요한 인터페이스라 생각하면 되겠다. 메서드로는 IconnectionPointContainer::FindConnectonPoing IconnectionPointContainer::EnumConnectionPoints를 지원한다. 메서드 이름에서 짐작할 수 있듯이 하나를 받아오느냐 여러 개를 받아오느냐의 차이일 뿐이다. 이 커넥션 포인트의 경우 멀티캐스트를 지원하기 때문에, 다시 말하면 하나의 자동화 개체가 여러 개의 싱크들에게 이벤트를 전달 할 수도 있기 때문에 여러 개의 커넥션포인트를 가질 때도 있다. 여기서 약간의 문제점은 멀티캐스트라고 해서 네트웍 프로그래밍의 멀티캐스트를 생각하면 곤란하겠다. 이유는 한번의 이벤트를 발생시키는 것이 아니라 각각의 싱크들에게 이벤트를 여러 번 전달해야 하는 이유 때문이다.(차라리 브로드캐스트라는 말이 어울릴 듯 하다
.)

참고로, 이 이벤트에 대한 자세한 내용은 앞에서 잠시 언급했듯이 마이크로소프트의 기술문서를 참조하기 바란다. 찾아보라는 얘기는 아니다. 여러분들을 위해 이 글 마지막 부분에 추가 시켜 놓았다. 처음에는 내가 써보려고 했지만, 내용이 중복되는 것이 너무 많은 것이 아닌가? 거기다 신뢰할 수 있는 자료라는 점에서 나의 설명과는 구분된다. 참고이긴 하지만 꼭 읽어보길 바란다
.

앞에서 소스 인터페이스에서 정의한 메서드와 싱크 인터페이스의 메서드들이 일치한다고 언급했었다. 따라서 소스 인터페이스의 정보를 모르는 상태에서는 싱크 인터페이스를 만들 수 없다는 얘기도 된다. 이 소스 인터페이스에 대한 정보를 얻기 위해 사용되는 인터페이스가 IProvideClassInfo 인터페이스인데 여기서는 언급하지 않겠다. 단지 이런 것이 있다는 것을 알면 된다. 사실, 대부분은 IDL에서 이 정보를 알 수 있기 때문에 자주 사용하지는 않을 것 같다
.
전반적인 과정을 정리해보도록 하자
.
먼저 자동화 컨트롤러는 자동화 서버의 IConnectionPointContainer를 요청한다. 그러면 자동화 서버에서 이 인터페이스를 넘겨 줄 것이다. 그리고 우리는 이 인터페이스의 메서드인 FindConnectionPoint를 호출해서 IConnectionPoint 인터페이스를 얻어올 것이다. 그리고 다시 IConnectionPoint 인터페이스의 Advice 메서드를 호출함으로 인해 반대로 자동화 서버에서 자동화 컨트롤러에서 구현된 싱크 인터페이스를 받아오게 된다. 그 다음은 자동화 서버에서 이벤트가 발생할 때 싱크 인터페이스의 메서드들을 자유롭게 호출할 수 있게 되는 것이다
.
지금까지 대충 커넥션 포인트에 대해 살펴 보았다. 자세한 코드 내용은 앞에서 말했듯이 이글 마지막에 첨부된 내용을 참조하면 되겠다
.

이제는 타입 정보에 대해서 간단히 알아보도록 하자. 여기에 대해서 그렇게 자세하게 알 필요가 있을까 하는 것이 내 생각이라서 대충 알고 넘어가는 수준에서 끝내도록 하겠다
.
타입 라이브러리라는 말은 앞에서 몇 번 들어 봤을 것이다. 쉽게 생각하자면 IDL 파일의 이진 버전이라고 생각하는 것이 가장 쉽다. COM 개체의 각종 정보가 들어 있는데 주로 인터페이스의 이진 설명들이라 할 수 있다. 여기에는 메서드와 인자, 리턴값들에 대한 정의가 있을 것이다. 그래야만 많은 다른 언어에서 이 정보에 접근하는 것이 가능해지기 때문이 아닐까라고 생각 한다. 이 타입 라이브러리를 만들기 위해 ICreateTypeLib ICreateTypeInfo 인터페이스를 지원한다. 앞에서 타입 라이브러리를 만들기 위해 IDL 파일을 MIDL로 컴파일 하는 것을 봤었다. 이 때 MIDL이 사용하는 두 주요 인터페이스가 앞의 두개 이다. 다른말로 하자면 MIDL 이 없이도 IDL 파일에서 우리는 MIDL이 만드는 타입라이브러리와 똑 같은 타입라이브러리를 생성할 수가 있다. 여기서 의문점이 생길 수 있다. MIDL에서 다 해주는데 우리가 이 타입정보를 굳이 알 필요가 있을까? 사실, 외부 툴(MIDL)을 사용한다는 자체가 맘에 안들 수가 있다. COM 자체에서 모든 것이 해결가능한데 왜 MIDL을 써야 하냐고 의문인 사람들, 특히나 고집스러운 프로그래머들은 대부분 그렇게 생각한다. 이때 유용하게 사용할 수 있는 COM 인터페이스가 앞의 두 인터페이스이다
.

마지막으로 포함(Contanment) 과 통합(Aggregation)에 대해 알아보자
.
COM
에서는 개체 상속을 지원하지 않는다. 일반적인 C++ 프로그래밍에 익숙한 프로그래머들에게는 조금은 생소할 수가 있다. 결국 COM에서는 구현상속이 아닌 인터페이스 상속으로 문제를 해결하고 있다. 하지만, 잘 생각해 보자. 인터페이스가 순수 가장함수 테이블임을 가만할 때 인터페이스 상속이라 해봐야 우리는 구현을 새로 다 해야 한다. 그렇다면 구현코드를 가져와서 사용할 방법은 없다는 말인가? 재사용의 이점을 버린 것인가? 그렇지가 않다. COM에서도 구현상속과 비슷한 방법이 있다. 그것이 바로 포함과 통합이라는 방법이다
.
차이점을 굳이 들자면 코드 차원의 재사용이 아닌 이진레벨의 재상용성이라는 더 큰 이점이 있다는 것이 가장 중요하다. 조금은 생소할 수 있지만, 살펴보면 그다지 어렵지는 않을 것이다
.
간단하게 알아보도록 하자
.
포함(Contanment) 과 통합(Aggregation)은 조금은 헷갈리는 부분이다. 둘 다 하나의 개체를 구현하면서 이미 구현된 개체를 내부에 가지면서 그 개체의 기능을 활용하는 방법이다. 포함의 경우는 하나의 개체에서 다른 개체의 인터페이스를 완전히 다시 구현하는 것이고, 포함의 경우는 그 인터페이스는 그대로 사용하고 새로운 인터페이스를 추가로 구현하는 방법이라 이해하면 되겠다
.
좀더 자세히 알아보면 포함의 경우 말 그대로 C++에서 class 안에 class을 선언한 경우와 비슷하다. 차이점은 이 과정이 인터페이스 레벨에서 이루어진다는 것이다. 예로 OutsideCOM 개체와 InsideCOM 개체가 있다고 가정하자. 그리고 InsideCOM의 인터페이스로 IAdd 라는 인터페이스가 있다고 가정한다면 OutsideCOM에도 IAdd 인터페이스가 있어서 이 OutsideCOM 개체의 IAdd 인터페이스가 InsideCOM 개체의 IAdd 인터페이스의 역할을 대신하도록 할 수 있다는 것이다. 내부적으로 InsideCOM 개체의 IAdd 인터페이스의 메서드를 그대로 호출해서 똑 같은 동작을 하게 할 수도 있다
.
통합의 경우는 말그대로 InsideCOM 개체의 IAdd 인터페이스를 OutsideCOM 개체의 인터페이스인양 바로 밖으로 노출시키는 방법이다. 대신 추가적인 기능보다는 내부 개체의 기능을 그대로 사용하는데 중점을 두는 방식이라 하겠다
.

지금까지 COM의 대략적인 개념들을 살펴보았다
.
내용상으로 많이 부족한 것도 사실이다. 하지만, 이 부분에 대해서는 책을 참조하길 권하고 싶다. 내가 할 수 있는 일은 앞장에서 잠시 언급했듯이 개념을 잡는데 조금이라도 도움을 줄 수 있다면 그것으로 만족한다. 처음의 각오는 상세한 부분까지 다 하고 싶었지만, 너무 막대한 분량인 것 같았다. 그래서 이 정도가 나의 한계이다
.






이제 끝이다. COM+ 에서 다시 보게 될지는 솔직히 의문이다
.









잡담으로 좀 넘어가보자
.
COM
자체를 중점적으로 공부하고자 하는 사람이 있다면 말리진 않겠다. 중요한 것이 사실이고, 고급 프로그래밍 기법에 속하는 분야라서 취직도 그만큼 잘 되리라 생각한다. 한 예로 ActiveX, COM 가능자라는 구인란을 봐도 그런 것 같다. 하지만, 이것 또한 얼마나 갈지 모르겠다. .NET 시대로 갈 것이기 때문일 것이다. 기본 개념만 확실히 잡혀있다면 별 문제가 되지 않을 것이라 생각되지만 결국은 새로운 언어를 배워야 하는 부담 역시 남아있다
.
COM
.net 컴포넌트로 대체될 것이고 DCOM역시 .net 리모팅으로 기타 COM+등등도 .net에서 새로운 기능으로 전환되고 있는 시점이다. 이전에 COM 라리브러리를 사용해서 복잡하게 COM 개체를 생성하는 방법 역시 .net 컴포넌트에서는 일반적인 프로그래밍 처럼 new 하나로 바뀐다. 메모리 해제는 가비지 컬렉션이라는 놈이 알아서 해주게 되고 점점 프로그래밍이 쉬워진다는 느낌이지만, 공부해야 할 내용은 더욱 많아지고 있다. 당장은 아닐  것이다. 장담은 못하겠지만 개인적인 생각으로는 적어도 5년 정도는 COM 프로그래머가 먹고 사는 데는 지장 없을 것이란 생각이지만 설 자리가 5년 동안 천천히 줄어들 것임은 자명한 일이다. 결국 내가 하고 싶은 말은 COM을 주로 팔 것인지, 아니면 개념상 공부만 할 것인지를 여기서 분명히 판단할 필요가 있다고 생각한다. (어디까지나, 이 글을 읽고 있는 초보들을 위한 말이다.) 아직 공부할 시기인 학생들이라면 COM의 개념을 중점으로 공부하고 간단한 샘플하나만의 작성으로 충분하리라 생각한다. 그리고 앞으로의 대세인 .net 프로그래밍 쪽으로 권하고 싶다. 그렇다고 COM에서 배운 지식 중에서 버릴 것은 거의 없을 거라 생각한다. 모든 개념은 거의 대부분 그대로 사용되고 있다. 그리고 당장 취업이 눈앞인 사람이 .net을 공부한다는 것은 무모한 일이라 생각한다. 이러한 분들은 오히려 ActiveX 쪽이 훨씬 쉽지 않을까 생각한다. MFC 할 줄 아는데요 하는 것보단 ActiveX 컨트롤 몇 개 만들어 봤습니다가 훨씬 잘 먹혀 들어갈 것이기 때문이다. 그리고 프로그래밍의 열정이 그대로 남아 있다면 집에서 틈틈히 .net을 공부하길 추천한다
.
내가 이런 말 할 자격이 없다는 것은 나 자신이 더 잘 알고 있다. 나도 잘 모른다. 할 줄 아는 것도 없다. 하지만, 주위 사람들로부터 듣는 말은 나이만큼이나 나보다 어린 사람들 보다는 더 나을 수도 있다고 생각하기 때문에 이런 말을 하는 것이라 이해해주면 고맙겠다
.
더 이상 언급하는 것은 잔소리로 밖에 들리질 않을 것 같다. 그만 하겠다
.


^^, 다들 애 쓰셨습니다
.
이제는 이런 말도 끝입니다
.^^
지금까지 저의 글을 읽어주신 분들에게 감사의 말씀을 전합니다(너무 부실하게 시작한 것이 끝도 부실하게 만드는군요
.)
다들 즐거운 하루 보내시구요. 기회가 되면 COM+에서 만나도록 하겠습니다
.

그리고 부가적으로 한 말씀 더 올리자면, 앞으로는 XML이 가장 중요하게 취급되는 시대가 올겁니다. XML을 한번도 보지 않으신 분들(VC++ 사용자라면 더욱 많을 듯 싶은데)은 관심이 있던 없던 무조건 해야 합니다. 이것을 HTML 차원으로 생각하신 분들이 있다면 그것 또한 엄청난 오류입니다. 앞으로의 모든 데이터 처리와 메세징 그리고 웹서비스와 거의 모든 분야에서 XML을 기본으로 발전해 갈겁니다. 다시 말해서 XML을 빼고 프로그래밍을 얘기한다는 자체가 시대의 흐름을 따라가지 못하고 도태되어 간다고 말할 수 있을 것 같습니다. 무슨 말도 안되는 소리냐고 반문하시는 분이 없지 않을 겁니다. 하지만, 한번쯤은 XML관련책이나 .NET 프로그래밍에서 XML 부분을 읽어 보시길 권장합니다. ~입니다. 보다 넓게 보는 시야를 키웁시다
.



e-mail :
icoddy@hotmail.com
msn id : icoddy@hotmail.com
-        
박성규





















<
마이크로소프트의 기술문서 첨부
>
Dr. GUI
COM 이벤트, 1

1999
9 13

시작하기에 앞서
현재, COM은 스레드를 완벽하게 처리할 수 있는 기능을 가지고 있고, Dr. GUI 역시 마찬가지여서(그러나, 상당히 많은 추가 작업이 필요함), 이벤트에 대한 기본 적인 개념이 혼동될 정도입니다
.
Dr. GUI
Windows 타이머 SetTimer API를 사용하여 작동되는 단일-스레드 타이머 개체를 만들었습니다. 그러나, 이 타이머 개체는 약간의 문제를 가지고 있으며, 타이머가 종료되기 전에 클라이언트가 타이머를 중단시키려고 하면, 이 문제는 더욱 심각해집니다. 또한, 이 접근 방법의 경우, 메시지와 타이머, 그리고 큐가 아주 복잡하게 얽혀 이벤트의 개념이 혼동되기 시작하였습니다
.
그래서 우리는 이벤트가 무엇을 할 수 있는지를 보여주는 더 간단한 개체를 만들어 보기로 하였습니다. 게다가, 이 개체는 코드의 중복을 줄이기 위해 강력한 프로그래밍 기법까지 제공합니다. 물론, Dr. GUI는 다음 칼럼에서 멀티-스레드 문제를 가지고 다시 돌아올 계획입니다
.
Windows 2000
에서 쉽게 사용할 수 있는 우수한 응용 프로그램

Windows 2000
의 날이 얼마 남지 않았습니다. 그 때가 될 때까지 그냥 날짜만 세고 있는 것보다는, Windows 2000의 새로운 기능을 배워 두는 것도 좋지 않을까요
?
Windows 2000
은 여러분의 응용 프로그램과 그 응용 프로그램을 실행하는 시스템을 보다 신뢰성 있고, 안정되고, 강력하게 만들 수 있는 풍부한 기능으로 가득 차 있습니다. 또한, 여러분에게 셋업, 트랜잭션, 큐 같은 복잡한 일을 보다 쉽게 처리할 수 있도록 표준 방법을 제공하는 새로운 시스템 서비스가 아주 많습니다
.
Windows CE H/PC Pro
Terminal Server Client
직접 Windows CE H/PC Pro 장치를 구입하신 분 계십니까? 만약 그렇다면, 그리고 NT Terminal Server를 보유하고 있으시다면, Terminal Server client  를 다운로드하는 것이 좋을 것입니다. Terminal Server 클라이언트를 설치하면, 거의 모든 NT 응용 프로그램을 H/PC Pro에서 사용할 수 있습니다

H/PC Pro
나 다른 NT Terminal Server 클라이언트 하드웨어를 갖고 있지 않더라도, Windows 95/98/NT에서 Terminal Server를 클라이언트로 사용할 수 있습니다. 그 이유는 Terminal Server는 여러분의 하드웨어, OS, 또는 응용 프로그램 환경과 다른 환경에서 사용이 가능하기 때문입니다. 예컨대, Microsoft사의 Developer Support 팀이 구 버전의 Visual Studio® NT Terminal Server를 설치했습니다. 고객이 구 버전의 개발 환경에 관한 문의를 해오면, 지원 팀의 공학자는 구 버전을 기계에 설치하는 번거로움 없이, 해당 NT Terminal Server 박스를 통해 고객이 경험하고 있는 문제를 재연할 수 있습니다
.
구 버전의 Visual Studio로 구축한 프로젝트에 대해서도 동일한 방식으로 적용할 수 있습니다. 단순히 프로젝트와 해당 버전의 Visual Studio(기타 다른 개발 환경)에 적합하게 NT Terminal Server를 설치하면 되고, 해당 라이센스 보유자는 자신의 자리에서 그 프로젝트를 계속 관리할 수 있습니다
.
Windows 2000 DDK
Windows 2000 Device Driver
를 사용해보십시오. 또한, Windows 2000 DDK RC1(Release Candidate 1) 버전을 무료로 다운로드하실 수 있습니다
. ( http://www.microsoft.com/ddk/  ).
초고속 Microsoft 웹 응용 프로그램 서버
여러분은 Microsoft의 웹 서버가 확장성이 떨어진다고 생각할 수도 있습니다. 그러나, PC Week의 확장성 벤치 마크 검사 결과는 그 예상과 다르게 나왔습니다. 이 벤치 마크 검사는 Microsoft 웹 플랫폼이지구상의 어떤 비즈니스도 수용할 수 있을 만큼 빠르다"는 결론을 내렸습니다
.
현재와 앞으로의 계획

이번 칼럼에서는, COM 이벤트에 관해 다루도록 하겠습니다. 다행히도, 이벤트에 관한 내용은 체계적으로 문서화되어 있으며, 앞으로도 계속해서 설명해 드리겠지만, 약간 생소한 내용의 이벤트도 종종 있으므로, 이벤트에 익숙해지는 것이 좋을 것입니다. 이번 칼럼에서는 이벤트를 파악하는 방법과 이벤트를 받는 원리에 관해 설명하겠습니다
.
다음 칼럼에서는, ATL로 이벤트를 구현하는 방법과 이벤트를 받는 Visual Basic 클라이언트를 작성하는 방법을 알아볼 계획입니다. 또한, 이벤트를 사용하여 얼마나 설계를 간단하게 할 수 있는지에 관해서도 설명할 것입니다
.
이벤트

이벤트란 무엇인가?
드디어, 이벤트를 설명할 때가 되었습니다. 그렇다면, 이벤트란 무엇일까요
?
돌이켜보면, 우리가 지금까지 COM에서 보아온 통신은 매우 단편적인 것이었습니다. , 클라이언트가 개체에 대하여 메서드를 호출하는 것이 전부였죠
.
물론, 개체가 반환 값을 재전달하기는 하지만, 그것은 어디까지나 클라이언트가 요청할 때만 발생합니다. , "필요할 때에만 이야기를 합니다
."
이 방식("필요할 때에만 이야기한다")은 단순히 명령에 응답하기만 하는 “dumber” 개체는 물론 다른 여러 개체에도 적용됩니다
.

<img src="../images/com_10/image003.gif">

그림 1. IFoo 인터페이스를 사용하는 클라이언트와 그 클라이언트의 개체 간의 단 방향 통신. 이름이 지정되지 않은 인터페이스는 IUnknown입니다
.
잠깐만! 클라이언트! 나 할 말이 있어

개체가 클라이언트에게 뭔가 특별한 일이 발생했음을 알려야 할 때 어떻게 해야 할까요? 예를 들어, 버튼 같은 시각 컨트롤이 클라이언트에게 자신이 언제 클릭되었는지를 알려주어야 할 경우가 있습니다. 또는, 비즈니스 규칙을 구현하는 개체는 클라이언트에게 규칙이 위반되었음을 알려야 할 것입니다. 또는, 개체가 백그라운드에서 어떤 작업을 하고 있고, 클라이언트에게 그 작업이 끝났음을 알려야 하는 경우도 있습니다.(백그라운드의 경우는 이 기사에서 다루지 않습니다
.)
폴링

물론, HasButtonBeenClicked, HasRuleBeenViolated, 또는 ArentYouDoneYET와 같이 개체 내에서 메서드를 구현할 수도 있습니다. 이 경우, 개체는 자신의 상태를 규정하는 플레그를 유지하고 그것을 메서드 호출에 대한 응답으로 반환합니다. 반면에, 클라이언트는 계속해서 메서드를 폴링해야 합니다. 이 방법은 비효율적이고 프로그래밍하기도 어려우므로 바람직하다고 볼 수는 없습니다.
응답하는 컴포넌트

대신, 개체가 클라이언트에 있는 메서드를 호출할 수 있다면 어떨까요? 이 경우, 개체는 버튼이 클릭되었거나, 규칙이 위반되었거나, 작업이 완료되었을 때처럼, 조건이 허락되면 즉시 메서드를 호출할 수 있을 것입니다. 또한, 클라이언트는 이벤트가 발생했다는 통보를 신속하게 받을 수도 있습니다(지나치게 동시적이지도 않고 비동시적이지도 않음). 이것을 그림으로 나타내면 다음과 같습니다
.

<img src="../images/com_10/image004.gif">

그림 2. 클라이언트와 개체 간의 양방향 통신. , IFoo인터페이스를 사용하는 클라이언트로부터 개체로의 통신과 IFooEvents 인터페이스를 사용하는 개체로부터 클라이언트로의 통신

이제 client object가 인터페이스에 대해 호출을 할 때 사용할 인터페이스를 구현해야 합니다. 하지만, 인터페이스를 지정하는 것은 여전히 object입니다. 개체는 이 인터페이스에 대한 호출의 소스이므로, 이 인터페이스를 source 인터페이스라고 합니다. 개체를 이벤트의 소스가 되는 것으로 생각하면 기억하기 쉬울 것입니다
.
클라이언트는 이 인터페이스 호출에 대한 sink입니다. 지금부터 source sink라는 단어는 개체(source)와 클라이언트(sink)를 가리키는 말로 사용하겠습니다. Dr. GUI소스 개체라고 지칭한 것은연결 가능한 개체를 의미하기도 합니다. 여기서는 편의상 두 단어를 모두 같은 것으로 간주하도록 하겠습니다
.
COM
의 이벤트 기능

COM
이벤트는 기본적으로 단순합니다. COM 이벤트는 간단히 말해서 개체(source)가 클라이언트(sink)에 대해 메서드를 호출할 수 있는 수단입니다. COM은 이러한 작업을 수행하기 때문에, 이벤트 메서드는 인터페이스 포인터를 통해 호출되며, 이는 다음과 같은 세 가지 의미를 갖습니다
.
1.        
동일한 이벤트 인터페이스 내에 서로 관련된 여러 개의 이벤트가 들어 있을 수도 있습니다. 예를 들어, 여러 종류의 클릭(한번 클릭, 더블 클릭 등), 여러 가지 위반 사항, 또는 여러 완성 단계(프로세스 내/완료
).
2.        
클라이언트는 인터페이스를 구현하는 간단한 미니-COM 개체를 구현해야 할 것입니다. (일반적으로 미니-개체는 IUnknown과 이벤트 인터페이스만 구현합니다.) 클라이언트의 개체는 소스 개체로부터 호출을 받기 때문에, sink 개체라고도 합니다
.
3.        
클라이언트는 어떻게 해서든 sink 개체의 인터페이스 포인터를 source 개체로 전달해야 합니다. (다소 복잡한 부분임
)
이러한 기본적인 내용 외에도, COM의 이벤트 구성은 다음과 같은 여러 가지 특별한 기능을 지원합니다
.
·        
하나의 개체(또는 source)는 두개 이상의 소스(이벤트) 인터페이스를 지원할 수 있습니다.(이들 인터페이스는 각각 하나 이상의 메서드를 가질 수 있으므로 융통성이 매우 높다고 할 수 있습니다
.)
·        
여러 sink 개체가 동일한 인터페이스로부터 이벤트를 받을 수 있습니다.(이것을 멀티 캐스팅이라고 함). 이를 위해서는, source 개체가 이벤트를 받고 싶어하는 모든 sink 개체를 기억하고 있어야 합니다
.
·        
클라이언트 내의 sink 개체는 하나 이상의 개체로부터 이벤트를 받을 수 있습니다
.
이벤트를 구현하려면 무엇이 필요한가
?
소스 인터페이스

지금까지 이벤트 인터페이스라고 불러온 인터페이스의 또 다른 명칭은 소스 인터페이스입니다. 이 인터페이스가 소스 인터페이스라고 불리는 이유는 인터페이스에 대한 호출의 소스가 되는 이벤트-파이어(event-firing) 개체 안에 선언되어 있기 때문입니다
.
이 샘플에 대한 소스 인터페이스는 아주 간단합니다. 다음은 인터페이스 정의 언어(IDL)입니다
.
[
uuid(F2F660CF-3ED7-11D3-9C8C-000039714C10),
   helpstring("_IAAAFireLimitEvents Interface")
]
dispinterface _IAAAFireLimitEvents
{
properties:
methods:
[id(1), helpstring("method Changed")]
            HRESULT Changed(IDispatch *Obj,
       CURRENCY OldValue);
[id(2), helpstring("method SignChanged")]
                    HRESULT SignChanged(IDispatch *Obj,
       CURRENCY OldValue);
};
Changed
이벤트는 개체의 값이 변경될 때마다 파이어되고, SignChanged 이벤트는 개체의 값 부호가 변할 때마다 파이어될 것입니다
.
dispinterface?
여러분이 가장 먼저 발견하게 되는 것은 이벤트에 대한 인터페이스의 타입이 dreaded dispinterfece, 즉 디스패치 인터페이스라는 것입니다. 그 이유가 무엇인지 궁금하시겠지요
?
하지만, 디스패치 인터페이스는 더 느리고 프로그래밍하기가 지루하다는 단점에도 불구하고, 한가지 중요한 장점을 가지고 있습니다. 그것은 바로 임의의 디스패치 인터페이스로부터의 호출을 정확하게 해석할 수 있는 코드를 작성하기가 비교적 쉽다는 것입니다. 여러분은 IDispatch, 특히 Invoke를 구현하기만 하면 됩니다. 매개 변수는 variant의 배열로 전달되는데, 스택을 통해 실제로 전달된 매개 변수를 찾는 것보다 파싱(구문해석) 작업이 쉽습니다. 클라이언트 개체는 타입 라이브러리를 사용하여 어떤 메서드가 존재하는지(필요에 따라서는, 그 메서드의 매개 변수가 무엇인지)를 알아내야 합니다
.
더 중요한 두 번째 이유는 디스패치 인터페이스를 사용할 경우, 호출을 받는 개체가 인터페이스 내의 모든 메서드 구현을 제공할 필요가 없다는 것입니다. 여러분이 정규 사용자 정의 인터페이스를 구현할 때 그 인터페이스에 있는 모든 메서드를 구현해야만 최소한 E_NOTIMPL을 반환할 수 있다는 점을 상기해 보십시오
.
디스패치 인터페이스의 경우, IDispatch의 메서드는 모두 구현해야 하지만, Invoke를 구현할 때는 디스패치 인터페이스 내의 모든 메서드를 구현할 필요가 없습니다. Invoke는 자신이 지원하지 않는 메서드(다시 말해서, 처리하고 싶지 않은 이벤트)에 대해 오류(DISP_E_MEMBERNOTFOUND)를 반환합니다
.
Visual Basic
이나 응용 프로그램용 Visual Basic(VBA)과 같은 언어는 사용자 정의 인터페이스에 대한 이벤트 호출보다 디스패치 인터페이스에 대한 이벤트 호출을 받기가 더 수월하므로, 디스패치 인터페이스에 대한 이벤트 호출만 지원합니다. C++로 직접 sink를 작성하고 다른 클라이언트에 관여하지 않는 경우에는, 사용자 정의 인터페이스를 향상된 성능으로 사용할 수 있습니다. 그러나 대부분의 경우, 이벤트는 비교적 적기 때문에 성능은 큰 문제가 되지 않습니다. 소스 인터페이스가 듀얼 인터페이스가 되는 것은 아무 의미가 없습니다. 듀얼 인터페이스가 갖는 이중성은 그 인터페이스의 메서드가 직접 호출 가능하고, IDispatch::Invoke를 통해 호출될 수도 있다는 데 있기 때문입니다. 다시 말해서, 듀얼 인터페이스는 호출을 받는 데 관여하는 것이지 호출 생성에는 관여하지 않습니다. Visual Basic과 호환 가능한 고성능의 이벤트 인터페이스를 만들려면, 서로 다른 인터페이스 ID(IID)를 갖는 두 개의 동등한 소스 인터페이스를 구현하고(하나는 사용자 정의 인터페이스, 다른 하나는 디스패치 인터페이스), Visual Basic에 부합하도록 디스패치 인터페이스를 기본 설정으로 만들어야 합니다
.
여러분이 발견하게 될 또 한 가지는 인터페이스 이름이 밑줄(_)로 시작된다는 것입니다. 이것은 Visual Basic 규칙인데, 인터페이스 이름이 밑줄로 시작되면, Visual Basic 환경은 그 인터페이스를 디스플레이하지 않습니다
.
이러한 사항을 제외하면, IDL 코드에는 별로 새로운 것은 없습니다
.
소스를 사용하라

그런데 한 가지 빠진 것이 있습니다. 특정 개체 안의 인터페이스가 소스 인터페이스인지 어떻게 알 수 있을까요? 인터페이스는 기본 설정에 의해 들어오는 인터페이스기 때문에 지금까지는 이 부분에 대해 거론한 적이 없었습니다
.
인터페이스 자체에 대한 IDL에는 그 인터페이스가 source 인터페이스인지 sink 인터페이스인지를 알려주는 정보가 없습니다. 사실, 인터페이스는 그것이 어떻게 사용되느냐에 따라 source 인터페이스도 될 수 있고 sink 인터페이스도 될 수 있습니다. 인터페이스가 source 인터페이스인지 여부는 특정 개체에 의해 결정되므로, 인터페이스는 IDL coclass 섹션 안에서 지정합니다
.
coclass AAAFireLimit
{
[default] interface IAAAFireLimit;
[default, source] dispinterface _IAAAFireLimitEvents;
};
주의할 것은 _IAAAFireLimitEvents 인터페이스를 나가는(source) 인터페이스와 기본 설정 소스 인터페이스 두 가지로 모두 지정했다는 점입니다. Visual Basic과 스크립팅 언어는 하나의 COM 개체 당 단 한 개의 소스 인터페이스를 처리하므로, default 속성을 사용하는 것이 가장 바람직합니다(여러 개의 소스 인터페이스가 있는 경우에는 필수적임
).
개체에 대한 sink의 인터페이스 포인터 얻기

이와 같이 인터페이스를 규정한 후에는, 그 인터페이스에 대한 인터페이스 포인터를 얻어야 합니다. (여기서는 클라이언트가 그것을 제대로 구현한다고 가정하겠습니다.) 그러면 클라이언트는 소스 인터페이스를 구현하는 sink 개체를 만들고, 그 개체의 인터페이스 포인터를 source 개체로 넘겨줍니다
.
물론, 이 작업은 간단하지는 않습니다
.
COM
은 안정적이긴 하지만 복잡한 솔루션이다: 커넥션 포인트와 컨테이너

COM
설계자는 자주 사용되지 않는 기능을 추가하기도 합니다. 예를 들어, 몇몇 IDispatch 메서드는 인터페이스 ID(IID) 매개 변수를 가지고 있는데, 이것은 여러 개의 디스패치 인터페이스를 지원하기 위한 것입니다. 그러나 그 요소는 구현되지도 않았고, COM 표준의 일부로 생성되지도 않았습니다. 따라서, 디스패치 인터페이스는 COM 개체 당 하나의 인터페이스가 있는 것으로 간주합니다
.
이벤트의 경우, COM 설계자가 원한 것은 상기 설명한 요소를 지원하고(개체마다 여러 개의 나가는(또는 발신) 인터페이스 등) 여러분이 개체에 대한 메인 코드를 변경하지 않고도 이벤트 인터페이스를 변경할 수 있게 하는 것이었습니다
.
이 문제에 대한 COM의 솔루션은 source 개체가 커넥션 포인트(connection point)라고 하는 미니- COM 개체를 구현하게 하는 것입니다. 소스 개체는 나가는(또는 발신) 인터페이스 각각에 대해 정확히 한 개의 커넥션 포인트 개체를 갖으며, 각 커넥션 포인트는 정확히 하나의 나가는(또는 발신) 인터페이스에 서비스를 제공합니다
.
커넥션 포인트는 독립적인 COM 개체이지만, CoCreateInstance에 의해 생성되는 것이 아니라 개체에 의해 생성됩니다.(이에 관해서는 나중에 간단히 설명할 것입니다.) 커넥션 포인트는 일반적으로 단 두 개의 인터페이스, IUnknown IConnectionPoint를 구현합니다
.
커넥션 포인트와
IConnectionPoint
IConnectionPoint
인터페이스의 메서드를 살펴보면 커넥션 포인트가 어떤 일을 하는지 가장 쉽게 이해할 수 있습니다
.
가장 중요한 것은 Advise Unadvise입니다. 클라이언트(sink)는 커넥션 포인트 개체에 대해 IConnectionPoint::Advise를 호출하여 연결을 설치합니다. (클라이언트가 커넥션 포인트 개체에 대한 인터페이스를 어떻게 얻는지는 당분간 생각하지 않기로 하겠습니다.) Advise에 사용되는 매개 변수는 이벤트 인터페이스를 구현하는 sink 개체에 대한 IUnknown 포인터입니다. Advise의 구현 부분은 이 인터페이스 포인터를 커넥션 포인트 개체 안에 저장합니다. 그러면, source 개체는 커넥션 포인트 개체로부터 인터페이스 포인터를 얻고 그 포인터에 대해 메서드를 호출하여 이벤트를 파이어할 수 있습니다
.
Advise
는 이 연결을 식별하는 고유 정수 값인 쿠키를 반환합니다. 연결을 끊으려면, 클라이언트가 동일한 쿠키를 사용하여 Unadvise를 호출해야 합니다. 그러면 커넥션 포인트 개체가 관련된 인터페이스 포인터를 삭제합니다. 클라이언트는 더 이상 이벤트를 받지 말아야 할 경우에(예를 들어, 클라이언트가 종료될 때) 반드시 이 작업을 수행해야 합니다
.
멀티케스팅이 지원되므로, 동일한 커넥션 포인트를 사용하여 두개 이상의 연결을 만들 수도 있습니다. 이벤트가 발생하면, source 개체는 각 연결에 대해 적절한 메서드를 호출해야 합니다. 20개의 개체가 Advise를 호출하는 경우, Changed 이벤트가 발생하면 20 개의 Changed 호출이 생성됩니다(Advise로 전달된 각 인터페이스 포인터에 대해 하나씩). 앞에서도 말했듯이, 커넥션 포인트는 모든 인터페이스 포인터를 저장할 책임이 있습니다
.
IConnectionPoint
안에 있는 그 다음 메서드는 source 개체의 편의를 위한 것입니다. EnumConnections IEnumConnections를 구현하는 개체(아직은 미니 개체)에 대한 포인터를 반환합니다. 이 경우, source 개체는 호출 생성에 필요한 인터페이스 포인터를 얻을 수 있습니다
.
마지막 두 개의 메서드는 클라이언트나 소스에 의해 사용되며, 그 이름이 의미하는 대로 GetConnectionInterface는 이 커넥션 포인트가 서비스를 제공하는 인터페이스의 IID를 반환하고, GetConnectionPointContainer source 개체의 IConnectionPointContainer 포인터를 반환합니다.(이에 관해서는 곧 설명할 것입니다
.)
복습: 클라이언트의 경우, 커넥션 포인터의 함수는 연결을 만들고(Advise) 연결을 파이어하는(Unadvise) 방법을 제공합니다. 이것을 이해하면, 나머지도 부분도 쉽게 이해할 수 있습니다
.
커넥션 포인트 컨테이너와
IConnectionPointContainer
이제 우리는 커넥션 포인트를 사용하여 연결을 설치하는 방법을 알게 되었습니다. 미니-개체의 IUnknown 포인터를 커넥션 포인터의 Advise 메서드로 넘겨줍니다
.
그런데, 초기에 커넥션 포인트에 대한 포인터를 어떻게 얻으면 될까요
?
앞에서 설명했듯이 COM 개체는 하나 이상의 커넥션 포인트를 지원할 수 있습니다. 이를 위해서는, source 개체가 IConnectionPointContainer 인터페이스를 구현해야 합니다. 그러면 클라이언트 개체는 이 인터페이스를 통해 커넥션 포인트를 얻을 수 있습니다
.
IConnectionPointContainer
는 단 두 개의 메서드를 갖는 간단한 인터페이스입니다. 먼저 FindConnectionPoint 메서드는 클라이언트 개체에 의해 전달된 IID에 지정된 커넥션 포인트에 대한 포인터를 반환하고, EnumConnectionPoints source 개체에 의해 지원되는 모든 커넥션 포인트를 홀더가 탐색할 수 있게 해주는 IEnumConnectionPoints 열거자를 반환합니다
.
결합 방법

Source
개체는 IConnectionPointContainer를 구현하고, IConnectionPointContainer의 메서드를 통해 검색하고 연결할 수 있는 커넥션 포인트들의 컬렉션을 관리합니다. 각 커넥션 포인트는 활성 연결의 목록을 관리하고, 활성 연결은 커넥션 포인트의 Advise 메서드 호출에 의해 설치되어 커넥션 포인트의 Unadvise 메서드 호출에 의해 파이어됩니다. 이들 연결은 열거가 가능합니다.(커넥션 포인트 개체 이외에도, IConnectionPointContainer::EnumConnectionPoint에 대하여 IenumConnectionPoints를 구현하는 개체와 IConnectionPoint::EnumConnections에 대하여 IEnumConnections를 구현하는 개체를 만들어야 합니다.) 따라서, 총 세 개의 서로 다른 미니-COM 개체가 생성됩니다
.
다소 복잡하기는 하지만 여러 개의 이벤트 인터페이스와 멀티캐스팅을 지원하려면 이 방법이 반드시 필요합니다
.
모든 연결 작업이 완료되었을 때 개체의 모습은 다음과 같습니다
.

<img src="../images/com_10/image005.gif">

그림 3. 커넥션 포인트 설치(또는 설정). IConnectionPoint에 대한 ICP 표준
.
이벤트를 설치를 위한 호출순서

이제 모든 개체와 인터페이스에 대해 살펴보았으므로, 클라이언트가 이벤트를 받는 데 필요한 단계를 설명하겠습니다
.
1.        
먼저 클라이언트는 IConnectionPointContainer에 대한 source 개체를 질의합니다. , 연결 가능한 모든 개체(source 개체)는 반드시 IConnectionPointContainer를 구현해야 하는데, IConnectionPointContainer를 구현하지 않는 개체는 어떠한 이벤트도 발생할 수 없습니다
.
2.        QueryInterface
가 성공하면, 클라이언트는 IConnectionPointContainer::FindConnectionPoint에 자신이 받고자 하는 이벤트 인터페이스의 IID를 전달합니다. 연결 가능한 개체가 이 인터페이스를 지원하는 경우, 개체는 그 인터페이스에 대한 커넥션 포인트 개체를 가리키는 포인터를 반환합니다. 선택적으로, 클라이언트는 IConnectionPointContainer::EnumConnectionPoints를 호출하여 열거자를 얻을 수 있으므로, 지원되는 모든 커넥션 포인터를 검사하여 지원 가능한 모든 커넥션 포인트를 찾을 수 있습니다
.
3.        
커넥션 포인트에 대하여 포인터를 얻었다고 가정하여, 클라이언트는 그 포인터에 대한 IConnectionPoint::Advise를 호출하며, 이때 실제로 이벤트를 받게 될 미니-개체를 가리키는 IUnknown 포인터를 전달합니다. 클라이언트는 Advise가 반환하는 쿠키를 저장하는 역할을 합니다
.
4.        
이 시점에서, 클라이언트는 자신이 전달한 인터페이스 포인터에 대한 이벤트를 받게 됩니다
.
5.        
클라이언트가 이벤트를 더이상 받고 싶지 않을 때(가령, 클라이언트가 종료 될 때), IConnectionPoint::Unadvise를 호출하면서 단계 3에서 저장했던 쿠키를 넘겨줍니다
.
이벤트 발생시키기

일단 연결을 만들고 나면, 이벤트를 파이어하는 일은 비교적 간단합니다. , Source 개체가 연결을 열거하고, 각 연결에 대해 적절한 메서드를 호출하기만 하면 됩니다
.
대부분의 이벤트 인터페이스는 디스패치 인터페이스이므로 이 때, 적합한 방법은 어떤 매개 변수를 사용하여 어떤 디스패치 메서드를 호출할 것인지를 나타내는 해당 매개 변수를 갖는 IDispatch::Invoke입니다
.
이벤트 받기

이제 sink 미니-개체는 이벤트를 받아 자신이 선택한 대로 이벤트를 처리합니다. 이 작업은 비교적 간단한 편입니다
.
이벤트와 스레드

다음은 기본적으로 지켜야 할 규칙입니다
.
특별한 경우가 아니라면(나중에 자세히 설명할 것임), 항상 IConnectionPoint::Advise를 호출했던 스레드와 동일한 스레드의 이벤트를 파이어해야 합니다. 일반적으로 또 다른 스레드를 시작하여 이 스레드로부터 이벤트를 파이어할 수 없습니다. 왜냐하면, 이벤트를 파이어한다는 것은 인터페이스 포인터를 사용하여 또 다른 개체를 호출한다는 것을 의미이기 때문입니다
.
COM
에서는 절대로 스레드 간에 인터페이스 포인터를 전달할 수 없습니다. 대신, 스레드를 마셜링해야 하며, 이를 위해서 아주 긴 이름을 가진 API , CoMarshalInterThreadInterfaceInStream을 호출합니다
.
다른 스레드에 대하여 스트림 포인터를 전달하고 새로운 스레드에 있는 CoGetInterfaceAndReleaseStream을 호출하면 마셜링된 인터페이스 포인터를 얻을 수 있습니다. 유감스럽게도, ATL의 이벤트 파이어 구현은 인터페이스 포인터를 마셜링하지 않으므로, 또 다른 스레드로부터 이벤트를 파이어하려면 약간의 추가 작업이 필요합니다
.
두 가지의 일반적인 경우에는, 이 것은 별 문제가 되지 않습니다. , 여러분이 마우스 단추 누름 메시지 같은 Windows 메시지에 대한 응답으로 이벤트를 파이어하는 경우에는 별 문제가 생기지 않으며, 메시지를 수신하였을 때 생성되었던 동일한 스레드 상에서 실행됩니다
.
아니면, 여기에 나온 예처럼, 메서드 호출에 대한 응답으로 이벤트를 파이어하는 경우를 들 수 있습니다. 여기서도, 메서드는 생성된 것과 동일한 스레드에서 호출되기 때문에 아무런 문제가 없습니다
.
여러분이 직접 명시적으로 스레드를 생성한 경우에는(예를 들면, 백그라운드 프로세싱을 위해) 새로 생성된 스레드로부터 이벤트를 파이어하기는 어렸습니다
.
발생된 COM+ 이벤트는
?
COM+
는 이벤트의 영역에서 중요한 변화를 가져옵니다. , COM+는 우리가 여기서 이야기한 COM 이벤트를 여전히 지원할 뿐 아니라 publish/subscribe 스키마가 사용되는 영속 이벤트를 포함하는 이벤트를 보다 쉽게 파이어하고 파괴할 수 있는 새로운 방법을 제공합니다
.
하지만, 이 부분에 대한 자세한 설명은 나중으로 미루도록 하겠습니다. 당분간은 Visual Basic과 스크립팅 언어가 이해할 수 있는고전적인” COM 이벤트만 생각하기로 합시다
.
다음 기사: ATL이 이벤트를 더 쉽게 만든다
!
커넥션 포인트에 관한 설명을 보면 알 수 있듯이, 이벤트 파이어에는 수많은 작업이 따릅니다. 먼저, 네 개의 서로 다른 개체(메인 소스 개체, 커넥션 포인트 열거자, 커넥션 포인트, 그리고 커넥션 열거자) 안에 각각 네 개의 인터페이스(IConnectionPointContainer, IEnumConnectionPoints, IConnectionPoint, 그리고 IEnumConnections)를 만들어야 합니다. 또한, 두 개의 컬렉션(커넥션 포인트와 커넥션)을 관리해야 하고, 이벤트를 파이어할 때 모든 연결의 인터페이스 포인터에 대해 적절한 메서드를 호출하는 코드를 구현해야 합니다. 이벤트를 파이어하는 데는 이처럼 많은 작업이 필요한 것입니다
!
더 쉬운 방법은 없을까요? Microsoft Foundation Classes (MFC) 또는 Active Template Libraries (ATL)를 사용하는 것도 좋은 방법입니다. 그 방법에 관해서는 다음에 설명하도록 하겠습니다
.
현재와 앞으로의 계획

이번 기사에서는 COM 이벤트와 커넥션 포인트 등에 관해 알아보았습니다. 실제 코드에 관한 내용은 다음 기사에서 다루도록 하고, 코드 다운로드를 원하시는 분은 http://msdn.microsoft.com/voices/drgui0899.zip   이용하시기 바랍니다
.

Dr. GUI

Dr. GUI
COM 이벤트, 2
1999
11 15
Dr. GUI
의 비트와 바이트
웹에서 향기를 맡아 보십시오
John Waters
의 영화 Polyester를 기억하십니까? 이 영화를 상영하는 극장에서는 관객에게 번호를 매긴 향기 카드를 나누어주었지요. 영화 중간에 어떤 숫자가 화면에 나타나면, 관객들은 카드에서 해당되는 숫자를 긁어 스크린에 나타나는 장면의 향기를 맡을 수 있었습니다.
DigiScents
라는 회사가 계획한 대로라면, 여러분은 얼마 안 있어 PC "Smell-O-Vision"을 부착하고 웹을 탐색하면서 향기를 맡을 수 있을 것입니다. 이 제품에는 다양한 향수 오일이 사용될 것이라 하는군요
.
이 회사가 다음에는 어떤 아이디어를 낼지 사뭇 궁금해지는군요
.
Microsoft Exchange
개발자 센터

MSDN
에는 점점 더 많은 개발자 센터가 생기고 있습니다. 가장 최근에 생긴 것은 Microsoft Exchange 개발자  를 위한 개발자 센터입니다. 이 곳을 방문하면 곧 발표될 Exchange 2000에 관한 정보와 Exchange 2000의 베타 버전을 받아볼 수 있습니다. 현재 Exchange를 사용하지 않는 개발자라도 유용하고 다양한 정보를 많이 얻을 수 있습니다.
이벤트를 발생시키는 ATL COM 개체

COM
이벤트 : 잠깐 복습
지난 번 기사 에서 다루었던 이벤트에 관한 내용을 복습해 봅시다.
먼저, 대부분의 이벤트 인터페이스는 dispatch 인터페이스라 할 수 있습니다. 그 이유는 클라이언트가 IDispatch를 구현하고 매개 변수를 알아내는 개체를 만드는 작업이 임의의 사용자 정의 인터페이스를 통해 호출된 매개변수를 알아내는 것보다 훨씬 더 간단하기 때문입니다. 이벤트를 발생시키는 개체, source 개체는 dispatch 인터페이스를 소스 인터페이스로 정의하고 클라이언트 개체가 타입 라이브러리를 통해 그 인터페이스를 사용할 수 있도록 합니다. 이벤트는 인터페이스를 통해 정의되기 때문에, 동일한 인터페이스에 여러 개의 이벤트 메서드가 있을 수도 있습니다
.
클라이언트는 개체의 IConnectionPointContainer 구현 여부를 질의하여 개체가 이벤트를 발생시키는지 알아낼 수 있습니다. 개체가 IConnectionPointContainer를 구현한다면 그 개체는 하나 이상의 이벤트 인터페이스를 구현할 수 있다는 것을 뜻합니다. 이 경우, 클라이언트는 IConnectionPointContainer::EnumConnectionPoints를 호출하여 개체가 어떤 이벤트 인터페이스를 구현하는지 알아낼 수도 있고, IConnectionPointContainer::FindConnectionPoint를 호출하여 특정 커넥션 포인트를 요청할 수도 있습니다
.
커넥션 포인트는 그 이벤트 인터페이스에 대한 모든 커넥션을 관리하는 미니 COM 개체로서 소스 개체에 의해 구현됩니다. 커넥션 포인트는 일반적으로 IUnknown IConnectionPoint만을 구현합니다
.
소스의 커넥션 포인트 중 하나에 대한 인터페이스 포인터를 얻기 위해 클라이언트는 이벤트를 받는 미니 COM 개체를 만든 다음, 미니 COM 개체에 대한 인터페이스 포인터를 IConnectionPoint::Advise에 전달함으로써 이벤트 인터페이스 커넥션을 설치합니다. 이벤트를 받는 미니 개체는 일반적으로 IUnknown IDispatch만 구현합니다
.
커넥션이 완성되면, 소스 개체는 클라이언트에 의해 전달된 인터페이스 포인터에 대한 메서드를 호출하여 이벤트를 발생시킬 수 있습니다. 여러 개의 클라이언트가 IConnectionPoint::Advise를 호출하는 경우, 소스 개체는 각 클라이언트가 Advise에 전달한 인터페이스 포인터에 대해 적절한 메서드를 호출하는 루프를 실행함으로써 모든 클라이언트 호출에 대해 이벤트를 발생시켜야 합니다
.
더 이상 이벤트를 받고 싶지 않을 때, 클라이언트는 IConnectionPoint::Unadvise를 호출하여 커넥션을 해제합니다. 또한, 클라이언트는 종료전 반드시 커넥션을 해제해야 합니다
.
이것을 코드로 작성하려면 상당히 복잡할 뿐 아니라 버그가 발생할 수도 있습니다. 우선, 커넥션 포인트 개체는 커넥션 목록을 관리해야 하고, 이벤트를 발생시키는 과정에서 커넥션 목록을 열거하여 각 커넥션에 대해 적절한 메서드를 호출해야 합니다. 아 참! 깜박 잊을 뻔했군요. VARIANT 매개 변수로 구성된 배열을 만들고, 그 배열을 많은 매개 변수를 갖는 IDispatch::Invoke 메서드에 전달하여 dispatch 인터페이스 메서드 호출도 생성시켜야 합니다. 작업이 복잡하지요? 하지만, 우리에게는 ATL이라는 좋은 친구가 있습니다
.
ATL
로 구현해보자
.
커넥션 포인트에 대한 설명에 나와 있듯이, 이벤트를 발생시키기 위해서는 많은 작업이 필요합니다. 우선, 네 개의 서로 다른 개체(메인 소스 개체, 커넥션 포인트 열거자, 커넥션 포인트, 그리고 커넥션 열거자) 안에 각각 네 개의 인터페이스(IConnectionPointContainer, IEnumConnectionPoints, IConnectionPoint, IEnumConnections)를 만들어야 합니다. 또한, 두 개의 컬렉션(커넥션 포인트와 커넥션)을 관리해야 하고, 이벤트를 발생시킬 때 모든 커넥션에 대한 인터페이스 포인터에 대해 적절한 메서드를 호출하는 코드도 구현해야 합니다
.
더 쉬운 방법은 없을까요? Microsoft Foundation Classes (MFC)를 사용하는 것도 좋은 방법 중 하나입니다. 하지만, 많은 개체가 필요하다는 단점이 있지요. 그렇다면, ATL(Active Template Libraries)을 사용하면 어떨까요
?
구현 능력이 뛰어난
ATL
ATL
IUnknown, IDispatch, IclassFactory와 같은 중요한 인터페이스에 대해 가장 효율적이고, 완벽하고, 디버깅된 스톡 템플릿 구현을 가능케 할 뿐만 아니라, 이벤트 처리에 필요한 구현 기능도 제공합니다
.
이러한 구현은 대부분 템플릿화된 클래스를 통해 가능하지만, 이벤트 발생 루프의 구현은 Implement Connection Point 마법사에 의해 생성되는 "프록시(proxy)" 클래스에 포함되어 있습니다.(이 마법사는 원래 ATL Proxy Generator라는 독립적인 프로그램이었습니다. 그 이름만 들어도 골치가 아픈 것 같군요
)
IDL
만들기

먼저, 소스 인터페이스에 대한 IDL(Interface Definition Language) 코드가 있어야 합니다. 이 코드는 지난 번 기사에서 자세히 설명한 바 있지만, 여러분의 이해를 돕고자 다시 한번 소개합니다. 소스 인터페이스 자체에 대한 IDL도 있어야 하지만, 개체 IDL coclass 섹션에는 인터페이스를 소스 인터페이스로 선언하는 부분도 있어야 합니다. 다음은 소스 인터페이스 자체에 대한 IDL입니다.
[
uuid(F2F660CF-3ED7-11D3-9C8C-000039714C10),
helpstring("_IAAAFireLimitEvents Interface")
]
dispinterface _IAAAFireLimitEvents
{
  properties:
  methods:
  [id(1), helpstring("method Changed")]
     HRESULT Changed(IDispatch *Obj,
        CURRENCY OldValue);
  [id(2), helpstring("method SignChanged")]
     HRESULT SignChanged(IDispatch *Obj,
        CURRENCY OldValue);
};
coclass
섹션에서 인터페이스를 소스 인터페이스로 선언하는 부분(볼드체로 표시)은 다음과 같습니다

coclass AAAFireLimit
{
  [default] interface IAAAFireLimit;
  [default, source] dispinterface _IAAAFireLimitEvents;
};
ATL
의 마법사를 적절히 사용하면, 위의 코드를 직접 작성할 필요가 없습니다
.
클래스 유도

ATL
IConnectionPointContainer IConnectionPoint에 대한 스톡 템플릿 구현인 IConnectionPointContainerImpl IConnectionPointImpl을 제공합니다. 두 구현을 모두 사용하기는 하지만, IConnectionPointImpl는 간접적으로 사용할 것입니다.
메인 소스 개체(클래스 이름이 CAAAFireLimit인 개체) 안에 IConnectionPointContainer를 구현하기 위해서는 두 가지 작업을 거쳐야 합니다
.
첫째, 아래의 코드 라인을 상속 리스트에 추가합니다
.
public IConnectionPointContainerImpl<CAAAFireLimit>
Dr.GUI
는 프로젝트 이름을 "AAAFireLimit"로 정했기 때문에, 클래스 이름이 CAAAFireLimit가 됩니다. 앞의 "AAA"는 이 개체가 Visual Basic 개체 참조 리스트(Project.References)의 맨 위에 들어가게 할 목적으로 사용된 것입니다. 개체가 리스트의 맨 위에 있으면, 개체 참조 및 참조 취소 작업이 더 수월합니다
.
둘째, 개체의 COM 맵에 IConnectionPointContainer에 대한 항목을 추가합니다
.
COM_INTERFACE_ENTRY(IConnectionPointContainer)
커넥션 포인트 맵

커넥션 포인트 컨테이너는 커넥션 포인트 맵에 의존하며, 커넥션 포인트 맵에는 커넥션 포인트 당 한 개의 엔트리가 들어 있습니다(예를 들어, 소스 인터페이스 당 한 개의 엔트리). 이 맵은 CAAAFireLimit 클래스 선언 내에 포함됩니다.
BEGIN_CONNECTION_POINT_MAP(CAAAFireLimit)
CONNECTION_POINT_ENTRY(DIID__IAAAFireLimitEvents)
END_CONNECTION_POINT_MAP()
이 맵은 IConnectionPointContainer 구현에 ID DIID__IAAAFireLimitEvents인 인터페이스(, 인터페이스 IAAAFireLimitEvents)에 대한 커넥션 포인트가 단 하나임을 알려줍니다. 더 많은 소스 인터페이스를 구현했다면, CONNECTION_POINT_ENTRY 매크로가 더 많았을 것입니다
.
이제 IconnectionPoint 문제는 해결 되었군요. 그러면, 실제 커넥션 포인트와 이벤트를 발생시키는 루프는 어떻게 얻을 수 있을까요
?
ATL
의 개체 마법사를 적절히 사용하면, 위의 코드는 자동적으로 추가됩니다. 그러나, 이벤트를 디버그하거나 기존의 개체에 직접 코드를 추가해야 하는 경우에는, ATL의 개체 마법사에 의해 추가된 코드를 알아야 합니다
.
IConnectionPointImpl
과 프록시 클래스

이제, 여러분은 IConnectionPointImpl로부터 상속을 받을 것이라고 생각하시겠지요? 그렇지만, IConnectionPointImpl로부터 직접 상속받는 대신, Visual Studio로 하여금 IConnectionPointImpl로부터 유도되고 각 이벤트 메서드에 대해 Fire_[event] 추가 멤버 함수를 구현하는 템플릿화된 "프록시" 클래스를 생성하게 합니다. 우리가 만든 인터페이스는 Changed SingChanged라는 두 개의 메서드를 가지므로, 커넥션 포인트 프록시 클래스는 Fire_Changed Fire_SignChanged를 구현합니다.
각 메서드의 구현에는 IConnectionPoint::Advise로 전달된 인터페이스 포인터 각각에 대한 호출을 생성시키는 루프가 포함되어 있습니다
.
이제 템플릿화된 프록시 클래스로부터 상속을 받습니다. 이 클래스(Fire_ 메서드 포함) Implement Connection Points 마법사라고 하는 프로그램(전에는 ATL Proxy Generator로 했음)에 의해 생성됩니다. 이 마법사를 어떻게 실행시키는지는 나중에 자세히 설명할 것입니다. 주의할 것은 이 마법사가 입력 값으로 타입 라이브러리를 요구하므로, 프록시를 생성하기 전에 반드시 IDL 파일을 컴파일해야 한다는 것입니다
.
Implement Connection Points
마법사는 아래의 코드 라인을 CAAAFireLimit의 상속 리스트에 추가합니다
.
public CProxy_IAAAFireLimitEvents< CAAAFireLimit >
CProxy_IAAAFireLimitEvents
는 마법사가 생성한 클래스의 이름입니다. 마지막으로, 마법사는 커넥션 포인트 맵에 커넥션 포인트 엔트리를 추가합니다
.
BEGIN_CONNECTION_POINT_MAP(CAAAFireLimit)
CONNECTION_POINT_ENTRY(DIID__IAAAFireLimitEvents)
END_CONNECTION_POINT_MAP()
커넥션 포인트가 여러 개인 경우(, 복수의 소스 인터페이스인 경우), 각 커넥션 포인트에 대해 프록시 클래스로부터의 유도를 시도해야 합니다. 주의해야 할 점은 하나의 상속 목록에 있는 동일한 클래스로부터 여러 번 상속을 받을 수 없다는 점인데, 그러나 이것은 별 문제가 되지 않습니다. 동일한 템플릿이라고 해도 서로 다른 매개 변수에 대해 약간씩 다른 클래스를 생성하기 때문에 동일한 클래스로부터 여러 번 상속을 받는 일은 없을 테니까요
.
다음은 Fire_ 메서드 중 하나(Fire_SignChanged)가 생략된 클래스의 코드 리스트입니다. 프록시 클래스가 IConnectionPointImpl로부터 유도되는 것을 볼 수 있습니다. Fire_ 메서드에 대한 코드를 보십시오. 이 코드를 직접 작성할 필요가 없다는 게 얼마나 다행입니까
?
template <class T>
class CProxy_IAAAFireLimitEvents :
  public IConnectionPointImpl<T,
     &DIID__IAAAFireLimitEvents, CComDynamicUnkArray>
{
  //Warning this class may be recreated by the wizard.
public:
  HRESULT Fire_Changed(IDispatch * Obj, CY OldValue)
  {
  CComVariant varResult;
  T* pT = static_cast<T*>(this);
  int nConnectionIndex;
  CComVariant* pvars = new CComVariant[2];
  int nConnections = m_vec.GetSize();

  for (nConnectionIndex = 0;
           nConnectionIndex < nConnections;
           nConnectionIndex++)
  {
     pT->Lock();
     CComPtr<IUnknown> sp =
           m_vec.GetAt(nConnectionIndex);
     pT->Unlock();
     IDispatch* pDispatch =
           reinterpret_cast<IDispatch*>(sp.p);
     if (pDispatch != NULL)
   {
     VariantClear(&varResult);
     pvars[1] = Obj;
     pvars[0] = OldValue;
     DISPPARAMS disp = { pvars, NULL, 2, 0 };
     pDispatch->Invoke(0x1,
              IID_NULL, LOCALE_USER_DEFAULT,
               DISPATCH_METHOD, &disp,
              &varResult, NULL, NULL);
     }
  }
  delete[] pvars;
  return varResult.scode;

  }
  //... Fire_SignChanged omitted, similar to Fire_Changed
};
IConnectionPointImpl
탐구

지금까지 IConnectionPoint에 대한 COM_INTERFACE_ENTRY는 포함시키지 않았는데, 왜 그랬을까요?
메인 개체가 IConnectionPoint를 구현하지 않는 대신, IConnectionPointContainer::FindConnectionPoint IUnknown IConnectionPoint를 구현하는 개별적인 개체에 대한 포인터를 반환합니다
.
사실 Dr.GUI IconnectionPointImpl?구성되는 동안 new를 사용하여 이 개체를 생성할 것이라 여겼지만, ATL 설계자들은 그보다 더 똑똑했던 모양입니다. 다중 상속, 템플릿, 그리고 약간의 포인터 조작을 통해, 커넥션 포인트 개체(또는 개체들)와 그 개체가 필요로 하는 데이터(커넥션 컬렉션에 대한 데이터)를 메인 개체 안에서 작성할 수 있게 만들었으니 말입니다. 각각의 커넥션 포인트 개체는 개별적인 COM 식별자(identity)를 갖고 있으므로, 메인 개체의 일부로 구현되더라도 논리적으로는 개별적인 개체라 할 수 있습니다. COM은 구현에 관여하지 않기 때문에(, behavior에만 관여), 규칙 위반이라 볼 수 없으며, 또한 COM 규칙을 어기지만 않는다면, 어떤 방식으로든 개체를 구현할 수 있습니다
.
이와 같은 교묘한 트릭을 본 칼럼에서 자세히 다루기는 어려울 것 같습니다. 따라서, 이에 대한 자세한 정보를 원하시면 ATL Internals (Rector Sells, Addison-Wesley 출판) 390 - 396쪽을 참고하거나 ATL의 소스 코드를 직접 확인하시기 바랍니다
.
중요한 마지막 단계
: IProvideClassInfo2
마지막으로, 클라이언트에게 타입 라이브러리를 어떻게 얻을 수 있으며 기본 소스 인터페이스를 어떻게 액세스할 수 있는지 알려주는 게 당연하겠지요. 이를 위해서는 IprovideClassInfo2를 구현해야 합니다. IP아래의 코드를 메인 클래스의 상속 리스트에 추가하면 ATL IProvideClassInfo2를 구현해 줍니다
.
  public IProvideClassInfo2Impl<&CLSID_AAAFireLimit,
     &DIID__IAAAFireLimitEvents,
        &LIBID_AAAFIRELIMITMODLib,
     LIBRARY_MAJOR, LIBRARY_MINOR>
이제, LIBRARY_MAJOR LIBRARY_MINOR라는 두 개의 매크로를 타입 라이브러리의 버전 번호에 맞게 정의해야 합니다. 두 매크로는 헤더 파일의 시작 부분에서 정의합니다. 여기서는, 다음과 같이 두 개의 매크로를 정의해 보았습니다. 여러분은 새 버전의 개체를 만들 때마다 이 두 매크로 정의를 업데이트하면 됩니다
.
// version number of type library
#define LIBRARY_MAJOR 1
#define LIBRARY_MINOR 0
COM
맵에 엔트리를 추가하는 것도 잊지 마십시오. 여러분이 작성한 개체는 IProvideClassInfo IProvideClassInfo2를 구현할 것이므로, 다음과 같이 두 개의 엔트리를 추가해야 합니다
.
COM_INTERFACE_ENTRY(IProvideClassInfo2)
COM_INTERFACE_ENTRY(IProvideClassInfo)
나머지 부분은 ATL이 알아서 처리합니다
.
스레드 문제

Dr.GUI
가 전에도 말씀 드린 적이 있지만, 새 스레드를 만들고 그 스레드에서 이벤트를 발생시키는 것은 불가능하며, 새 스레드에서 그냥 Fire_Change를 호출해도 문제가 발생합니다.
문제는 이벤트 인터페이스 포인터 저장에 사용되는 스레드(IConnectionPoint::Advise를 호출할 때)와 저장된 포인터를 사용하는 스레드(Fire_Changed를 호출할 때)가 서로 다르다는 데 있습니다. Fire_Changed 안에 있는 코드는 저장된 인터페이스 포인터를 직접 사용합니다. 그 코드는 다음과 같습니다
.
CComPtr<IUnknown> sp = m_vec.GetAt(nConnectionIndex);
이것은 마셜링되지 않은(원시) 인터페이스 포인터를 스레드 간에 전달할 수 없다는 COM 규칙을 위반하는 것이지만, 또한 이러한 부분이 간과되기도 쉽습니다. , 인터페이스 포인터가 ATL Fire_ 메서드 안에 잘 숨겨지기 때문이죠. 하지만, 클라이언트가 여러 스레드로부터의 호출을 처리하는 능력이 있는지(예를 들어, 클라이언트가 다중 스레드 연산을 처리할 수 있는지)를 알아내는 방법은 없기 때문에, 이 규칙을 지키는 것은 매우 중요합니다
.
이 문제를 해결하려면, IConnectionPoint::Advise 에서 인터페이스 포인터를 마셜링하고(원래의 스레드에서 실행할 때), Fire_Changed 에서 인터페이스 포인터 마셜링을 취소해야 합니다(새 스레드에서 실행할 때). 또한, 새 스레드에서 CoInitializeEx를 호출하는 것도 잊지 마십시오. 이 호출은 COM이 사용되는 모든 스레드에 반드시 필요합니다
.
이 내용을 이번 기사에서 코드로 소개하기에는 좀 복잡할 것 같군요. 하지만, 솔루션이 필요한 분들은 여기에 나온 내용(기타 COM에 관한 내용)을 유용하게 활용할 수 있을 것입니다. 코드에 관한 부분은 다음 칼럼에서 소개하도록 하겠습니다
.
ATL
의 예

ATL
이 대부분의 성가신 작업을 처리해 준다고 해서, 위의 작업이 쉽다는 것을 의미하는 것은 아닙니다. 하지만 걱정하지 마십시오. ATL의 훌륭한 마법사와 체크 박스가 상기 내용을 코드로 구현하는 데 많은 도움이 될 테니까요. 여러분이 할 일은 소스 인터페이스를 정의하고 필요한 때에 이벤트를 발생시키는 것 뿐입니다.
우리가 만들 개체는 아주 간단합니다. 여러분은 이 개체의 값을 얻거나 설정할 수도 있고, 이 개체에 새로운 값을 추가할 수도 있습니다. 개체의 값이 변경되거나 값의 부호가 바뀌게 되면 이벤트는 발생됩니다
.
단계1: 모듈 만들기

COM
개체를 만들 때는, 먼저 개체가 들어갈 모듈을 만들어야 합니다(아직 없다면). 이 단계는 그다지 복잡하지 않으며, ATL COM AppWizard에서 여러분의 프로젝트에 적합하게 옵션을 설정하기만 하면 됩니다. Dr.GUI는 개체의 이름을 "AAA"로 시작해서(개체가 알파벳순으로 정렬되도록 함) "Mod"로 끝내겠습니다(모듈이 개체와 구별될 수 있게 함).
이름을 입력했으면 모든 기본 옵션을 그대로 선택하십시오. 이에 관한 자세한 내용은 Dr.GUI ATL 대체에 대한 기사  를 참고하시기 바랍니다

단계 2: 개체 만들기(반드시 커넥션 포인트 지원을 요청해야 함)
일반적인 개체를 만들 때와 마찬가지로, Insert.New ATL Object (Insert 메뉴에 있음)를 사용하여 Simple Object를 선택한 다음, 개체의 이름을 입력합니다. , 전 단계에서 사용한 것과 동일한 이름을 입력해야 합니다(접미사 "Mod"는 빼고). 아직 OK 단추를 클릭하지 마십시오. 이 개체에 대한 애트리뷰트를 설정해야 하므로, Attributes 탭을 클릭하고 Support Connection Points 확인란에 표시하십시오. 그러면 다음과 같이 대화상자가 나타납니다
.

<img src="../images/com_10/image006.gif">

그림 1. Support Connection Points가 선택된
ATL Object Wizard
반드시 Support Connection Points 확인란에 표시를 해야 합니다
. .
참고 어떤 이유에서인지는 모르지만, Dr. GUI Visual Studio을 설치할 때 Insert.New ATL Object 명령을 빠뜨렸군요. 이 문제는 Service Pack 5  를 설치하면 쉽게 해결됩니다
.
ATL Object Wizard
에 의해 생성된 코드를 살펴 보면, 전에 본 적이 없는 코드가 발견될 수도 있습니다
.
·        
소스 인터페이스는 위에서 설명한 대로 IDL 파일에 추가됩니다. (, 메서드 선언은 후속 단계에서 추가됩니다
.)
·        
소스 인터페이스 선언은 위에서 설명한 대로 IDL 파일의 coclass 섹션에 추가됩니다
.
·        
위에서 설명한 대로 IConnectionPointImpl로부터 ATL 클래스가 유도됩니다
.
·        
커넥션 포인트 맵은 이미 추가되어 있습니다. (엔트리는 아직 추가되지 않았음
).
단계 3: 소스(이벤트) 인터페이스에 메서드 추가하기

다음 단계는 일반 Visual Studio 메커니즘(Class View에서 소스 인터페이스 이름을 오른쪽 마우스 버튼으로 클릭한 다음, Add Method를 선택함)을 사용하여 두 개의 메서드를 추가하는 것입니다.
Changed
메서드의 프로토타입은 다음과 같습니다
.
HRESULT Changed(IDispatch *Obj, CURRENCY OldValue);
이 프로토타입을 보면, 개체 안의 값이 변경될 때 원래 값과 함께 개체에 대한 인터페이스 포인터가 메서드에 전달되는 것을 알 수 있습니다. 인터페이스 포인터를 전달하면 클라이언트가 모든 메서드와 속성을 액세스할 수 있게 됩니다
.
여기서는 Dr.GUI의 데이타 유형에 따른 COM 자동화에 대한 기사에서 설명한 대로, COM CURRENCY 타입을 사용했습니다
.
SignChanged
의 프로토타입도 Changed의 경우와 비슷합니다
.
단계 4: IDL 컴파일하기

메서드를 선언한 다음, IDL 파일을 컴파일합니다. 가장 빠른 방법은 File View로 이동하여, IDL 파일을 찾아 오른쪽 마우스 버튼으로 클릭하고 Compile을 선택하는 것입니다(시간이 충분하다면, 전체 프로젝트를 작성해도 상관 없겠지요).
단계 5: 커넥션 포인트 구현하기

IDL
을 컴파일한 후에는, 다시 Class View로 이동하여 개체의 클래스 이름을 오른쪽 마우스 버튼으로 클릭하고(이 예에서는, CAAAFireLimit), Implement Connection Point를 선택합니다. 타입 라이브러리를 적절하게 컴파일했다면, 다음과 같은 대화 상자가 나타날 것입니다.

<img src="../images/com_10/image007.gif">

그림 2. 이벤트 인터페이스가 선택된 Implement Connection Point 박스

커넥션 포인트를 구현할 인터페이스() 확인란에 표시를 합니다. (다시 한번 말하지만, 이 마법사는 기존의 ATL Proxy Generator에서 업데이트된 버전입니다)
그러면, 마법사는 다음과 같이 코드를 변경합니다
.
·        
"proxy" 클래스를 프로젝트에 추가합니다(이 예에서는 CProxy_IAAAFireLimitEvents). 이 클래스는 커넥션 포인트의 구현으로서 소스는 개별 파일에 각각 들어 있습니다. 이 클래스는 IConnectionPointImpl로부터 유도되며 이벤트 인터페이스의 각 메서드에 대해 Fire_ 메서드를 추가합니다. 앞에서 이 클래스 목록을 이미 보아서 알겠지만 마법사가 이 메서드를 작성해 줄 수 있다는 것은 정말이지 놀라운 일이 아닐 수 없습니다
.
·        
새 클래스를 유도 리스트에 추가합니다
. .
·        
이벤트 인터페이스에 대한 엔트리를 개체의 커넥션 포인트 맵에 추가합니다
.
우리는 이 클래스로부터 상속을 받으므로, 이 클래스의 Fire_ 메서드를 쉽게 호출할 수 있습니다. 만약 이벤트 인터페이스를 조금이라도 변경하면, 전체 단계(IDL을 다시 컴파일하고 커넥션 포인트를 구현)를 다시 거쳐야 하고, 프록시 클래스도 처음부터 재생성됩니다. 따라서, 이벤트 인터페이스의 코드는 변경하지 않는 것이 좋겠지요
?
단계 6: IClassInfo2의 구현 추가하기

IClassInfo
IClassInfo2 구현이 모두 제공되는 것이 바람직하다는 점을 앞에서도 언급한 바 있습니다. 해당 섹션에서 설명한 코드를 추가하면 됩니다
.
단계 7: 개체의 속성 및 메서드 추가 그리고 이벤트 발생

마지막으로, 일반 속성 및 메서드를 추가하고 이벤트를 발생시킵니다.
앞에서 말했듯이, 이 개체는 현재 값을 기억하고 있다가 값이 변경되거나 값의 부호가 바뀔 때 이벤트를 발생시키는 개체입니다. 따라서, 우리는 값에 대한 속성을 추가할 것입니다(디스패치 ID 0으로 설정하여 속성을 기본값으로 설정). 또한, 개체에 숫자를 추가할 수 있도록 Add 메서드를 추가하겠습니다. 이 개체의 인터페이스에 대한 IDL은 다음과 같습니다
.
interface IAAAFireLimit : IDispatch
{
  [propget, id(0), helpstring("property Value")]
     HRESULT Value([out, retval] CURRENCY *pVal);
  [propput, id(0), helpstring("property Value")]
     ?HRESULT Value([in] CURRENCY newVal);
  [id(1), helpstring("method Add")]
     HRESULT Add(CURRENCY cyAddend);
};
메서드 구현은 여러분이 예상하는 것과 거의 유사할 것입니다. 한 가지 새로운 점이 있다면, 개체의 값을 변경할 가능성이 있는 모든 메서드는 필요한 이벤트를 발생시키는?핼퍼 메서드 CheckAndFire를 호출한다는 것입니다
.
STDMETHODIMP CAAAFireLimit::get_Value(CURRENCY *pVal)
{
  *pVal = m_cyValue; //
값을 변경할 수 없음
.
}
  return S_OK;

STDMETHODIMP CAAAFireLimit::put_Value(CURRENCY newVal)
{
  CURRENCY oldVal = m_cyValue;
  m_cyValue = newVal;
  CheckAndFire(oldVal);
  return S_OK;
}

STDMETHODIMP CAAAFireLimit::Add(CURRENCY cyAddend)
{
  CURRENCY cyOld = m_cyValue;
  VarCyAdd(m_cyValue, cyAddend, &m_cyValue);
  CheckAndFire(cyOld);
  return S_OK;
}
데이터 멤버인 m_cyValue CURRENCY 타입의 클래스 멤버입니다. 여기서는 데이터 유형에 대한 칼럼  에서 설명된 대로, COM VarCyAdd 함수를 사용하여 값을 추가했습니다
.
또한, 이전의 값을 기억하고 있다가 이벤트를 발생시킬 필요가 있는지 검사합니다. 만약 값을 기존의 값으로 재설정하면 이벤트가 발생되지 않습니다. , Changed 이벤트는 실제로 값이 변경될 때에만 발생됩니다
.
CheckAndFire
메서드는 약간 복잡하군요. Changed 이벤트의 경우는 비교적 간단하지만, SignChanged 이벤트의 경우에는 약간 까다로운 편입니다. SignChanged 이벤트는 값이 양수에서 음수로, 또는 그 반대로 바뀔 때에만 발생되어야 하기 때문입니다(값이 0이 되는 것이 아무 의미가 없음). 또한, 값이 양수 0에서 음수 0이 되는 경우나 그 반대의 경우에도 SignChanged 이벤트가 발생되어야 합니다. CheckAndFire 메서드는 이러한 경우를 적절하게 처리하기 위해 인스턴스 변수(hrOldSign, constructor에서 VARCMP_EQ로 초기화됨)를 사용하여 부호를 추적합니다
.
COM
VarCyCmp 함수는 값을 비교하는 데 사용됩니다. 또한, 다음과 같이 0으로 초기화된 전역 변수 cyZero도 만들었습니다
.
CURRENCY cyZero = { 0i64 };
CheckAndFire
메서드는 다음과 같습니다
.
void CAAAFireLimit::CheckAndFire(CURRENCY cyOld)
{
  // Fire event if value changed
  HRESULT hrCmpRes = VarCyCmp(m_cyValue, cyOld);
  if (hrCmpRes != VARCMP_EQ)
  Fire_Changed(this, cyOld);
  // Fire event if sign changed
  HRESULT hrCmpZero = VarCyCmp(m_cyValue, cyZero);
  if (hrCmpZero != VARCMP_EQ) {// 0
이 아님

  if (hrCmpZero != hrOldSign && hrOldSign != VARCMP_EQ) {
     Fire_SignChanged(this, cyOld);
  }
  hrOldSign = hrCmpZero;
  }
}
이벤트를 발생시켜야할 경우, this 포인터(클라이언트가 메서드를 호출할 수 있도록)와 이전 값(클라이언트 정보용)을 함께 전달합니다.
마지막으로, m_cyValue cyZero로 초기화합니다
.
이제, 개체를 작성하고 디버그한 후, 그것을 이벤트에 응답 가능한 클라이언트에서 실행하면 됩니다. 이벤트를 받는 클라이언트에 관한 설명은 다음 기회로 미루겠습니다
.
기존의 개체에 이벤트를 추가하려면
?
기존의 개체에 이벤트를 추가하려면, 단계 2에서 Support Connection Points 확인란을 표시한 후 추가되었던 모든 코드를 추가해야 합니다. 이때도 마법사를 사용하여

///

 

[출처] COM 경험담..|작성자 띵돌