Win32

Last-modified: 2015-08-31 (月) 01:50:21

アプリケーション

基本的に、アプリケーションを作る際に、main()関数もしくはWinMain()関数をエントリポイントに設定します(Visual Studioで、リンカ設定で変更可能)。
このエントリポイントは、Windows OSがアプリケーションを呼び出す関数になります。
つまり、アプリケーションプロセスは、すべからくエントリポイントの関数から始まり、その関数が終了することで、アプリケーションは終了します。
※ただし、これはごく簡単な説明で、もっともっと複雑なことをWindowsはやっています。

メッセージ

マウスやキーボード操作によって入力された情報は、Windows OSから「メッセージ」としてアプリケーションに送られます。

ウィンドウプロシージャ

Windows OSからメッセージが「送られる」と言っても、単純な関数呼び出しです。
この関数は「ウィンドウプロシージャ関数」と呼ばれており、アプリケーションが Win32 API の RegisterClassEx() 関数を呼び出すことで、Windows OSにアプリケーションのウィンドウプロシージャ関数を登録します。
ただ、Windows OSでは、複数のアプリケーションがあり、そのアプリケーションにも複数のウィンドウがあるので、キーボードやマウス操作の情報をもらいたい箇所はいっぱいあります。なので、基本的に、RegisterClassEx() の呼び出しはウィンドウ単位で行い、そのウィンドウごとに、ウィンドウプロシージャ関数が Windows OS から呼び出されます。
※ちなみに、たいていファイル単位にウィンドウに関する処理を実装し、そのファイル単位にファイルローカルな「WndProc」関数を作ります。

メッセージの種類:システム定義メッセージとアプリケーション定義メッセージ

メッセージは、大別すると次の2つの種類があります。

  • システム定義メッセージ
  • アプリケーション定義メッセージ

システム定義メッセージは、Windows OSが予約しているメッセージで、先のマウス操作に関するものや、キーボード操作に関するものなど、数多くの種類が定義されています。
アプリケーション定義メッセージは、アプリケーションが定義するメッセージで、通常はアプリケーション内での通信や、外部アプリケーションとの通信に使用します。
メッセージは、0x0000~0xFFFFの値が使用され、0x0400~0x7FFFをアプリケーション定義メッセージで使用します。

メッセージの伝達経路:キュードメッセージと非キュードメッセージ

メッセージは「メッセージキュー」と呼ばれるFIFOに送られ(ポストされ)ます。「メッセージキュー」はシステム定義のメモリオブジェクトで、一時的にメッセージを保管しています。そして、メッセージは、ウィンドウプロシージャに渡されます。
さて、このメッセージの種類によっては、このメッセージキューを経由しないことがあります。
そこで、メッセージキューを経由するメッセージを「Queued Message」、メッセージキューを経由せず直接ウィンドウプロシージャに渡されるメッセージを「Nonqueued Message」と呼びます。

キュードメッセージ(Queued Messages、キュー済みメッセージ)

たとえば、マウスやキーボードなどからの入力メッセージは、メッセージキューを経由してウィンドウプロシージャに渡されます。
ということで、Visual Studio 2013 がWin32プロジェクトのソースコードを見てみます。

while (GetMessage(&msg, NULL, 0, 0))
{
    if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
}

ここで、GetMessage() API は、メッセージキューからメッセージを取り出すための関数です。
この API を呼び出すと、引数に指定したmsg構造体にメッセージ内容が書き込まれて、戻ってきます。
ただし、アプリケーション終了を意味する「WM_QUIT」メッセージがやってくると、戻り値が FALSE になり、while ループを抜けます。
さて、TranslateAccelerator() API と TranslateMessage() は、ひとまず放置しておきましょう。
次に、DispatchMessage() API は、GetMessage() API で取得したメッセージをウィンドウプロシージャに送る(Dispatch=発送する、送る)ための関数です(正確には、Windows OSに指示するだけ)。
この後、ウィンドウプロシージャが呼ばれます。
なお、逆にメッセージキューにメッセージを送る際は、PostMessage() API を使用します。

あと、落穂拾い的ですが、GetMessage() API は、メッセージが取り出せるまで返ってきません。そのため、ゲームアプリケーションのようにリアルタイムに他の処理をしなければいけない場合、GetMessage() でメッセージを取り出している時間が有効に使用できません。そこで、PeekMessage() API を使用します。こちらは、メッセージが取り出せなくても戻ってきます。
また、PostMessage() API はメッセージキューがいっぱいだった場合、メッセージをキューせずに戻ってきます。使用する場合は、必ず戻り値を調べて、キューできたことを確認する必要が有ります。

非キュードメッセージ(Nonqueued Messages、キューなしメッセージ)

次に、メッセージキューを使用せず、直接、ウィンドウプロシージャにメッセージが送られる場合があります。
たとえば、フォーカスが変わったことを意味する「WM_SETFOCUS」メッセージは、非キュードメッセージの1つです。
アプリが独自に非キュードメッセージを送る際は、SendMessage() API を使用します。
ただし、SendMessage() APIを使用する場合、デッドロックに注意を払う必要が有ります。SendMessage() は、必ず送り先のウィンドウプロシージャを呼び出して、復帰するのを待ちます。このため、もし送り先のウィンドウが、もし送り元のウィンドウがやっていた何らかの処理を待ったり、終了を待ったり、送り元のウィンドウに再度、SendMessage() してしまうとデッドロックします。

メッセージの種類:特別なメッセージの詳細

システム定義メッセージの中には、特別扱いされているメッセージがあります。
これらの特別なメッセージは、前述の「メッセージキュー」と呼ばれるFIFOに入っているくせに、「First-In, First-Out」になっていないものや、そもそも1つのメッセージが複数のウィンドウプロシージャに送られることがあります。
ということで、その辺をまとめます。

複数のメッセージがまとめられる系

ウィンドウの内容物を描画する必要があることを意味する「WM_PAINT」メッセージは、キュードメッセージですが、このメッセージはFIFOになっていません。単純なFIFOにしてしまうと、同じ箇所に対して何度も再描画してしまう可能性があり、描画パフォーマンスが低下してしまいます。そこで、複数のWM_PAINTは1つにまとめられ、1回の描画処理で済むようにメッセージキューが設計されています。

すべてのウィンドウにメッセージが送られる系

たとえば、Windows OS の管理しているシステム時刻が変わったことを意味する「WM_TIMECHANGE」メッセージは、その特性上、アクティブウィンドウにだけメッセージを送ると、1つのウィンドウだけに時刻が変わったことが通知され、あとのウィンドウはそれを知る術がなくなってしまいます。それはおかしいので、すべてのウィンドウにこのメッセージは通知されます。

ご参考