나의 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% 그대로 받아 들이면 큰일 난다는 것이다. 언제 또 오늘 처럼 말을 뒤집을지 모른다. 모든 것은 이해하기 위한 과정일 뿐이다.
잠 온다.
email : icoddy@hotmail.com msn id : icoddy@hotmail.com
박성규