. 오브젝트화

오브젝트화 한다는 것은 컨트롤을 C++ 클래스의 인스턴스로 만드는 것이며 C++ 컴파일러의 객체지향 기술을 십분 활용하는 우수한 방법이다. 오브젝트란 일단 한 번 만들어지고 나면 재사용성이 탁월하여 어느 곳에서나 쉽게 가져가 사용할 수 있다. 뿐만 아니라 상속이 가능하므로 기능의 추가나 변경이 쉽고 정보 은폐로 인해 부주의한 사용으로 인한 위험을 제거하기도 하며 유지, 보수에도 유리한 그야말로 장점 투성이다.

컨트롤을 오브젝트로 만들어 두면 CShowMsg msg; 선언문 하나로 객체를 생성할 수 있다. 마치 MFC에서 CString str; 선언문으로 문자열 객체를 만들 수 있듯이 말이다. 필요한 기능은 객체의 멤버함수를 호출하면 되고 멤버변수를 읽으면 원하는 정보를 얻을 수 있다. 만들기는 조금 까다롭지만 일단 제대로 만들어 놓으면 사용자들은 훨씬 더 편하게 컨트롤을 사용할 수 있으며 코드를 유지하기도 쉬워진다.

컨트롤을 오브젝트화하는데 있어서의 어려움은 근본적으로 윈도우즈가 객체지향 운영체제가 아니라는 점에서 기인한다. 윈도우는 메시지 처리를 위한 윈도우 프로시저를 가지는데 이 함수가 콜백 함수라는 점이 OOP와는 잘 맞지 않는 것이다. 콜백 함수는 클래스의 멤버함수가 될 수 없다. 왜냐하면 멤버함수는 숨겨진 인수 this를 가지기 때문에 콜백 함수와는 스택 프레임이 호환되지 않기 때문이다.

콜백 함수를 멤버함수로 만들려면 정적 멤버함수로 선언하면 된다. 이렇게 되면 this 인수가 없는 함수를 멤버로 가질 수 있는데 그러나 정적 멤버함수는 객체를 구분할 수 없다는 것이 또 문제가 된다. 메시지 처리 함수는 어떤 인스턴스에 대한 메시지인지 구분하기 위해 this가 정말로 필요하기 때문이다. 정확하게 표현하자면 윈도우 핸들이 필요한데 윈도우 핸들은 this를 알아야만 얻을 수 있다.

그렇다고 해서 해결책이 없을 리는 만무하고 멋진, 그러나 조금 복잡한 해결책이 있다. 일단 메시지 처리 함수는 멤버함수가 아닌 일반함수로 작성하여 콜백으로 동작하도록 한다. 메시지 처리 함수는 윈도우 핸들로 인스턴스를 구분하는데 각 객체는 자신의 윈도우 핸들을 멤버변수로 가지고 있다. 이 핸들로부터 객체의 포인터를 구한 후 객체가 직접 메시지를 처리하도록 하면 된다. 문제의 핵심은 핸들로부터 객체 포인터를 찾을 수 있는 방법을 제공하는 것이다.

객체와 윈도우 핸들을 연관짓기 위해 객체가 윈도우를 생성할 때 CreateWindow 함수의 마지막 인수인 lpParam으로 this 포인터를 넘겨 준다. 이때 메시지 처리 함수로 WM_NCCREATE 메시지가 가장 먼저 전달되며 이 메시지에서 lpParam으로 전달되는 CREATESTRUCT 구조체에서 this 포인터를 구할 수 있다. 메시지 프로시저는 메시지를 직접 처리하지 않고 윈도우 핸들과 객체 포인터의 맵만 작성해놓는다. 맵은 두 값의 대응관계를 기억하는 일종의 대응표이다.

이후부터 메시지가 전달되면 메시지 프로시저가 직접 처리하지 않고 맵으로부터 객체 포인터를 구한 후 이 객체에게 처리를 넘긴다. 대응표에 객체 포인터와 윈도우 핸들의 쌍을 정의해놓았기 때문에 메시지 프로시저로 전달된 핸들로부터 객체를 찾을 수 있다. 객체는 자신에게 전달되는 모든 메시지를 처리할 수 있는 OnMessage 가상함수를 정의하며 이 함수가 객체의 메시지 처리 함수처럼 동작하게 된다. OnMessage는 객체의 멤버함수이기 때문에 this 포인터가 있고 따라서 각 인스턴스를 구분할 수 있다.

이론적인 설명만으로는 이 방법을 이해하기가 좀 어려우므로 직접 코드를 보면서 구현 방법을 분석해보도록 하자. 이 실습은 직접 하지 말고 CD-ROM에 있는 예제를 읽어오도록 하자. 구성 파일은 윈도우 컨트롤의 경우와 동일하다. ShowMsg.h 헤더 파일과 ShowMsg.cpp 구현 파일로 구성되어 있고 메인 윈도우의 코드는 ShowMsgTest.cpp에 작성되어 있다. 각 파일의 코드는 관련 부분에서 분석해보도록 하자.