아래의 번역된 글을 이용하여 글을 올린다.
번역문 : http://cafe.naver.com/ArticleRead.nhn?articleid=213&sc=e0d437180f462b9619&clubid=10426365
역 자 : sobahoko
==================================================================================================
원문
: 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 은
윈도우의
개수에
상관없이
동일한
고성능을
보인다.