Windowsプラグラムを書いているとウィンドウクラスを作ろうとしてしまうのはどうやらよくある事のようで、検索等をしてみると関連の話題がよく取り上げられています。
よく紹介されている手法ではウィンドウプロシージャをstaticメンバ関数として定義します。
1 | class Window
2 | {
3 | public:
4 | BOOL Create(DWORD, HINSTANCE);
5 |
6 | static LRESULT CALLBAC WindowProc(HWND, UINT, WPARAM, LPARAM);
7 | };
8 |
9 | BOOL Window::Create(DWORD style, HINSTANCE inst)
10 | {
11 | WNDCLASSEX wc;
12 |
13 | wc.cbSize = sizeof(WNDCLASSEX);
14 | wc.hInstance = inst;
15 | wc.lpfnWndProc = WindowProc;
16 | wc.lpszClassName = "クラス名";
17 | RegisterClassEx(&wc);
18 | HWND wnd = CreateWindow(
19 | "クラス名","ウィンドウ名",
20 | style,
21 | 0, 0, 100, 100,
22 | NULL, 0, inst, (LPVOID)this);
23 | ShowWindow(wnd, SW_SHOW);
24 | return(wnd != NULL);
25 | }
26 |
27 | LRESULT CALLBACK Window::WindowProc(HWND wnd, UINT msg, WPARAM wp, LPARAM lp)
28 | {
29 | Window *pwnd;
30 |
31 | pwnd = GetWindowLong(wnd, GWL_USERDATA);
32 | switch(msg)
33 | {
34 | case WM_NCCREATE:
35 | SetWindowLong(wnd, GWL_USERDATA, ((LPCREATESTRUCT)lp)->lpCreateParams);
36 | break;
37 | default:
38 | return(DefWindowProc(wnd, msg, wp, lp));
39 | break;
40 | }
41 | return(FALSE);
42 | }
こうすることによりRegisterClassExの引数、WNDCLASSEX構造体にWindowProcが引き渡せます。
thisポインタをCreateメンバ関数の引数として渡すことでウィンドウプロシージャ側で受け取ることが可能になります。
ただこのままでは派生した時にやっかいなことになるので手直しします。
1 | class Window
2 | {
3 | public:
4 | BOOL Create(DWORD, HINSTANCE);
5 |
6 | static LRESULT CALLBAC WindowProc(HWND, UINT, WPARAM, LPARAM);
7 | virtual LRESULT WndProc(HWND, UINT, WPARAM, LPARAM);
8 | };
9 |
10 | BOOL Window::Create(DWORD style ,HINSTANCE inst)
11 | {
12 | // 変更なし
13 | }
14 |
15 | LRESULT CALLBACK Window::WindowProc(HWND wnd, UINT msg, WPARAM wp, LPARAM lp)
16 | {
17 | Window *pwnd;
18 |
19 | pwnd = GetWindowLong(wnd, GWL_USERDATA);
20 | if(pwnd) // 追加
21 | {
22 | return(pwnd->WndProc(wnd, msg, wp, lp));
23 | }
24 | switch(msg)
25 | {
26 | case WM_NCCREATE:
27 | SetWindowLong(wnd, GWL_USERDATA, ((LPCREATESTRUCT)lp)->lpCreateParams);
28 | break;
29 | default:
30 | return(DefWindowProc(wnd, msg, wp, lp));
31 | break;
32 | }
33 | return(FALSE);
34 | }
35 |
36 | LRESULT Window::WndProc(HWND wnd, UINT msg, WPARAM wp, LPARAM lp) // 追加
37 | {
38 | switch(msg) // ここにメッセージの振り分けを書く
39 | {
40 | case WM_COMMAND:
41 | break;
42 | }
43 | return(DefWindowProc(wnd, msg, wp, lp));
44 | }
このようにWndProcを定義すれば、子クラスではWndProcをオーバーライドすればいいだけになりますし、staticメンバ関数とも違いメンバ変数も簡単に扱うことが出来ます。
ここまでで納得できたらMFCやATLのクラスを利用してプログラムを楽しんで下さい。(^-^)ノシ
以降はstaticメンバ関数をいかに消すかのみに注力していきます。
ただしVisual C++6.0でのみ動作確認済み、他の環境ではまともに動作するか分かりません。[*1]
まぁ、説明は抜きに、先にサンプルを示します。
1 | class Window
2 | {
3 | private:
4 | WNDPROC m_wndproc; // 追加
5 |
6 | public:
7 | BOOL Create(DWORD, HINSTANCE);
8 |
9 | Window(void); // 追加
10 | ~Window(void); // 追加
11 | virtual LRESULT WndProc(HWND, UINT, WPARAM, LPARAM);
12 | WNDPROC GetWindowProc(void); // 追加
13 | };
14 |
15 | Window::Window(void)
16 | {
17 | m_wndproc = NULL;
18 | }
19 |
20 | Window::~Window(void)
21 | {
22 | free(m_wndproc);
23 | }
24 |
25 | BOOL Window::Create(DWORD style, HINSTANCE inst)
26 | {
27 | // wc.lpfnWndProc = WindowProc; とあったのを wc.lpfnWndProc = GetWindowProc(); と変更のみ
28 | }
29 |
30 | LRESULT Window::WndProc(HWND wnd, UINT msg, WPARAM wp, LPARAM lp)
31 | {
32 | // 変更なし
33 | }
34 |
35 | WNDPROC Window::GetWindowProc(void) // 追加
36 | {
37 | char *thunk;
38 |
39 | if(m_wndproc)
40 | {
41 | return(m_wndproc);
42 | }
43 |
44 | m_wndproc = (WNDPROC)malloc(10);
45 | thunk = (char*)m_wndproc;
46 |
47 | * (thunk + 0) = 0xB9; // mov ecx,offset this(Window*)
48 | *(int*)(thunk + 1) = (int)this;
49 | * (thunk + 5) = 0xE9; // jmp this->WndProc
50 | *(int*)(thunk + 6) = (*((int*)(*((int*)this) + 0 * sizeof(void*)))) - ((int)thunk + 10);
51 |
52 | return(m_wndproc);
53 | }
コンストラクタとデストラクタでは初期化と解放を行うだけです。
GetWindowProcではウィンドウプロシージャを作っています。[*2]
ecxレジスタにthisポインタを入れ、WndProcへjmpするアセンブリコードを入れています。
jmp先は仮想関数テーブル(vtbl)の1番目、つまりここではWndProcになるわけです。
もちろん仮想関数テーブルの1番目と決めうちで指定しているため仮想関数を変に追加したり多重継承するとクラッシュします。(>_<)
こんなコードを書いた翌日には上司からジャーマンスープレックスを決められても仕方がないでしょう。