Win32
ウィンドウクラスを構築する Part.2


HomeProgramming TipsWin32SDK Tips[SDK-005]

 ダウンロード : ウィンドウクラス構築サンプル2(2006/03/25) 
※ 2006/03/25 WM_PAINT内記述ミス修正

前回の続きである。
とりあえずウィンドウクラスらしきものは出来た。通常の使用では何ら問題は起きない。
ところが複数のウィンドウを作ろうとしたとたんに問題が起きる。

プロシージャは静的メンバ関数なのだ。
静的なメンバは動的なメンバのアドレスを知ることが出来なければアクセスすることが出来ない。
通常は静的メンバは静的メンバしか扱えないのだ。
インスタンスされたオブジェクトのポインタを静的メンバ変数に格納しても複数には対応できない。

そのポインタを静的配列として複数格納しても、自分が一体何番目にインスタンスされた
オブジェクトかを知ることが出来なければ、どのアドレスが正しいのかを知ることは出来ない。

ああ〜、呼び出された俺は一体何者なんだ〜(笑)。

自分が何者なのかを知る唯一の方法は…、おお、一つだけある。それがウィンドウハンドルである。
ウィンドウハンドルはウィンドウを生成したときに OS から発行される唯一無二の番号である。
このウィンドウハンドルとポインタアドレスを関連付けて、ウィンドウハンドルから
ポインタアドレスを取り出すことは出来ないのか。

…と、いうことで、出来るのだ。ここで役に立つのが SetProp(), GetProp(), RemoveProp() である。
これらはウィンドウハンドルと識別子(文字列でよい)の組み合わせで
任意のデータを登録、呼び出し、破棄することが出来る便利なものである。

考え方は以下の通りである。

SetProp で CreateWindow 直後にクラスポインタを登録する。
GetProp でプロシージャ呼び出しの時にクラスポインタを取り出してメンバを使用する。
RemoveProp で WM_DESTROY 時点で登録された情報を破棄する。

前回のプログラムを以下のように改造する。


// -------------------------------------------------------------------
//  識別子定義
// -------------------------------------------------------------------
#define PROP_WINPROC    "PropClassWindowProc"   // プロパティを識別する文字列



// -------------------------------------------------------------------
// CBaseWindow コンストラクタ
// -------------------------------------------------------------------

    // 〜 前略 〜
    m_hWnd = CreateWindowEx(
        dwExStyle,                              // 拡張ウィンドウスタイル
        m_pszClassName,                         // ウィンドウ名
        NULL,                                   // キャプション
        dwStyle,                                // ウィンドウスタイル
        CW_USEDEFAULT, CW_USEDEFAULT,           // 好きな位置に表示
        CW_USEDEFAULT, CW_USEDEFAULT,           // サイズ
        NULL, NULL, wc.hInstance, NULL          // インスタンス指定
    );
    if (!m_hWnd) return;

    // オブジェクト(自分自身)のポインタを登録する
    ::SetProp(m_hWnd, PROP_WINPROC, this);      // ←追加

    ::ShowWindow(m_hWnd, SW_SHOWDEFAULT);
    ::UpdateWindow(m_hWnd);




// -------------------------------------------------------------------
// [静的] ウィンドウプロシージャ
// -------------------------------------------------------------------
LRESULT CALLBACK CBaseWindow::WndProc(
    HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam
){
    // 必要不可欠なメッセージ処理をする
    switch (msg){
        case WM_CLOSE:
            ::DestroyWindow(hWnd);
            return 0;
        case WM_DESTROY:
            // 関連付けを抹消する
            ::RemoveProp(hWnd, PROP_WINPROC);   // ←追加
            ::PostQuitMessage(0);
            return 0;
    }

    // 以下、新規追加
    // 関連付けられたポインタを取り出す
    CBaseWindow* win = (CBaseWindow*)GetProp(hWnd, PROP_WINPROC);
    if (!win) return DefWindowProc(hWnd, msg, wParam, lParam);

    // インスタンス別ウィンドウプロシージャ処理を行う
    return win->WndProcLocal(hWnd, msg, wParam, lParam);
}



// -------------------------------------------------------------------
// インスタンス別ウィンドウプロシージャ ※新規追加
// -------------------------------------------------------------------
LRESULT CBaseWindow::WndProcLocal(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    // 個別のメッセージ処理を記述する
    return ::DefWindowProc(hWnd, msg, wParam, lParam);
}


ここまで出来れば、後は、このクラスをそのまま拡張していくもヨシ、派生クラスを作り機能拡張するもヨシである。

派生クラスを作る場合は、WndProcLocal をオーバーライドする。そして、派生先でメッセージ処理を行う。
処理しなかったメッセージは CBaseWindow の WndProcLocal を呼び出す。これで処理の流れは完結する。


// -------------------------------------------------------------------
// [派生] インスタンス別ウィンドウプロシージャ
// -------------------------------------------------------------------
LRESULT CDerivedWindow::WndProcLocal(
    HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam
){
    PAINTSTRUCT ps;                            // **2006/03/25バグ修正
    switch (msg){
        case WM_PAINT:
        {
            HDC hDC = ::BeginPaint(hWnd, &ps); // **2006/03/25バグ修正
            // 〜 独自の処理 〜
            ::EndPaint(hWnd, &ps);             // **2006/03/25バグ修正
            return 0;
        }
    }
    return CBaseWindow::WndProcLocal(hWnd, msg, wParam, lParam);
}

※2006/03/25追記
WM_PAINT では唯一 BeginPaint(); 〜 EndPaint(); で HDC を管理しなければならないと初めて知りました。以下は MSDNヘルプの抜粋です。
> アプリケーションがこの関数を呼び出せるのは、WM_PAINT メッセージに応答するときだけです。
> 更新領域が消去されるようにフラグが設定されていれば、BeginPaint 関数は WM_ERASEBKGND メッセージをウィンドウに送ります。
> 描画される範囲内にキャレットがある場合、そのキャレットが消去されないように、BeginPaint 関数が自動的に非表示にします、
> ウィンドウのクラスが背景色のブラシを持っている場合には、BeginPaint 関数は制御が戻る前に、そのブラシを使って更新リージョンの背景を消去します。
つまり、画面の再描画に関する様々な手続きを行ってくれるのが BeginPaint() ということです。
ここを今まで本情報では GetDC(); 〜 DeleteDC(); と間違って記述していました。
さらに GetDC() したデバイスコンテキストは ReleaseDC() しなければならないのですが、不思議なことに DeleteDC() していました。
以上、お詫びして訂正いたします。ご指摘ご指導くださったヴォーガ氏に感謝します。


なお、一つだけ注意が必要である…本当はたくさん注意が必要だが(爆)。それはサブクラスは使えないということだ。
サブクラスは OS が把握しているプロシージャアドレスを書き換えることで成立する。
OS は RegisterClass によりプロシージャアドレスを得ている。
そして、RegisterClass はどのオブジェクトであっても必ず静的なメンバである WndProc なのだ。
つまり、この手法で作成されたクラスに対してサブクラス化をすると、
どのオブジェクトのプロシージャなのかは GetProp しなければ分からないと言うことだ。

独自で制作したウィンドウクラスは MFC の CWnd より軽い。
だが、違いが分かる程かと言えば、最近の CPU の超高性能化により差はまったく分からないと言ってよい。
オブジェクト指向では「あるものは使う」のが便利なプログラミングの基本である。
そのため、わざわざ SDK でウィンドウクラスを作るぐらいなら、CWnd の恩恵にあずかれば良いと
最近は本気で考えている。…ちょっと Microsoft 独特のクセはありますけどね。


 Copyright 2005 VALGUS. All Rights Reserved.