MFC

클래스 멤버함수를 윈도우 프로시저로 사용하기 [1/3]

디버그정 2008. 9. 1. 20:17

원문 : Window Procedures as Class Member Functions 

원문위치: http://www.rpi.edu/~pudeyo/articles/wndproc/index.html  

원저자 : Oleg Pudeyev 

원문 마지막 수정일: 2003 1 29 

번역자 : sobahoko 

번역시작일 : 2006.3.11 

번역 마지막 수정일 : 2006.3.18  

클래스 멤버함수를 윈도우 프로시저로 사용하기

 

Oleg Pudeyev  

 

문서에서, (Oleg Pudeyev) 클래스 멤버함수를 윈도우 프로시저로 사용하는 몇가지 방법에 대해 살펴볼 것이다. 나는 특정 메시지를 특정 멤버함수에 멥핑하는 방법에 대해서는 언급하지 않을 것이다. 모든 예제 코드에서, 하나의 윈도우 프로시저가 모든 메시지를 처리할것이며, 기본적인 처리를 위해서는 DefWindowProc 함수를 호출할 것이다. 여기서 살펴볼 방법들은 쉬운것에서부터 시작하여 복잡한 순으로 나열된다. 문서의 목차는 다음과 같다.

기본 코드 

정직한 방법 

전역 변수 

윈도우 고유 데이터 

CBT Hooks 

전역 헨들  

MFC 접근법 

ATL 접근법 

 

 

기본 코드 

나는 글을 진행하기 위해 간단한 Win32 프로그램을 출발점으로 사용하려고 한다. 프로그램은 윈도우를 열고 Hello, World!”를 WM_PAINT 메시지에 반응하여 출력하는 기능을 가지고 있다. 소스코드는 여기( http://www.rpi.edu/~pudeyo/articles/wndproc/basecode.cpp )에서 있다

 

정직한 방법 

비록 제대로 동작하지 않지만, 윈도우 프로시저를 클래스 내부에 위치시키는 방법은 정직하게 다음처럼 하는 것이다

 

class Window 

       LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp); 

       // ... 

}; 

 

LRESULT CALLBACK Window::WndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) 

       // Access member variables here 

 

// ... 

WNDCLASS wc = { 

       // ... 

       Window::WndProc, 

       // ... 

}; 

// ... 

 

코드의 전체 소스코드는 여기( http://www.rpi.edu/~pudeyo/articles/wndproc/obvious.cpp )에서 있다. MSVC7에서 컴파일러는 컴파일에 실패하고 다음 에러 메시지를 낸다

 

obvious.cpp(81) : error C2440: 'initializing' : cannot convert from 

                               'LRESULT (__stdcall Window::* )(HWND,UINT,WPARAM,LPARAM)' to 

                               'WNDPROC' 

     None of the functions with this name in scope match the target type 

 

 

MSVC6 다음 에러 메시지를 낸다

 

obvious.cpp(82) : error C2440: 'initializing' : cannot convert from 

                       'long (__stdcall Window::*)(struct HWND__ *,unsigned int,unsigned int,long)' to 

                       'long (__stdcall *)(struct HWND__ *,unsigned int,unsigned int,long)' 

     There is no context in which this conversion is possible 

 

에러는 멤버함수가 전역함수와 다르기 때문에 발생한다. 멤버함수는 숨겨진 this 파라메터를 가지며, 멤버 함수가 호출되었을 그것을 통해서 클래스 인스턴스를 참조한다. 클래스 멤버가 아닌 함수들은 그런 파라메터가 없으므로, 멤버함수와 다르다

 

정직한 방법을 간단히 수정 

에러 메시지는 다음 코드와 같이 멤버함수 WndProc static 으로 만들면 사라진다

 

class Window 

       // ... 

       static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp); 

       // ... 

}; 

 

이것으로 Window::WndProc 함수로 WNDCLASS 구조체를 초기화 하는데 사용할 있다. 그러나, WndProc 함수가 static 이므로, 함수는 HelloString 변수와 같은 인스턴스 내부의 변수들에 접근할 없다. 그러므로 이것은 완전한 해결책이 아니다

 

 

전역 변수 

이것을 해결하는 간단한 방법은 메시지를 처리하는 클래스의 전역 인스턴스를 만들고, 메시지를 전역 인스턴스의 WndProc 함수로 전달해 주는 보조 함수를 만들어 사용하는 것이다. 코드는 아래와 같다

 

Window w; 

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) 

       return w.WndProc(hwnd, msg, wp, lp); 

 

방법은 명확하며 신뢰할 만한 방법이다. 그리고 윈도우 클래스의 인스턴스가 오직 하나일 적합한 방법이다. 특별히 응용프로그램에 오직하나의 윈도우가 존재할 , 방법은 좋은 선택이 것이다. 그러나 방법은 클래스의 인스턴스가 여러개이며 메시지가 각각의 인스턴스에서 다르게 처리되어야 하는 상황에 사용할 없다. 이것에 대한 소스코드는 여기 ( http://www.rpi.edu/~pudeyo/articles/wndproc/global.cpp ) 에서 있다

 

 

윈도우 고유 데이터 

Windows 의해서 생성된는 모든 윈도우의 인스턴스에는 메모리블럭을 할당할 있다. 메모리블럭과 블럭에서 참조되는 변수들을 통칭하여 윈도우 고유 데이터(Per-Window Data) 부른다. WNDCLASS 구조체의 cbWndExtra 멤버에 적절한 값을 주어, 데이터 영역의 크기를 지정할 있으며, 읽을때는 GetWindowLong 이나 GetWindowLongPtr 사용하고 쓸때는 SetWindowLong 이나 SetWindowLongPtr 사용한다. Ptr 붙은 버전은 64-bit Windows 호환되기 때문에 사용이 권장되며, 상위 호환성을 염두에 둔다면 Ptr 붙은 버전을 사용해야 한다

 

우리는 윈도우 고유 데이터 영역에 객체의 포인터를 저장하여 특정 Window 객체와 윈도우를 연결시키려고 한다. , 어떤 시점에는 연결을 위해서 다음 함수를 호출하고 

 

SetWindowLong(hwnd, GWL_USERDATA, this); 

 

후에는 Window 포인터를 가져오기 위해서 다음함수를 호출한다는 얘기다

 

Window *w = (Window *) GetWindowLong(hwnd, GWL_USERDATA); 

 

그러므로 전역 WndProc 함수는 다음과 같을 것이다

 

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) 

       Window *w = (Window *) GetWindowLong(hwnd, GWL_USERDATA); 

       if (w) 

               return w->WndProc(hwnd, msg, wp, lp); 

       else 

               return DefWindowProc(hwnd, msg, wp, lp); 

 

if 문은 연결이 완료되기 전에 WndProc 함수가 호출되었을 경우를 처리하기위해 필요하다. 연결은 언제 완료되는가? 언제 SetWindowLong 호출하는가? 아마도 다음 코드와 같이 CreateWindow 호출한 직후에 SetWindowLong 호출하고 싶은 생각이 들것이다.  

 

Window w; 

HWND hwnd = CreateWindow(TEXT("BaseWnd"), TEXT("Hello, World!"), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, 0, 0, hinst, 0); 

if (!hwnd) 

       return -1; 

SetWindowLong(hwnd, GWL_USERDATA, &w); 

 

불행히도, 방법은 WM_CREATE 메시지를 포함한 많은 메시지를 처리하지못한다. 메시지들은 CreateWindow 함수가 리턴되기 전에 발생하여, 결과로 WndProc 함수가 호출되기 때문이다

그러므로 좀더 이른 시점에 연결을 완료하는 것이 필요하다

 

WM_NCCREATE 희망을 준다. MSDN 에서는, 이것은 WM_CREATE 전에 보내지며, 우리가 this 포인터를 저장하는데 사용하려고 하는 CREATESTRUCT 포인터를 파라메터로 가진다고 말하고 있다. CreateWindow 함수는 lpParam 파라메터가 있다. 이것은 CREATESTRUCT lpCreateParam 멤버와 같은 파라메터이다. Window 객체 w 포인터를 lpParam 전달함으로써, 우리는 나중에 lpCreateParam 통해서 그것을 다시 가져올 있다. 포인터를 가지고 있으므로, 우리는 WM_NCCREATE 헨들러에서 SetWindowLong 함수를 호출할 있다. WM_CREATE 메시지가 왔을때는, 객체의 포인터를 GetWindowLong 함수를 통해 가져오고, 이것을 통해 멤버함수 호출이 가능하다. 이것에 대한 소스코드는 여기( http://www.rpi.edu/~pudeyo/articles/wndproc/windowdata.cpp )에서 있다

 

접근법에는 한가지 단점이 있다. 그것은 WM_NCCREATE 메시지가 WndProc 함수가 받는 번째 메시지가 아니라는 사실이다. 만약 GetWindowLong에서 반환된 값이 NULL 아님을 확인하지 않고 멤버 변수를 사용하면, this 포인터가 NULL , 프로그램이 다운될 것이다. 정확이 얘기하면 윈도우 프로시저는 WM_NCCREATE 전에 WM_GETMINMAXINFO 메시지를 먼저 받는다

 

 

윈도우 고유 데이터 해결책의 최적화 

static WndProc 함수가 항상 if 문장을 평가해야 한다는 것은 쉽게 있다. 그러나 this 포인터가 설정되고 나면, 어떤 코드가 실행되어야 하는지 우리는 정확히 한다. 두개의 WndProc 함수를 사용하면 코드는 미세하게 빨라진다. 예제 코드는 여기( http://www.rpi.edu/~pudeyo/articles/wndproc/windowdata2.cpp )에서 있다

 

 

전역 임시 this 포인터 저장소 

방법은 전역 WndProc 함수가 받는 모든 메시지에 대해서 멤버 WndProc 함수가 호출되도록 하는 방법이다. 아이디어는 Magmai Kai Holmlor 것이다. ( GameDev.net thread 참조 : http://www.gamedev.net/community/forums/topic.asp?topic_id=59171

방법에서는 this 포인터를 임시 전역 변수에 저장한 전역 WndProc 함수가 처음으로 실행되었을 SetWindowLong 호출하는 방법이다. 방법이 위에서 언급한 전역변수 방법보다 나은 이유는 전역변수를 짧은 시간 동안만 사용하므로 많은 윈도우를 생성할수 있으며, 모든 윈도우들이 같은 전역변수를 사용하기 때문이다. 이것에 대한 소스코드는 여기( http://www.rpi.edu/~pudeyo/articles/wndproc/gwd.cpp ) 참조할것

 

방법은 불행히도 쓰레드안정성을 가지고 있지 않다. 만약 두개의 쓰레드가 동시에 윈도우를 생성할 데이터가 훼손될 있으며 프로그램이 다운될 있다. 이것을 피하는 방법은 전역변수를 보호할 critical section 사용하는 것이다. 이것에 대한 소스코드는 여기 ( http://www.rpi.edu/~pudeyo/articles/wndproc/gwd2.cpp ) 참조할

여전히 코드는 만족스럽지 않다. 다음 시나리오에서 두개의 쓰레드는 데드락상태가 있다

 

쓰레드 A 윈도우를 생성한다

윈도우의 WM_CREATE 헨들러에서, 쓰레드 A 쓰레드 B 생성하고 쓰레드 B 의해서 이벤트가 설정되기를 기다린다

쓰레드 B 다른 윈도우를 생성하고, 이벤트를 설정한다. 그리고 뭔가 다른 일을 한다

 

쓰레드 B 윈도우를 생성하려고 , 이미 쓰레드 A 잡고 있는 critical section 진입하려고 한다. 이것은 데드락을 발생시킨다. 문제는 두가지 방법으로 해결할 있다. 두가지 방법 모두 궁극적으로 쓰레드가 전역 critical section 락을 잡고 있는 시간을 줄이는 방법을 사용한다

 

1. 메시지 헨들러에서 번째 메시지가 들어오면 critical section 떠난다. 방법은 기존 코드를 그리 많이 수정하지 않아도 된다. 그러나 윈도우 객체의 멤버에 플래그를 추가하는 것이다. 이것에 대한 소스코드는 여기( http://www.rpi.edu/~pudeyo/articles/wndproc/gwd2m.cpp ) 참조할 . 플래그는 윈도우 생성이 성공했든, 실패했든지간에 critical section 오직 한번만 해제되었다는 것을 보증하기 위한 것이다. 방법은 복잡하며 유지관리가 편하지 않다.  

2. 여기 ( http://www.rpi.edu/~pudeyo/articles/wndproc/gwd2c.cpp ) 구현된 방법은 보조 컨테이너를 사용하여 쓰레드 아이디와 윈도우 객체 포인터가 저장된 멥에 대한 접근을 동기화시킨다. 방법은 동기화 코드를 정의된 범위에 집어 넣어 critical section lock 걸리는 시간을 최소화한다. , 멥을 조회하고 멥에 삽입하는 짧은 시간 동안만 락을 건다. 방법에서 하나의 명확한 부작용은 thread-local storage 전혀 사용하지 않는다는 것이다

 

모든 활성 쓰레드에 대해 전역 포인터를 중복가능하게 하는 대안으로 다음코드와 같이 __declspec(thread) 수정자를 사용할 있다.  

 

__declspec(thread) Window *g_pWindow; 

 

불행히도 dll 저장된 데이터에 대해서는 __declspec(thread) 수정자를 사용할 없다. 방법은 오직 실행파일(exe) 존재하는 코드에 대해서만 적용가능하다

 

 

CBT Hooks 

CBT hooks Window 객체의 포인터를 윈도우 고유 데이터에 저장할 있는 또다른 방법을 제시한다. CBT hook 윈도우 프로시저에 보내지는 모든 메시지보다 앞서 호출된다. 그러므로 hook에서 윈도우 객체 포인터를 윈도우 헨들에 연결시키는데 관한 모든 작업을 수행하는 것이 가능하다. 윈도우 프로시저는 포인터를 받기만 하면 된다

 

윈도우가 생성되기 , 다음과 같이 hook 설정한다

 

HHOOK hHook = SetWindowsHookEx(WH_CBT, CBTProc, 0, GetCurrentThreadId()); 

 

작업은 윈도우가 생성될때마다 수행되는 것이 아니라, 쓰레드 실행중 한번만 수행되어야 한다. 그러나 코드를 단순하게 하기위해, 윈도우가 생성될때마다 수행하는 방식으로 예제를 작성하였다. CreateWindow 함수가 호출되면, CBTProc 호출된다. CBTProc 내부에서, this 포인터를 받아 전역변수, thread-local storage 또는 다른 기법을 사용한후, SetWindowLog 호출한다. 이방법을 보여주는 소스코드는 여기( http://www.rpi.edu/~pudeyo/articles/wndproc/hook.cpp ) 참조할것

 

 

전역 헨들  

다른 방법은 모든 윈도우에 대해서 헨들-포인터 쌍을 저장하는 하나의 전역 멥을 사용하는 것이다. HWND 그에 대응하는 Window 포인터로 구성된 것이 Window 생성될때마다 멥에 추가되고, Window 객체가 소멸될 맵에서 삭제되는 방식이다. 그러면 클래스는 HWND 해당하는 Window 포인터를 돌려주는 방법을 제공할 수있다. 그런 예를 소스코드( http://www.rpi.edu/~pudeyo/articles/wndproc/gwd2c.cpp ) CPtrMap에서 있다. 구현은 매우 직관적이지만 모든 메시지가 처리될때마다 포인터를 조회하고, 필요한 부가작업을 수행하는 것은 잠재적으로 효율이 떨어진다. 응용프로그램에서 많은 수의 윈도우를 열고 있을 vector list 사용하면 선형시간( O(N) ) 소요되고, map 사용하면 로그시간( O(log N) ) 소요된다. hash map 상수시간 ( O(1) ) 조회를 수행할 있지만, 다른 단점를 가지고 있다. MFC에서 하는것처럼 쓰레드 마다 분리된 멥을 사용하면 조회시간을 최적화하는 것이 가능하다. 사용자 레벨에서 헨들을 포인터로 변환( MFC FromHandle FromHandlePermanent 함수 )하는 용도의 전역 헨들 방법은 윈도우 관리 코드에 의해 내부적으로 헨들을 포인터에 맵핑하는 다른 해결책과 동시에 사용하는 것이 가능하다. 또한 사용자에게 그런 변환기능을 제공하는 것이 반드시 필요한 것은 아니다. 사실 ATL 에는 그런 변환기능이 없지만 아무 문제가 없다. 마지막으로 방법 하나만으로는 실제로 동작하지 않으며 다른 방법들도 마찬가지다. 누군가 헨들과 포인터를 연결시켜야하고, 거기에는 헨들과 거기에 대응하는 포인터가 필요하다. 그러므로, 임시 윈도우 프로시저, hook 또는 다른 방법이 앞서 언급된 연결을 확립하는데 반드시 필요하다. 간단한 예를 들어, CreateWindow 함수를 호출한 다음 맵을 생성할 수도 있다. CreateWindow 실행되는 동안 발생되는 메시지를 처리할 필요가 없다면, 방법도 사용할 있는 해결책이 될것이다

 

MFC 접근법 

 

MFC 윈도우 헨들을 객체에 연결시키는 방법을 알아보기위해, 나는 다음 MFC 코드를 사용했다

(http://www.rpi.edu/~pudeyo/articles/wndproc/mfc.cpp

UI 그리 멋진 것은 아니지만, 프로그램은 나의 목표를 달성하기에 충분하다. MFC dll 내부를 탐험하기위해 디버거에서 실행시킨다.  

 

번째 눈에 들어오는 것은 wincore.cpp 파일에 있는 AfxHookWindowCreate 함수이다. 함수는 윈도우가 CWnd::CreateEx 함수에의해서 생성될 함수내부에서 호출되어, 윈도우 객체를 MFC hook 한다. MFC CBT hook 사용하여 현재 쓰레드의 윈도우 생성을 hook 하는 것을 수있다. CBT hook 위에서 언급했기 때문에 낯설지 않을 것이다. MFC hook handle 저장하기 위해 thread state block 사용한다. thread state block thread 실행되는동안 hook 관리하기 때문이다. MFC 어떻게 데이터를 전달하는지에 주목하기 바란다. 모든 thread per-thread state structure 가지고 있다. ( _AFX_THREAD_STATE, afxstat.h ) 여기에 모든 thread-specific MFC data 저장하는데, 현재 생성중인 윈도우에 대한 정보도 저장된다. thread state structure 포인터를 저장하는 동적 배열에 저장된다. 배열에 대한 포인터는 TlsXXX 계열의 함수를 통해 저장되고 참조된다. AFX thread-local storage 대한 구현상세는 afxtls_.h, afxtls.cpp에서 볼수 있다. hook data exchage 얘기로 다시돌아와서, CreateEx 함수는 CWnd 객체의 포인터를 thread state 저장하고, _AfxCbtFilterHook 함수는 그것을 thread state 로부터 가져온다.  

hook 이제 thread state block으로부터 가져온 CWnd 포인터와 그것에 대응하는 HWND 가지게 된다. CWnd::Attach 함수를 호출하여 CWnd HWND 연결하고, HWND CWnd 쌍을 per-thread handle map 저장한다. ( per-thread 라는 단어에 주목하기 바란다. 이것이 multi-thread 환경에서 CWnd 그밖의 window resource wrapper 객체를 공유하지 못하는 이유이다. 특정 thread CWnd 다른 thread HWND-CWnd map 존재하지 않는다. 만약 CWnd 객체를 생성한 thread 아닌 다른 thread에서 CWnd 포인터를 통해 HWND lookup 한다면, 코드는 실패할 것이다. 그러나 HWND 다른 헨들은 thread 간에 전달 할수있으며, CWnd::Attach 함수를 호출하여 CWnd HWND 연결시킬 있다. 그러나 thread 다르므로, CWnd 객체는 다른 것이 될것이다. ) 그런다음, MFC 윈도우를 subclass 하고 thread 고유한 현재 생성중인 윈도우를 가리키는 포인터를 null 설정한다. CWnd 이제 메시지를 처리할 있는 상태가 된다

 

윈도우가 메시지를 받으면, 메시지는 AfxWndProcBase 함수를 거쳐, AfxWndProc 함수로 보내진다. AfxWndProc 함수는 window handle map 으로부터, 전달받은 HWND 조회하여 CWnd 얻기위해 CWnd::FromHandlePermanent 함수를 사용한다. map thread-specific 하다. 그러므로 윈도우를 생성한 thread 만이 윈도우에 보내진 메시지를 처리할 있다. 그후 HWND 헨들을 받는 AfxWndProc 함수는 AfxCallWndProc 함수를 호출하는데, 이함수는 HWND 헨들대신 CWnd 포인터를 받는다는 점만 다르다. 그후 AfxCallWndProc 함수는 CWnd::WindowProc 함수에게 메시지를 보낸다. 그것을 받은 CWnd::WindowProc 함수는 CWnd::OnWndMsg 함수를 호출한다. CWnd::OnWndMsg 함수는 지정된 윈도우의 메시지맵에서 헨들러를 찾고, 메시지 파라메터를 decode 하고, 헨들러를 호출하여, 사용자가 작성한 OnPaint 함수에 이르게 된다

 

 

ATL 접근법 

 

나는 ATL 메시지 처리 구조를 조사하기위해 다음코드를 사용했다. ( http://www.rpi.edu/~pudeyo/articles/wndproc/atl.cpp ) 디버거에서 실행하고 CWindowImpl::Create ( atlwin.h ) 함수안으로 들어간다. 잠시후 AtlWinModuleAddCreateWndData (atlbase.h) 함수에 이르게 되는데, 함수는 _AtlCreateWndData 구조체 포인터를 리스트에 저장한다. 구조체 리스트에는 윈도우 객체 포인터들이 저장되는데, WM_NCCREATE 메시지가 도달하기 전에 ATL window procedure 들이 접근하여 사용할 있다. 모든 구조체는 thread identifier 거기에 속한 윈도우 객체 포인터를 가지고 있다. MFC 에서는 실제로 윈도우를 생성중이건 아니건간에, 모든 thread 현재 생성중인 윈도우에 대한 참조를 가지고 있지만, ATL 전체 application 대하여 하나의 리스트가 그것을 관리한다. 이런 방식은 하나의 UI thread 많은 worker thread 존재할 약간의 메모리를 절약 있다

 

이제 static 멤버함수 CWindowImplBaseT::StartWndProc (atlwin.h) 이른다. 이것은 현재 윈도우에 보내진 번째 메시지를 처리하는 메시지 헨들러 이다. 여기서 CWindow HWND 쌍을 연결하는 작업을 하여, 현재 메시지와 앞으로 메시지들이 멤버 윈도우 프로시저로 route 되도록 한다. 지체 없이 create-window-data structure 리스트로부터 AtlWinModuleExtractCreateWndData (atlbase.h) 함수를 사용하여 this 포인터를 가져온다. 함수는 current thread identifier 리스트를 조회한다. thread identifier 일치하면 거기에 대응하는 window object pointer 리턴된다. 다른 ATL 코드나 사용자 코드가 실행되기 전에 가져오기 때문에 특정 thread 에는 오직 하나의 window object pointer 연결된다. 이로써 CreateWindow 함수는 번째 받은 메시지로부터 호출되게 된다. ATL 여러개의 thread 동시에 리스트에 접근하는 것을 방지하기 위해 critical section 사용한다. window 생성 과정전체 동안이 아니라, create window data 추가하고 가져오는 동안에만 lock 하기 때문에 다른 thread 들이 필요없이 기다리지 않는다

 

이제 CWindowImplBaseT::StartWindowProc 함수는 this 포인터를 가지게 되었다. CWindowImplBaseT::GetWindowProc 함수를 사용하여 실제 윈도우 프로시저를 가져온다. 그다음 thunk 생성하는데, 이것의 목적은 HWND 헨들을 CWindow 포인터로 변환하는 것이다. 그것은 다음과 같이 이루어진다

 

StartWindowProc 함수는 window object instance data 일부인 특정 메모리를 특정 코드로 초기화한다. 코드는 임시 윈도우 프로시저처럼 동작하는 코드로, 들어오는 HWND CWindow 포인터로 멥핑한다. 그런 다음 윈도우 프로시저는 ( SetWindowLong 함수를 통해서 ) 메모리 블록의 시작지점을 가리키도록 설정된다.  

 

특정 메모리는 다음과 같이 평범한 프로토타입의 윈도우 프로시저를 저장하는 어떤 것이다

LRESULT CALLBACK WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam); 

 

윈도우 메시지가 프로시저에 도달했을 , 코드는 hwnd parameter CWindow object 포인터로 변환한다. CWindow object 주소는 같은 메모리블럭에 저장된다

그다음 메시지 프로시저는 실제 static member procedure jump 한다. 그것은 WindowProc 함수와 동일한 프로토타입을가지고 있지만 hwnd parameter 실제 윈도우 헨들이 아니라, CWindow pointer 이다. 그함수는 포인터를 얻기위해 cast 수행한다

CWindowImplBaseT< TBase, TWinTraits >* pThis = (CWindowImplBaseT< TBase, TWinTraits >*)hWnd; 

 

다음은 atlbase.h 있는 _stdcallthunk thunk code 이다

 

mov dword ptr[esp+4], pThis 

jmp WndProc 

 

pThis Init 파라메터로 전달된다. 이것은 현재윈도우의 CWindow 객체에 대한 포인터이다. WndProc 또한 파라메터로 전달된다. 이것은 실제 WndProc 주소이며, 대부분의 경우 이것은 CWindowImplBaseT::WindowProc (atlwin.h) 된다. Init 코드를 실행시에 주어진 두가지 정보를 가지고 build 한다. 코드는 다음과 같은 이유로 CWindow 객체의 일부인 메모리블럭에 저장된다

 

1. Code segment 읽기전용이며, 메모리에 있는 코드를 수정하는 것은 바이러스처럼 보인다

2. 윈도우가 다르면 윈도우 프로시저도 다르다. 그리고 윈도우 프로시저가 가진 CWindow 포인터는 모두 다르다. 그러므로 하나의 코드가 종류가 다른 윈도우를 처리하는데 사용될 수는 없고, 코드는 실행시에 생성되거나 복제되어야 한다

 

 

코드가 하는 일은 hwnd parameter thunk 속한 객체의 포인터를 사용하여 window procedure 교체하는 일이다. (CWindowImplRoot, atlwin.h) 그리고 static 멤버함수 WndProc 으로 jump 하는데, 함수의 번째 파레메터는 HWND 가장한 CWindow 포인터이다. 물론 컴파일러는 에러를 발생시키지 않는다. ATL 구현은 멤버 윈도우 프로시저를 가져오는 부분에 있어서는 MFC 비해 매우 효율적이다. MFC 코드는 윈도우 객체를 조회하는데 윈도우의 개수에 비례하는 선형시간이 소요되는 반면, ATL 상수시간이 소요된다. ATL 코드는 하나의 move, 하나의 jump 사용하여, 최대한 효율적이다

 

 

MFC ATL 비교 

 

위에서 언급했듯이, ATL MFC 구현에는 다음과 같은 차이점이 있다

 

ATL thread-local storage 사용하지 않는다. 그러므로 어떠한 초기화도 사용하지 않는다. MFC 사용하면 AfxWinInit 으로 초기화를 반드시 해야한다. ATL 사용자 코드를 직접적으로 수행한다. 게다가 TLS 접근하는 것은 동적 배열을 사용하므로, 상각된 상수시간( amotized constant time ) 소요되는데, 이것은 MFC 실행 속도를 감소시킬 있다

 

ATL 맵핑은 thread-specific 하지 않다. 프로그래머는 CWindow 객체를 만든 thread에서 뿐만 아니라, 모든 thread에서 사용할 있다. MFC CWnd 같은 객체를 그것을 생성한 thread bind 한다. MFC 포인터를 CWnd* 형으로 전달하는반면, ATL 함수 파라메터로 HWND 헨들을 전달한다

MFC WndProc 간접호출이 많아 오버헤드를 좀더 가진다. ATL 조금더 빠르다

MFC 주어진 HWND CWnd 포인터를 조회하는데 상수시간이 소요된다. 응용프로그램에 윈도우가 다수존재할 경우 이것은 그리 빠른성능을 보이지 않는다. MFC WndProc 윈도우의 개수가 많아 질수록 속도가 느려지는 반면, ATL 윈도우의 개수에 상관없이 동일한 고성능을 보인다