0.ウィンドウズプログラミング事始


0.まずはWinMain()から

今回はゲーム作りの準備としてウィンドウズでゲームを作ろうとする人が
必ず一度は通る道であるウィンドウの初期化をします。
かく言う私も数年前に通りました。が、久しぶりに作ろうとすると細部は綺麗に忘れてしまっていました。orz
(それでも何とかなる部分なのでそんな気分で眺めてみてもらえばいいかなと。)
まあとにかくウィンドウズでゲームを作るのに必要な部分なので面倒ですが一つづつ見ていきます。
第一にウィンドウズプログラミングではコンソールでのmain()に変わるWinMain()がないと実行も出来ません
という事で早速WinMain()を作ってみます。
極短ウィンドウズプログラム
#include <windows.h>

int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpszCmdLine, int nCmdShow)
{
    return 0;
}
???となった人。私と同じです。
これまでコンソールのみでプログラムを作ってきた人には馴染みの無い型がたくさん使われていますし、
何か変な所にWINAPIというのも入っているし、初めて見ると面喰うと思います。
ただじっくり見ていくと、main()に似ている事に気づくと思います。
lpszCmdLineというのはコマンドライン引数っぽいですし、戻値として0を返すというのもmain()と同じです。
ウィンドウズアプリケーションはこのWinMain()で始まり、この関数を抜けると終了します。
その間にゲームを遊んでもらうわけですが、試しに上のプログラムを書いて動かして見ると分かりますが、
ウィンドウがなく、一瞬で終わってしまいます。
相当ハイセンスなゲームを作ろうとしている人でなければ、ウィンドウが必要なはずなので、
次にウィンドウの作り方を見ていきます。

1.ウィンドウクラスの登録

上のプログラムの一行目を見ると、windows.hなるものをインクルードしています。
これにはウィンドウズアプリケーションで使う、様々な関数や構造体、定数が定義されていますので、
ウィンドウズアプリケーションを作る時にはとりあえずインクルードしておきましょう。
で本題のウィンドウクラスの登録ですが、このウィンドウクラスというのは、
C++でのクラスという概念よりはRPGの武器生成のような雰囲気で、
幾つかの可能な組み合わせの中から、自分の作りたいものを選らぶという感じです。
で実際のソースを見ると次のようになっています。
ウィンドウクラスの設定
    WNDCLASSEX wc;
    wc.cbSize = sizeof(WNDCLASSEX);
    wc.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;
    wc.lpfnWndProc = WindowProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = g_hInstance;
    wc.hCursor = LoadCursor(0, IDC_ARROW);
    wc.hIcon = LoadIcon(0, IDI_WINLOGO);
    wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
    wc.lpszMenuName = 0;
    wc.lpszClassName = WINDOW_NAME;
    wc.hIconSm = 0;
cbSizeというのはこの構造体の大きさです。とりあえずsizeofして入れてあげましょう。
styleというのはウィンドウの性質に関する設定です。CS_HREDRAW、CS_VREDRAWはそれぞれ再描画に関する設定で、
CS_DBLCLKSというのはダブルクリックをされた時にウィンドウに知らせるかどうかについての設定です。
lpfnWndProcというのはウィンドウプロシージャ関数へのポインタです。
これを設定することによってクリックされた場合などにこの関数を通して通知されるようになります。
cbClsExtraウィンドウクラス単位での補足メモリ領域のバイト数です。使ったことがありません。
cbWndExtraウィンドウ単位での補足メモリ領域のバイト数です。使ったことがありません。
hInstanceこれにWinMain()の引数にあったインスタンスハンドルを渡します。
hCursorカーソルハンドルです。
hIconアイコンハンドルです。デスクトップなどで表示されるあれです。
hbrBackground背景の塗り潰し用ブラシハンドルです。再描画時に使用されます。
lpszMenuNameメニューリソース名です。
lpszClassName登録するこのウィンドウの名前です。
hIconSmタイトルバーなどに表示される小さなアイコン用のハンドルです。
とずらずらと書きましたが、ゲームを作るだけなら上のような設定で十分です。
で構造体に値を設定し終わったら次の関数を読んで実際に登録します。
ウィンドウクラスの登録
    //ウィンドウクラスの登録
    if(RegisterClassEx(&wc) == 0)
    {
        MessageBox(0, "ウィンドウクラスの登録に失敗", "RegisterClassEx()", MB_OK);
        return 0;
    }
として、RegisterClassEx()の戻値が0以外であればウィンドウの登録に成功したということなので、
次のウィンドウの生成に進みます。
(上のソースにもありますが、ウィンドウズアプリケーションではprintf()が使えないので、
デバッグ用の出力にはMessageBox()などを使うといいと思います。)

2.ウィンドウの生成

引き続いてウィンドウの生成です。
生成自体は関数一つで行えるので簡単です。
呼び出しは次のような感じになります。
ウィンドウの生成
    HWND g_hMainWindow = CreateWindowEx(WINDOW_STYLEEX, WINDOW_NAME, WINDOW_TITLE, WINDOW_STYLE,
                            x, y, width, height, hParent, hMenu, g_hInstance, pParam);
引数を一つずつ見ていきます。
WINDOW_STYLEEXは拡張ウィンドウスタイルです。ドロップされたファイルの受け取り用スタイルなどが設定できます。
WINDOW_NAMEはウィンドウクラス名です。先ほど登録した名前を渡しましょう。
WINDOW_TITLEにはタイトルバーに表示させたい文字列を渡します。
WINDOW_STYLEにはウィンドウのスタイルを設定します。
xはウィンドウの初期x位置です。
yはウィンドウの初期y位置です。
widthはウィンドウの初期横幅です。
heightはウィンドウの初期縦幅です。
hParentは親ウィンドウのハンドルです。
hMenuはメニューハンドルです。
g_hInstanceはインスタンスハンドルです。
pParamは生成メッセージに渡される引数データへのポインタを渡します。
で戻値として返ってきたウィンドウハンドルが0でなければウィンドウの生成が成功したということなので、
ウィンドウ表示
    ShowWindow(g_hMainWindow, nCmdShow);
として返ってきたウィンドウハンドルを使ってShowWindow()で表示させます。
ここで、WinMain()の引数のnCmdShowを使います。これはウィンドウの初期表示状態に関する設定値です。
ここまで来るとウィンドウは表示されていると思いますが、まだウィンドウプロシージャ関数がなく動かないので次のウィンドウの管理へ進みます。

3.ウィンドウの管理

前回までの流れでウィンドウの生成は行えたので、今回はウィンドウの管理を行います。
ウィンドウの管理において重要になってくるのが、メッセージという概念です。
メッセージというのはあるウィンドウに対して何らかの操作が行われた場合などにウィンドウに対して発行されるものです。
そのメッセージに対して応答することで、例えば閉じボタンを押された時にウィンドウを閉じるなどの振る舞いが可能になります。
でウィンドウの管理ですが、大別すると二つの部分に分けられます。
1.メッセージが発行されるのを待っている部分
2.実際にメッセージに対して応答してする部分
です。
まずメッセージが発行されるのを待っている部分のソースを見てみます。
メッセージループの中身
    MSG msg;
    while(1)
    {
        if(PeekMessage(&msg, 0, 0, 0, PM_REMOVE))
        {
            //終了メッセージがきたら終了
            if(msg.message == WM_QUIT) break;

            DispatchMessage(&msg);
        }
        else
        {
            if(g_active)
            {
            }
            else
            {
                WaitMessage();
            }
        }
    }
ずらーと長くなっていますが、やっていること自体はそれほどありません。
まずメッセージの受け取り用変数msgを作っています。
その次に終了メッセージがくるまで周り続けるwhileで無限ループを作っています。
PeekMessage()でメッセージがあるか調べ、あればDispatchMessage()でウィンドウに対してメッセージを通知します。
ない場合にはアプリケーションがアクティブかを調べて、アクティブでなければWaitMessage()を呼び次にメッセージが送られてくるまで待ちます。
とメッセージの受け取り部分に関する処理はこんな感じです。
続いて実際にメッセージに対して応答していく部分のソースを見てみます。
メッセージプロシージャの中身
LRESULT CALLBACK WindowProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch(msg)
    {
    case WM_ACTIVATEAPP: g_active = (bool)(wParam != 0); break;
    case WM_DESTROY: PostQuitMessage(0); break;
    default: return DefWindowProc(hWnd, msg, wParam, lParam); break;
    };

    return  FALSE;
}
ウィンドウクラスの登録の時に渡していたのがこのWindowProc()へのポインタです。
では一つづつ引数を見て行きましょう。
hWndがメッセージを受けとったウィンドウのハンドルです。
msgがメッセージの種類定数です。 WM_ACTIVATEAPPというのはアプリケーションのアクティブ状態が変化する場合に呼ばれるメッセージです。WM_DESTROYはその名の通りウィンドウが破棄される時に呼ばれます。
wParam, lParamはメッセージ毎に意味が変わってくる追加パラメータです。各メッセージとパラメータの意味に関してはMSDN等を参照してください。
で具体的に各メッセージに対して何をしているかを見てみると、
WM_ACTIVATEAPPの時にはg_activeというグローバル変数にアプリケーションのアクティブ状態を設定し、
WM_DESTROYの時にはPostQuitMessage()を呼び、アプリケーションの終了を通知しています。
それ以外のメッセージであれば、DefWindowProc()という各メッセージに対して標準的な振る舞いをする関数を呼び出しています。
とここまでかなり長くなってしまいましたが、これで一応ウィンドウの初期化は完了です。
次回は今回作った処理をもう少し扱い易いようにして見ようかなと思います。
より詳しくウィンドウズプログラミングを知りたい方は、「猫でもわかるプログラミング 」さん等を御覧ください。
今回のソース。
戻る。